:root{
  --bg:#0a0a0a;
  --panel:#121212;
  --panel2:#181818;
  --panel3:#202020;
  --border:#2c2c29;
  --border-soft:#212120;
  --text:#ECEAE4;
  --text-dim:#9a978d;
  --text-faint:#6b6960;
  --accent:#EC2027;
  --accent-dim:#b81920;
  --accent-rgb:236,32,39;
  --good:#6FCF97;
  --good-bg:#1c2a20;
  --good-rgb:111,207,151;
  --warn:#F2A93B;
  --warn-bg:#2e2515;
  --warn-rgb:242,169,59;
  --danger:#EB6B5E;
  --danger-bg:#2e1c18;
  --danger-rgb:235,107,94;
  --info:#5AA9E6;
  --info-bg:#16232e;
  --info-rgb:90,169,230;
  --gray-bg:#26251f;
  --panel-rgb:18,18,18;
  --panel2-rgb:24,24,24;
  /* Plain R,G,B triplets (no # / rgb() wrapper) so rgba(var(--x-rgb),.N) works
     for opacity-tinted variants — box shadows, hover glows, status pill
     backgrounds — without duplicating the color as a second hardcoded value
     that silently drifts from the solid one above whenever the palette
     changes. --shadow-rgb is 0,0,0 today; if the palette ever moves toward
     colored/tinted shadows instead of pure black, this is the one place. */
  --shadow-rgb:0,0,0;
  --radius:10px;
  --radius-lg:9px;
  --sans:'Noto Sans',sans-serif;
  --display:'Barlow Condensed',sans-serif;
  --display-strong:'Saira Condensed',sans-serif;
  --mono:'JetBrains Mono',monospace;
}
*{box-sizing:border-box;}
html{background:var(--bg);}
/* Ambient blob glow — the reason cards downstream can go translucent
   (backdrop-filter) instead of flat solid panels: without something with
   color/shape sitting behind them, "glass" cards would just look like plain
   dark boxes with a slight blur, no different from opaque ones. Painted as
   body's own `background` (multiple radial-gradient layers under the solid
   base color) rather than a positioned/z-index pseudo-element — see the
   #pwa-intro comment elsewhere in this file: html+body both already set an
   explicit background, which triggers the CSS "body background becomes the
   canvas" quirk and silently eats any negative-z-index element behind it.
   A background-image layer has no such stacking-context pitfall; it's
   unconditionally the bottom paint layer of the box it's declared on.
   background-attachment:fixed keeps the glow anchored to the viewport
   (an ambient light source) instead of scrolling away with page content. */
body{
  margin:0;padding:0;color:var(--text);font-family:var(--sans);-webkit-font-smoothing:antialiased;
  background:
    radial-gradient(1100px 900px at 85% -12%, rgba(var(--accent-rgb),.26), transparent 62%),
    radial-gradient(820px 720px at -6% 22%, rgba(var(--accent-rgb),.14), transparent 60%),
    radial-gradient(950px 800px at 50% 118%, rgba(var(--accent-rgb),.10), transparent 58%),
    radial-gradient(700px 600px at 96% 78%, rgba(var(--accent-rgb),.09), transparent 60%),
    var(--bg);
  background-attachment:fixed;
  min-height:100vh;
  animation:pageFadeIn .32s ease both;
}
@keyframes pageFadeIn{from{opacity:0;}to{opacity:1;}}
/* Opt-in fade-out before a real cross-document navigation (login <-> app) —
   plain CSS opacity so it works identically regardless of whether the
   browser supports the View Transitions API; see login.html and doLogout()
   in app.js, which add this class then wait out the transition before
   actually changing location.href. */
body.page-leaving{opacity:0;transition:opacity .18s ease;pointer-events:none;}
/* Baseline so a button never shows native OS chrome even if a specific
   class is missing — .btn/.btn-icon/etc below all set their own
   background/border via a class selector, which always wins over this bare
   element one regardless of source order, so this only ever affects a
   button that truly has no other styling. */
button{font-family:var(--sans);cursor:pointer;background:none;border:none;color:inherit;appearance:none;-webkit-appearance:none;padding:0;}
input,select,textarea{font-family:var(--sans);}
::-webkit-scrollbar{width:8px;height:8px;}
::-webkit-scrollbar-thumb{background:var(--panel3);border-radius:4px;transition:background .15s ease;}
::-webkit-scrollbar-thumb:hover{background:var(--text-faint);}

.app{display:flex;align-items:stretch;min-height:100vh;}

/* sidebar */
.sidebar{width:288px;flex-shrink:0;background:rgba(var(--panel-rgb),.62);backdrop-filter:blur(22px) saturate(150%);-webkit-backdrop-filter:blur(22px) saturate(150%);border-right:1px solid var(--border-soft);box-shadow:6px 0 24px rgba(var(--shadow-rgb),.3);display:flex;flex-direction:column;padding:28px 20px;position:sticky;top:0;height:100vh;overflow-y:auto;z-index:1;}
.sidebar-brand{display:flex;align-items:center;padding:4px 6px 32px;}
/* filter lives on this wrapper div, NOT on the <svg> itself — Chromium has a
   known bug where animating/transitioning `filter` directly on an inline SVG
   that fills its shapes via an internal <defs><style> block (like this logo)
   can lose the fill mid-transition, leaving just an outline ("hollow" logo)
   until the next repaint. Filtering the div instead composites the SVG's
   already-rendered bitmap, sidestepping that whole bug class. */
.sidebar-logo{height:38px;width:auto;flex-shrink:0;transition:filter .25s ease;filter:drop-shadow(0 0 0 rgba(var(--accent-rgb),0));}
.sidebar-logo svg{height:100%;width:auto;display:block;}
.sidebar-brand:hover .sidebar-logo{filter:drop-shadow(0 0 6px rgba(var(--accent-rgb),.6));}

.sidebar-nav{display:flex;flex-direction:column;gap:6px;flex:1;position:relative;}
.sidebar-divider{height:1px;background:var(--border-soft);margin:16px 6px;flex-shrink:0;}
.sidebar-group{display:flex;flex-direction:column;}
.sidebar-footer{margin-top:auto;padding-top:20px;border-top:1px solid var(--border-soft);display:flex;flex-direction:column;gap:14px;flex-shrink:0;}

/* Slides between items instead of the active state just popping on/off —
   see positionSidenavIndicator() in app.js, which measures the active
   .sidenav-item and FLIP-animates this element to match. Sits behind the
   items (z-index 0 vs 1) since it supplies the "selected" background that
   .sidenav-item.active itself no longer paints. */
.sidenav-indicator{position:absolute;left:0;right:0;top:0;height:0;z-index:0;background:linear-gradient(90deg,rgba(var(--accent-rgb),.14),var(--panel2) 65%);box-shadow:inset 0 0 0 1px rgba(var(--accent-rgb),.14);border-radius:var(--radius);opacity:0;transition:top .22s cubic-bezier(.4,0,.2,1),height .22s cubic-bezier(.4,0,.2,1),opacity .15s ease;pointer-events:none;}
.sidenav-item{position:relative;z-index:1;display:flex;align-items:center;gap:13px;background:none;border:none;color:var(--text-dim);padding:12px 13px;border-radius:var(--radius);font-size:14.5px;font-weight:500;text-align:left;text-decoration:none;width:100%;white-space:nowrap;transition:background .15s ease,color .15s ease;}
.sidenav-item:hover{background:var(--panel2);color:var(--text);}
.sidenav-item.active{color:var(--accent);font-weight:600;}
.sidenav-label{overflow:hidden;text-overflow:ellipsis;}
.sidenav-chevron{margin-left:auto;color:var(--text-faint);font-size:17px;line-height:1;transition:transform .15s ease;flex-shrink:0;}
.sidenav-chevron.open{transform:rotate(90deg);}
.sidenav-subnav{display:flex;flex-direction:column;gap:4px;margin:4px 0 6px 26px;padding-left:12px;border-left:1px solid var(--border-soft);}
.sidenav-subitem{padding:9px 13px;font-size:13.5px;}
.sidenav-subitem .nav-icon{width:19px;height:19px;}

.nav-icon{display:inline-flex;width:23px;height:23px;flex-shrink:0;}
.nav-icon-sm{width:18px;height:18px;}
.nav-icon-wrap{position:relative;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;}
.nav-icon svg{width:100%;height:100%;display:block;}
.sidenav-item.voz-live{color:var(--accent);}
/* Expanding, fading rings centered on the mic icon — "someone's in the
   room" presence indicator, independent of whether you've joined yourself.
   Lives outside #app (see vozSyncSidebarRipple) so it never gets torn down
   and restarted by a render() elsewhere in the app; position is fixed and
   set inline in JS to track the icon. */
.voz-ripple{position:fixed;pointer-events:none;}
.voz-ripple span{position:absolute;inset:0;border-radius:50%;border:1px solid var(--accent);opacity:0;animation:voz-ripple-pulse 2.4s ease-out infinite;}
.voz-ripple span:nth-child(2){animation-delay:.8s;}
.voz-ripple span:nth-child(3){animation-delay:1.6s;}
@keyframes voz-ripple-pulse{
  0%{transform:scale(1.5);opacity:.75;}
  100%{transform:scale(2.6);opacity:0;}
}
/* These are solid-silhouette icons (fill only, stroke:none in the source files),
   so stroke-width alone does nothing — we turn stroke ON here. Each icon's
   viewBox uses a different unit scale (24x30 vs 110x135 vs 100x125...), so a
   flat stroke-width would render at a different visual thickness per icon;
   vector-effect:non-scaling-stroke pins it to actual screen pixels instead,
   which is what makes the weight look consistent across all of them. */
.nav-icon svg path,.nav-icon svg g{
  /* A few source files set color:#000000 inline (Inkscape export cruft),
     which poisons `currentColor` on this element specifically — force it
     back to the real inherited color before using it for fill/stroke. */
  color:inherit !important;
  fill:currentColor !important;
  stroke:currentColor !important;
  stroke-width:0.5px !important;
  vector-effect:non-scaling-stroke;
  stroke-linejoin:round;
  stroke-linecap:round;
}

/* Orçamento's service/despesa row icons — pre-colored white outline icons,
   left as authored (no currentColor/stroke-width override like .nav-icon,
   which would either fill in these stroke-only shapes or thin them out). */
.svc-icon{display:inline-flex;width:22px;height:22px;flex-shrink:0;margin-top:1px;}
.svc-icon svg{width:100%;height:100%;display:block;}

/* Equipe's Instagram/WhatsApp card links (personCard) — same "leave the
   fill alone" reasoning as .svc-icon above: these are already
   fill="currentColor" outline icons, not the solid-silhouette style
   .nav-icon's forced stroke override is tuned for. */
.person-link-icon{display:inline-flex;width:16px;height:16px;flex-shrink:0;}
.person-link-icon svg{width:100%;height:100%;display:block;}

.savebadge{font-size:11px;color:var(--text-faint);font-family:var(--mono);display:flex;align-items:center;gap:6px;}
.dot{width:6px;height:6px;border-radius:50%;background:var(--good);}
/* One-shot pulse right after a save round-trip lands — see renderSaveBadge()
   in app.js. A fresh element every time (b.innerHTML rebuilds it), so the
   animation always replays from the start rather than needing a restart hack. */
.dot-pulse{animation:dotPulse .6s ease;}
@keyframes dotPulse{
  0%{box-shadow:0 0 0 0 rgba(var(--good-rgb),.55);}
  60%{transform:scale(1.6);box-shadow:0 0 0 6px rgba(var(--good-rgb),0);}
  100%{transform:scale(1);box-shadow:0 0 0 6px rgba(var(--good-rgb),0);}
}

/* main content */
.main{flex:1;min-width:0;position:relative;z-index:1;}

/* Static blurred brand mark sitting behind the content, same logo/opacity
   idea as /briefing's .bf-watermark but blurred instead of crisp-and-faint,
   and deliberately not animated (see body's own radial-gradient blobs above
   for the ambient color — this is the "paper watermark" layer on top of
   that). position:fixed + a plain positive z-index, same pattern already
   proven for .voz-avatar-overlay elsewhere in this file: negative z-index
   was tried there first and silently swallowed by the html/body canvas-
   background quirk (both set an explicit `background` — see body{} above),
   so this stays positive and simply relies on .main/.sidebar (both z-index:1)
   sitting later in paint order to cover it, not on being "behind" via a
   negative index. */
.app-watermark{position:fixed;inset:0;z-index:0;display:flex;align-items:center;justify-content:center;overflow:hidden;pointer-events:none;}
.app-watermark svg,.app-watermark img{width:min(70vw,900px);height:auto;filter:blur(46px);opacity:.05;transform:translate(6%,-4%);}
.main-inner{max-width:1180px;margin:0 auto;padding:28px 28px 80px;}
/* Tarefas: fixed-268px columns mean the board actually benefits from extra
   width instead of wrapping/scrolling sooner than it has to. .main is
   already a flex sibling of the 288px sidebar (see .app/.sidebar above), so
   it can never physically reach under it — dropping the max-width here just
   lets the board use all of .main's own width, and the existing left/right
   padding above is the "simple constraint" keeping it off both edges. */
.main-inner-wide{max-width:none;}
/* Named so goToTab()'s document.startViewTransition() only cross-fades the
   tab content itself, not the whole page (sidebar/header stay put — only
   their active-state classes change, which shouldn't visually "transition"). */
#content{ view-transition-name: main-content; }
/* Plain crossfade between tabs — no explicit keyframes needed, just turn off
   the browser's default morph/resize animation on the group (which is what
   makes an unstyled view-transition look like it's scaling/sliding when the
   old and new tab content are different heights) so only the UA's built-in
   fade-out/fade-in on old/new is left running. */
::view-transition-group(main-content){ animation-duration:.18s; animation-timing-function:ease; }
::view-transition-old(main-content),
::view-transition-new(main-content){
  animation-duration:.18s;
  animation-timing-function:ease;
  height:100%;
  mix-blend-mode:normal;
}
::view-transition-old(main-content){ animation-name:vtFadeOut; }
::view-transition-new(main-content){ animation-name:vtFadeIn; }
@keyframes vtFadeOut{to{opacity:0;}}
@keyframes vtFadeIn{from{opacity:0;}}

/* mobile header + off-canvas drawer — hidden entirely above the breakpoint */
.mobile-header{display:none;}
.mobile-nav-backdrop{display:none;}

@media(max-width:820px){
  .app{flex-direction:column;min-height:0;}

  /* Slim bar by default: hamburger + compact mark, pinned to the top.
     Past a scroll threshold it collapses into a floating circle at the
     top-left holding just the hamburger — see the "scrolled" state below. */
  .mobile-header{
    display:flex;align-items:center;gap:12px;
    position:fixed;top:0;left:0;right:0;z-index:40;
    background:var(--panel);border-bottom:1px solid var(--border-soft);
    padding:14px 16px;
    transition:top .25s ease,left .25s ease,right .25s ease,width .25s ease,height .25s ease,
               padding .25s ease,border-radius .25s ease,box-shadow .25s ease,border-color .25s ease;
  }
  .mobile-header.scrolled{
    top:14px;left:14px;right:auto;
    width:48px;height:48px;padding:0;
    border-radius:50%;
    border:1px solid var(--border-soft);
    box-shadow:0 4px 16px rgba(var(--shadow-rgb),.45);
    justify-content:center;
  }
  /* Taken out of flex flow and centered on the header's own midpoint
     (the header is position:fixed, so it's already the containing block)
     instead of sitting in flow after the hamburger — flow order would
     center it relative to the remaining space next to the button, not the
     bar as a whole. */
  .mobile-header-logo{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);height:20px;width:auto;max-width:140px;overflow:hidden;transition:opacity .15s ease,max-width .2s ease;}
  .mobile-header-logo svg{height:100%;width:auto;display:block;}
  .mobile-header.scrolled .mobile-header-logo{opacity:0;max-width:0;pointer-events:none;}

  .mobile-hamburger{display:flex;justify-content:center;align-items:center;width:32px;height:32px;background:none;border:none;padding:0;flex-shrink:0;color:var(--text);font-size:24px;line-height:1;}
  .mobile-hamburger svg{display:block;}

  /* room for the fixed header so page content doesn't start underneath it */
  .app{padding-top:60px;}

  /* Sidebar becomes an off-canvas drawer, slid in from the left */
  .sidebar{
    position:fixed;top:0;left:0;bottom:0;width:280px;max-width:82vw;height:100vh;
    flex-direction:column;align-items:stretch;
    overflow-x:hidden;overflow-y:auto;
    padding:24px 18px;
    transform:translateX(-100%);
    transition:transform .25s ease;
    z-index:50;
    box-shadow:4px 0 28px rgba(var(--shadow-rgb),.5);
    touch-action:pan-y; /* let vertical scroll stay native; horizontal swipe-to-close is handled in JS */
  }
  .sidebar.mobile-open{transform:translateX(0);}
  .sidebar-brand{padding:4px 6px 28px;flex-shrink:0;}
  .sidebar-nav{flex-direction:column;flex:1;gap:6px;}
  .sidebar-group{flex-direction:column;}
  .sidenav-subnav{flex-direction:column;margin:4px 0 6px 26px;padding-left:12px;}
  .sidebar-divider{width:auto;height:1px;margin:16px 6px;}
  .sidebar-footer{flex-direction:column;margin-top:auto;padding-top:20px;border-top:1px solid var(--border-soft);border-left:none;padding-left:0;flex-shrink:0;}
  .sidenav-label{display:inline;}
  .sidenav-item{padding:8px 12px;font-size:12px;}

  .mobile-nav-backdrop{
    display:block;position:fixed;inset:0;background:rgba(var(--shadow-rgb),0);z-index:45;
    opacity:0;pointer-events:none;transition:opacity .25s ease,background-color .25s ease;
  }
  .mobile-nav-backdrop.open{background:rgba(var(--shadow-rgb),.55);opacity:1;pointer-events:auto;}

  /* Stack table rows into label/value pairs instead of scrolling sideways —
     each <td> repeats its column header (via data-label) above its value. */
  table.plain.responsive-stack thead{display:none;}
  table.plain.responsive-stack, table.plain.responsive-stack tbody, table.plain.responsive-stack tr, table.plain.responsive-stack td{display:block;width:100%;}
  table.plain.responsive-stack tr{border-bottom:1px solid var(--border);padding:10px 0;}
  table.plain.responsive-stack tr:last-child{border-bottom:none;}
  table.plain.responsive-stack td{border-bottom:none;padding:5px 0;text-align:left !important;}
  table.plain.responsive-stack td::before{content:attr(data-label);display:block;font-size:10px;text-transform:uppercase;letter-spacing:.5px;color:var(--text-faint);margin-bottom:2px;}
}

/* generic */
.row{display:flex;gap:10px;flex-wrap:wrap;align-items:center;}
.divisao-row{justify-content:space-between;padding:6px 0;border-bottom:1px solid var(--border-soft);}
.divisao-row:last-child{border-bottom:none;}
.spacer{flex:1;}
/* Label wrapping a checkbox (or radio) so the whole row is clickable, not
   just the input itself — recurs across most modals with a yes/no field.
   Defined here (before the spacing/gap utilities below) specifically so
   combos like class="checkbox-row gap-6" resolve in the override's favor —
   same specificity either way, so plain source order decides the winner. */
.checkbox-row{display:flex;align-items:center;gap:8px;cursor:pointer;}

/* Small composable utilities for the most repeated inline-style patterns
   across app.js (margin:0 overrides, spacing, alignment). Not a full
   utility-class system — just the handful of exact strings that showed up
   dozens of times, so the same look reads the same way everywhere instead
   of being re-typed slightly differently per call site. Genuinely one-off
   styles (specific pixel widths, per-field tweaks) are left inline. */
.m0{margin:0 !important;}
.mt-0{margin-top:0 !important;}
.mt-2{margin-top:2px !important;}
.mt-4{margin-top:4px !important;}
.mt-6{margin-top:6px !important;}
.mt-8{margin-top:8px !important;}
.mt-10{margin-top:10px !important;}
.mt-12{margin-top:12px !important;}
.mt-14{margin-top:14px !important;}
.mt-16{margin-top:16px !important;}
.mt-20{margin-top:20px !important;}
.mt-24{margin-top:24px !important;}
.mt-26{margin-top:26px !important;}
.mb-0{margin-bottom:0 !important;}
.mb-2{margin-bottom:2px !important;}
.mb-3{margin-bottom:3px !important;}
.mb-4{margin-bottom:4px !important;}
.mb-6{margin-bottom:6px !important;}
.mb-8{margin-bottom:8px !important;}
.mb-10{margin-bottom:10px !important;}
.mb-12{margin-bottom:12px !important;}
.mb-14{margin-bottom:14px !important;}
.mb-18{margin-bottom:18px !important;}
.mb-20{margin-bottom:20px !important;}
.mb-26{margin-bottom:26px !important;}
.mr-8{margin-right:8px !important;}
.gap-6{gap:6px;}
.gap-8{gap:8px;}
.gap-12{gap:12px;}
.gap-16{gap:16px;}
.gap-18{gap:18px;}
.gap-20{gap:20px;}
.text-right{text-align:right;}
.fw-600{font-weight:600;}
.fw-700{font-weight:700;}

/* .row modifiers — .row itself is flex + gap:10px + wrap + align-items:center */
.row-nowrap{flex-wrap:nowrap;}
.row-between{justify-content:space-between;}
.row-end{justify-content:flex-end;}
.row-center{justify-content:center;}
.align-start{align-items:flex-start;}

/* text color/size — color:var(--text-dim)/(--text-faint) alone (no other
   properties) shows up dozens of times across app.js as its own inline
   style, same idea as the spacing utilities above. !important for the same
   reason as .hidden above: these get combined with component classes like
   .hint that set their own color/font-size and are defined later in this
   file, so plain source order would let the component win instead of the
   override — e.g. .hint.text-danger needs to actually render red. */
.text-base{color:var(--text) !important;}
.text-dim{color:var(--text-dim) !important;}
.text-faint{color:var(--text-faint) !important;}
.text-danger{color:var(--danger) !important;}
.text-accent{color:var(--accent) !important;}
.fs-9{font-size:9px !important;}
.fs-11{font-size:11px !important;}
.fs-12{font-size:12px !important;}
.fs-12-5{font-size:12.5px !important;}
.fs-18{font-size:18px !important;}
.line-through{text-decoration:line-through;}

/* !important — .hidden gets combined with all sorts of component classes
   (.row, .task-desc-toolbar, .field, etc.) that set their own `display`,
   and plenty of those are defined later in this file than this rule; without
   !important, source order alone would decide the fight and .hidden would
   silently lose on exactly the classes it needs to win against. */
.hidden{display:none !important;}
.min-w-0{min-width:0;}
.w-100{width:100%;}
.w-70{width:70px;}
.max-w-220{max-width:220px;}
.max-w-240{max-width:240px;}
.max-w-560{max-width:560px;}
.table-fixed{table-layout:fixed;}
.flex-shrink-0{flex-shrink:0;}
.cursor-pointer{cursor:pointer;}
.nowrap{white-space:nowrap;}
.pre-wrap{white-space:pre-wrap;}
.truncate{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.relative{position:relative;}
.flex-1{flex:1;}
.self-end{align-self:flex-end;}
.btn-icon-sm{width:24px;height:24px;}
.chip-remove-btn{background:none;border:none;color:var(--text-faint);cursor:pointer;font-size:12px;}
.link-accent{color:var(--accent);font-size:11px;}
.section-summary{cursor:pointer;font-family:var(--display);text-transform:uppercase;letter-spacing:.6px;font-size:12px;color:var(--text-dim);margin-bottom:10px;}
.btn{background:linear-gradient(180deg,var(--panel3),var(--panel2));border:1px solid var(--border);color:var(--text);padding:9px 14px;border-radius:var(--radius);font-size:12px;font-weight:500;display:inline-flex;align-items:center;gap:6px;height:32px;transition:background .15s ease,border-color .15s ease,transform .1s ease,box-shadow .15s ease;box-shadow:inset 0 1px 0 rgba(255,255,255,.05),0 1px 2px rgba(var(--shadow-rgb),.4),0 4px 10px rgba(var(--shadow-rgb),.22);}
.btn:hover{background:linear-gradient(180deg,#2c2c2c,var(--panel3));border-color:var(--text-faint);box-shadow:inset 0 1px 0 rgba(255,255,255,.07),0 2px 4px rgba(var(--shadow-rgb),.45),0 8px 18px rgba(var(--shadow-rgb),.3);}
.btn:active{transform:translateY(1px);}
.btn:disabled{opacity:.5;cursor:default;pointer-events:none;}
.btn-accent{background:linear-gradient(180deg,#f2333a,var(--accent));border-color:var(--accent);color:#fff;font-weight:600;box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 4px 14px rgba(var(--accent-rgb),.28);}
/* Darker on hover, not lighter — a lighter red against white text loses contrast. */
.btn-accent:hover{background:var(--accent-dim);border-color:var(--accent-dim);box-shadow:0 4px 14px rgba(var(--accent-rgb),.35);}

.quickadd-icon-wrap{position:relative;display:inline-flex;}
.quickadd-icon-wrap>.nav-icon{width:20px;height:20px;}
.quickadd-badge{position:absolute;right:-5px;bottom:-5px;width:15px;height:15px;border-radius:50%;background:var(--bg);color:var(--accent);display:flex;align-items:center;justify-content:center;}
.quickadd-badge .nav-icon{width:13px;height:13px;}
.btn-danger{color:var(--danger);}
.btn-danger:hover{background:var(--danger-bg);border-color:var(--danger);color:var(--danger);}
.btn-danger svg{padding:3px 1px 3px 2px;}
.btn-good{color:var(--good);}
.btn-good:hover{background:var(--good-bg);border-color:var(--good);color:var(--good);}
.btn-sm{padding:6px 10px;font-size:12px;}
.btn-xs{padding:2px 7px;font-size:10.5px;height:auto;}
.btn-icon{width:32px;height:32px;padding:0;justify-content:center;position:relative;overflow:hidden;user-select:none;-webkit-touch-callout:none;touch-action:manipulation;transition:transform .15s ease;}
.btn-icon .nav-icon{position:relative;z-index:1;}
.btn-icon .hold-fill{position:absolute;left:0;right:0;bottom:0;height:0%;background:var(--danger);z-index:0;pointer-events:none;}
/* Press-and-hold to delete: instant embiggen + a shake that ramps up in
   amplitude as the hold approaches completion — the escalating wobble reads
   as rising urgency/tension while the fill bar (driven separately in JS)
   fills bottom-up. Duration is set via JS (animationDuration) to always
   match armHoldDelete's FAST_MS + SLOW_MS, so the two never drift apart. */
.btn-icon.holding{border-color:var(--danger);z-index:5;animation-name:hold-shake;animation-timing-function:ease-out;}
@keyframes hold-shake{
  0%{transform:scale(1.18) translateX(0);}
  4%{transform:scale(1.18) translateX(-0.5px);}
  8%{transform:scale(1.18) translateX(0.5px);}
  12%{transform:scale(1.18) translateX(-0.5px);}
  16%{transform:scale(1.18) translateX(0.6px);}
  20%{transform:scale(1.18) translateX(-0.6px);}
  24%{transform:scale(1.18) translateX(0.8px);}
  28%{transform:scale(1.18) translateX(-0.8px);}
  32%{transform:scale(1.18) translateX(1px);}
  36%{transform:scale(1.18) translateX(-1px);}
  40%{transform:scale(1.18) translateX(1.3px);}
  44%{transform:scale(1.18) translateX(-1.3px);}
  48%{transform:scale(1.18) translateX(1.6px);}
  52%{transform:scale(1.18) translateX(-1.6px);}
  56%{transform:scale(1.18) translateX(2px);}
  60%{transform:scale(1.18) translateX(-2px);}
  64%{transform:scale(1.18) translateX(2.5px);}
  68%{transform:scale(1.18) translateX(-2.5px);}
  72%{transform:scale(1.18) translateX(3px);}
  76%{transform:scale(1.18) translateX(-3.2px);}
  80%{transform:scale(1.18) translateX(3.6px);}
  84%{transform:scale(1.18) translateX(-4px);}
  88%{transform:scale(1.18) translateX(4.5px);}
  92%{transform:scale(1.18) translateX(-5px);}
  96%{transform:scale(1.18) translateX(5.5px);}
  100%{transform:scale(1.18) translateX(0);}
}

/* Big centered hold-to-delete indicator — a duplicate of the small button's
   fill/shake, shown full-screen so a finger covering the actual button on
   mobile doesn't hide the feedback. Appears the instant a hold starts and
   vanishes the instant it ends (no fade either way — see armHoldDelete). */
.hold-delete-overlay{
  position:fixed;inset:0;z-index:200;
  background:rgba(10,10,10,.5);backdrop-filter:blur(5px);-webkit-backdrop-filter:blur(5px);
  display:flex;align-items:center;justify-content:center;
  opacity:0;pointer-events:none;
}
.hold-delete-overlay.open{opacity:1;}
.hold-delete-wrap{position:relative;width:120px;height:120px;}
/* Sits behind the circle and extends past its edges — the opaque circle
   face covers the middle, so this only ever shows as a soft aura bleeding
   out around the rim, rising in sync with the fill level (see armHoldDelete). */
.hold-delete-glow{
  position:absolute;left:50%;bottom:-14px;transform:translateX(-50%);
  width:150px;height:0%;
  background:var(--accent);
  filter:blur(26px);opacity:.8;
  z-index:0;pointer-events:none;
}
.hold-delete-circle{
  position:relative;z-index:1;width:120px;height:120px;border-radius:50%;
  background:var(--panel2);border:none;
  display:flex;align-items:center;justify-content:center;overflow:hidden;
}
.hold-delete-circle .hold-fill-big{position:absolute;left:0;right:0;bottom:0;height:0%;background:var(--accent);z-index:0;}
/* Wavy liquid surface — a repeating SVG wave tile at the top of the fill,
   scrolling horizontally forever so it reads as flowing rather than static. */
.hold-delete-circle .hold-fill-big::before{
  content:'';
  position:absolute;left:-10%;right:-10%;top:-9px;height:18px;
  background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 20'><path d='M0 10 Q 30 0 60 10 T 120 10 V20 H0 Z' fill='%23EC2027'/></svg>");
  background-repeat:repeat-x;background-size:120px 18px;
  animation:hold-liquid-wave 1.1s linear infinite;
}
.hold-delete-circle .hold-delete-icon{position:relative;z-index:1;color:#fff;}
.hold-delete-circle .hold-delete-icon .nav-icon{width:48px;height:48px;}
.hold-delete-circle.holding{animation-name:hold-shake;animation-timing-function:ease-out;}
@keyframes hold-liquid-wave{
  from{background-position-x:0;}
  to{background-position-x:-120px;}
}

.delete-burst{position:fixed;left:0;top:0;width:0;height:0;z-index:210;pointer-events:none;}
.delete-burst-particle{position:absolute;left:0;top:0;width:12px;height:12px;margin:-6px 0 0 -6px;border-radius:50%;background:var(--accent);animation:delete-burst-fly 1.1s cubic-bezier(.2,.8,.3,1) forwards;}
@keyframes delete-burst-fly{
  0%{transform:translate(0,0) scale(1);opacity:1;}
  100%{transform:translate(var(--dx),var(--dy)) scale(0);opacity:0;}
}

input[type=text],input[type=number],input[type=date],input[type=time],input[type=url],input[type=email],input[type=password],select,textarea{
  background:var(--bg);border:1px solid var(--border);color:var(--text);padding:9px 11px;border-radius:var(--radius);font-size:12px;width:100%;height:36px;
  box-shadow:inset 0 1px 3px rgba(var(--shadow-rgb),.4);
  transition:border-color .15s ease,box-shadow .15s ease;
  color-scheme:dark; /* tells the browser to render the native date/time picker popups + icon in a dark palette */
}
/* Textareas need their own vertical size (rows attribute), not the shared
   fixed control height above, and default to resize:both — which lets a
   drag widen one past the modal and break the layout, so only vertical
   resize is allowed. */
textarea{height:auto;resize:vertical;}
input[type=date]::-webkit-calendar-picker-indicator,input[type=time]::-webkit-calendar-picker-indicator{
  filter:invert(.7);cursor:pointer;opacity:.8;
}
input[type=date]::-webkit-calendar-picker-indicator:hover,input[type=time]::-webkit-calendar-picker-indicator:hover{opacity:1;}
/* The browser owns type=color's popup swatch UI — this just makes the
   closed-state control (the only part we can restyle) match the rest of
   the form instead of looking like a stray OS widget dropped into it. */
input[type=color]{
  background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);
  width:56px;height:36px;padding:4px;cursor:pointer;
}
input[type=color]::-webkit-color-swatch-wrapper{padding:0;}
input[type=color]::-webkit-color-swatch{border:none;border-radius:calc(var(--radius) - 2px);}
input[type=color]::-moz-color-swatch{border:none;border-radius:calc(var(--radius) - 2px);}
input:focus,select:focus,textarea:focus{outline:none;border-color:var(--accent-dim);box-shadow:inset 0 1px 3px rgba(var(--shadow-rgb),.4),0 0 0 3px rgba(var(--accent-rgb),.16);}
/* Browser autofill (saved usernames/emails/passwords) paints its own bright
   yellow background over the input by default — Chrome/Edge/Safari won't
   take a plain background-color override, only this exact `-webkit-box-
   shadow: 0 0 0 1000px <color> inset` trick (an opaque inset shadow big
   enough to cover the whole field, painted above the browser's own fill);
   the long transition delay is the standard way to stop that yellow from
   even flashing before the override applies. Firefox honors a normal
   background-color on :autofill, so it gets a separate, simpler rule. */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus{
  -webkit-text-fill-color:var(--text);
  -webkit-box-shadow:0 0 0 1000px var(--bg) inset;
  box-shadow:0 0 0 1000px var(--bg) inset;
  caret-color:var(--text);
  transition:background-color 5000s ease-in-out 0s;
}
input:autofill{background-color:var(--bg);color:var(--text);}
label{font-size:10px;text-transform:uppercase;letter-spacing:.6px;color:var(--text-faint);font-weight:600;display:block;margin:1px 0 6px;}
.field{margin-bottom:12px;}
.card-body > .field:last-child{margin-bottom:0;}
.field-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;}
@media(max-width:560px){.field-grid{grid-template-columns:1fr;}}
.field-grid-3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px;}
@media(max-width:680px){.field-grid-3{grid-template-columns:1fr;}}

/* dashboard */
.stat-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px;margin-bottom:26px;}
.stat-card{padding:16px;} /* base recipe shared with .card/.person-card — see that rule */
.stat-card .num{font-family:var(--display-strong);font-size:28px;font-weight:800;color:var(--accent);}
.stat-card .lbl{font-size:11px;color:var(--text-faint);text-transform:uppercase;letter-spacing:.6px;margin-top:2px;}
.section-title{font-family:var(--display);font-size:14px;text-transform:uppercase;letter-spacing:1px;color:var(--text-dim);margin:26px 0 12px;}

.event-card-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:12px;margin-bottom:26px;}
.event-card .card-body{padding:12px;}
.event-date-badge{background:var(--panel2);border:1px solid var(--border-soft);border-radius:8px;padding:6px 4px;text-align:center;min-width:50px;flex-shrink:0;}
.event-date-month{font-family:var(--display);font-size:10px;font-weight:600;letter-spacing:1px;color:var(--accent);text-transform:uppercase;}
.event-date-day{font-family:var(--display);font-size:18px;font-weight:600;color:var(--text);line-height:1.15;}
/* Compromissos pessoais get the same yellow as .tag-warn instead of the
   default accent red, so the dashboard reads them apart from work events
   at a glance without needing to open the card. */
.event-date-badge-personal .event-date-month{color:var(--warn);}
/* Pagamento cards mute the badge by default and only flip it red once overdue. */
.event-date-badge-pagamento .event-date-month{color:var(--text-faint);}
.event-date-badge-pagamento.overdue{border-color:var(--danger);}
.event-date-badge-pagamento.overdue .event-date-month,
.event-date-badge-pagamento.overdue .event-date-day{color:var(--danger);}

/* item grid */
.item-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:14px;}
/* drive */
.drive-breadcrumbs{display:flex;flex-wrap:wrap;align-items:center;gap:4px;margin-bottom:14px;font-size:12.5px;}
.drive-crumb{background:none;border:none;color:var(--text-dim);padding:2px 4px;cursor:pointer;border-radius:4px;}
.drive-crumb:hover{color:var(--text);background:var(--panel2);}
.drive-crumb:last-child{color:var(--text);font-weight:600;}
.drive-crumb-sep{color:var(--text-faint);}
.drive-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:10px;}
.drive-file-card{cursor:pointer;}
/* Optimistic-update rollback — see the drive-rename handler in app.js,
   which reverts the displayed value and adds this class briefly whenever
   the API call it already applied locally turns out to have failed. */
.shake{animation:shakeX .4s ease;}
@keyframes shakeX{
  0%,100%{transform:translateX(0);}
  20%{transform:translateX(-6px);}
  40%{transform:translateX(5px);}
  60%{transform:translateX(-3px);}
  80%{transform:translateX(2px);}
}
.drive-file-card .card-body{padding:12px 14px;}
.drive-file-icon{flex-shrink:0;width:32px;height:32px;display:flex;align-items:center;justify-content:center;color:var(--text-faint);}
.drive-file-icon .nav-icon{width:20px;height:20px;}
.drive-file-thumb-wrap{width:100%;aspect-ratio:16/9;background:var(--panel2);overflow:hidden;}
.drive-file-thumb{width:100%;height:100%;object-fit:cover;display:block;}
.drive-file-actions .btn-icon{width:26px;height:26px;}
.drive-move-list{display:flex;flex-direction:column;gap:2px;max-height:280px;overflow-y:auto;margin-bottom:4px;}
.drive-move-folder-row{display:flex;align-items:center;gap:8px;text-align:left;background:none;border:none;color:var(--text);padding:8px 10px;border-radius:6px;cursor:pointer;font-size:13.5px;}
.drive-move-folder-row:hover{background:var(--panel2);}
.drive-move-folder-row .nav-icon{width:18px;height:18px;color:var(--text-faint);flex-shrink:0;}

.drive-action-menu{position:absolute;z-index:60;background:rgba(var(--panel-rgb),.88);backdrop-filter:blur(20px) saturate(150%);-webkit-backdrop-filter:blur(20px) saturate(150%);border:1px solid var(--border-soft);border-radius:10px;box-shadow:0 8px 24px rgba(var(--shadow-rgb),.35);padding:4px;display:flex;flex-direction:column;min-width:200px;}
.drive-action-menu-item{display:flex;align-items:center;gap:10px;background:none;border:none;color:var(--text);padding:8px 10px;border-radius:6px;cursor:pointer;font-size:12px;text-align:left;width:100%;}
.drive-action-menu-item:hover{background:var(--panel2);}
.drive-action-menu-item svg{flex-shrink:0;}
.drive-action-menu-danger{color:var(--danger);}
.drive-action-menu-danger:hover{background:var(--danger-bg);}

.quick-actions-dropdown{position:relative;display:inline-block;}
/* display:none, not just visibility:hidden — an absolutely-positioned
   dropdown anchored to a column's right edge still counts toward document
   overflow when hidden that way, so a whole page's worth of closed task-
   column menus stretched the body ~140px wider than the viewport on
   mobile. That inflated .overlay (inset:0 → body width) and every modal
   it contains along with it. */
.quick-actions-menu{position:absolute;top:100%;left:0;margin-top:6px;background:rgba(var(--panel-rgb),.88);backdrop-filter:blur(20px) saturate(150%);-webkit-backdrop-filter:blur(20px) saturate(150%);border:1px solid var(--border-soft);border-radius:10px;box-shadow:0 8px 24px rgba(var(--shadow-rgb),.35);padding:4px;flex-direction:column;min-width:230px;z-index:60;opacity:0;display:none;transform:translateY(-4px);transition:opacity .15s ease,transform .15s ease;}
.quick-actions-menu.open{opacity:1;display:flex;transform:translateY(0);}

#ptr-indicator{position:fixed;top:0;left:50%;transform:translate(-50%,-60px);width:36px;height:36px;display:flex;align-items:center;justify-content:center;z-index:200;opacity:0;pointer-events:none;}
.ptr-spinner{width:22px;height:22px;border-radius:50%;border:2.5px solid var(--border-soft);border-top-color:var(--accent);transition:transform .15s ease;}
#ptr-indicator.ptr-ready .ptr-spinner{transform:scale(1.15);}
#ptr-indicator.ptr-loading .ptr-spinner{animation:ptrSpin .6s linear infinite;}
@keyframes ptrSpin{to{transform:rotate(360deg);}}

#upload-bar{position:fixed;left:50%;bottom:20px;transform:translate(-50%,12px);z-index:210;
  display:flex;align-items:center;gap:10px;background:rgba(var(--panel-rgb),.85);backdrop-filter:blur(20px) saturate(150%);-webkit-backdrop-filter:blur(20px) saturate(150%);border:1px solid var(--border-soft);
  border-radius:20px;padding:9px 16px 9px 12px;box-shadow:0 8px 24px rgba(var(--shadow-rgb),.4);
  opacity:0;pointer-events:none;transition:opacity .2s ease,transform .2s ease;}
#upload-bar.show{opacity:1;transform:translate(-50%,0);pointer-events:auto;}
.upload-bar-spinner{width:16px;height:16px;border-radius:50%;flex-shrink:0;
  border:2px solid var(--border-soft);border-top-color:var(--accent);animation:ptrSpin .7s linear infinite;}
.upload-bar-text{font-size:12.5px;font-weight:500;color:var(--text);white-space:nowrap;}
@media(max-width:820px){ #upload-bar{bottom:calc(20px + env(safe-area-inset-bottom, 0px));} }

.dpp-tree{max-height:340px;overflow-y:auto;margin-bottom:4px;}
.dpp-node-row{display:flex;align-items:center;gap:8px;padding:5px 4px;}
.dpp-node-icon{flex-shrink:0;width:18px;height:18px;display:flex;align-items:center;justify-content:center;color:var(--text-faint);}
.dpp-node-icon .nav-icon{width:16px;height:16px;}
.dpp-node-name{font-size:13.5px;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.dpp-node-name-input{flex:1;min-width:0;}
.dpp-node-row .btn-icon{width:24px;height:24px;flex-shrink:0;}
.dpp-add-btn{display:inline-flex;align-items:center;gap:6px;background:none;border:1px dashed var(--border-soft);color:var(--text-dim);padding:5px 12px;border-radius:6px;cursor:pointer;font-size:12.5px;margin:2px 0 6px;}
.dpp-add-btn:hover{color:var(--text);border-color:var(--text-dim);}
.dpp-add-btn .nav-icon{width:14px;height:14px;}
.dpp-add-root-btn{border-style:solid;background:var(--panel2);margin-left:0!important;margin-top:4px;}
.drive-preview-modal{max-width:min(900px,92vw);}
.drive-preview-media{max-width:100%;max-height:70vh;display:block;margin:0 auto;border-radius:8px;}
.budget-relatorio-textarea{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;width:100%;resize:vertical;}
.settings-help-text{font-size:12px;color:var(--text-dim);line-height:1.6;}

/* Configurações: groups related sections under one collapsible <details>,
   so the page reads as a handful of topics instead of a dozen flat headers
   in a row. Plain <details>/<summary> — no JS needed for open/closed state,
   and every existing #id inside still resolves exactly the same regardless
   of this extra wrapper, so none of the bind logic needed to change. */
.settings-group{border:1px solid var(--border-soft);border-radius:10px;background:var(--panel2);margin:22px 0;overflow:hidden;}
.settings-group-summary{list-style:none;cursor:pointer;display:flex;align-items:center;gap:10px;
  padding:16px 18px;font-family:var(--display);font-size:15px;text-transform:uppercase;
  letter-spacing:.8px;color:var(--text);user-select:none;}
.settings-group-summary::-webkit-details-marker{display:none;}
.settings-group-summary::before{content:"›";display:inline-block;flex-shrink:0;font-size:19px;
  line-height:1;color:var(--text-faint);transition:transform .18s ease;}
.settings-group[open] .settings-group-summary::before{transform:rotate(90deg);}
.settings-group-summary .settings-group-hint{margin-left:auto;text-transform:none;letter-spacing:normal;
  font-family:var(--sans);font-size:11.5px;font-weight:400;color:var(--text-faint);}
.settings-group-body{padding:2px 18px 22px;}
.settings-group-body .section-title{font-size:12px;margin:22px 0 10px;}
.settings-group-body .section-title.mt-0,.settings-group-body .section-title:first-child{margin-top:0;}

.changelog-version+.changelog-version{margin-top:20px;padding-top:18px;border-top:1px solid var(--border-soft);}
.changelog-version-header{display:flex;align-items:baseline;gap:10px;margin-bottom:8px;}
.changelog-version-header b{font-family:var(--display);font-size:13px;letter-spacing:.4px;color:var(--text);}
.changelog-list{margin:0;padding-left:20px;display:flex;flex-direction:column;gap:8px;}
.changelog-list li{font-size:12.5px;line-height:1.6;color:var(--text-dim);}
.changelog-pager{display:flex;align-items:center;justify-content:center;gap:10px;margin-top:18px;padding-top:16px;border-top:1px solid var(--border-soft);flex-wrap:wrap;}
.changelog-pager .btn:disabled{opacity:.4;cursor:default;}
.settings-fine-print{font-size:11.5px;color:var(--text-faint);line-height:1.5;}
.divider-top{border-top:1px solid var(--border-soft);padding-top:10px;}
.stat-grid-2col{grid-template-columns:1fr 1fr;}
.divider-bottom{border-bottom:1px solid var(--border-soft);padding-bottom:10px;}
.ph-error{font-size:9px;padding:4px;text-align:center;line-height:1.3;}
.hint-empty{padding:8px 2px;}
.drive-preview-frame{width:100%;height:70vh;border:none;border-radius:8px;}
/* Checklist cards are light on content (name + one meta line + actions) —
   sized so 3 fit per row at a normal content width, fewer on narrower ones. */
.checklist-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:14px;}
.pagamento-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(360px,100%),1fr));gap:14px;}
.prospect-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(320px,100%),1fr));gap:14px;}
.prospect-card{cursor:pointer;}
.prospect-card:hover{border-color:var(--text-faint);}
.prospect-footer{display:flex;justify-content:space-between;align-items:center;margin-top:12px;padding-top:10px;border-top:1px solid var(--border-soft);cursor:default;}
.pagamento-card{cursor:pointer;}
.pagamento-card:hover{border-color:var(--text-faint);}
.pagamento-card .card-actions{cursor:default;}

/* Neutral chip + a single colored accent (an icon, or — when a tag carries
   no icon — a small dot) instead of a different saturated background/text
   color per status. Reads calmer at a glance across a page full of tags
   (a task board, a payment list) since the eye only has one color signal
   to parse per tag instead of a whole background+text hue shift; --tag-
   accent is what the variant classes below actually change. */
.tag{display:inline-flex;align-items:center;gap:6px;font-size:11px;font-weight:600;padding:4px 9px;border-radius:6px;line-height:1.2;background:rgba(var(--panel2-rgb),.85);border:1px solid var(--border-soft);color:var(--text-dim);--tag-accent:var(--text-faint);}
.tag svg{width:12px;height:12px;flex-shrink:0;display:block;color:var(--tag-accent);}
.tag:not(:has(svg))::before{content:'';width:6px;height:6px;border-radius:50%;background:var(--tag-accent);flex-shrink:0;}
/* Status color pairs — shared with .badge below (same semantic colors,
   different shape: .tag is a 6px-radius rect, .badge a 20px pill), so a
   status's accent color is defined once instead of twice under two naming
   schemes. */
.tag-good, .badge.available{--tag-accent:var(--good);border-color:rgba(var(--good-rgb),.3);}
.tag-bad, .badge.atrasado{--tag-accent:var(--danger);border-color:rgba(var(--danger-rgb),.3);}
.tag-warn, .badge.maintenance{--tag-accent:var(--warn);border-color:rgba(var(--warn-rgb),.3);}
.tag-info{--tag-accent:var(--info);border-color:rgba(var(--info-rgb),.3);}
.tag-gray{--tag-accent:var(--text-faint);}
/* Fixed-width columns, Trello-style — a board with more columns than fit
   the viewport scrolls horizontally instead of squeezing every column
   narrower. Click-and-drag panning on the board background is wired up in
   bindTaskBoardDragScroll (app.js); overflow-x:auto alone already covers
   trackpad/wheel/touch scrolling natively. Below the mobile breakpoint
   columns stack vertically at full width instead — there's no room to pan
   sideways on a phone screen. */
.task-board{display:flex;gap:16px;align-items:start;overflow-x:auto;padding-bottom:6px;cursor:grab;}
.task-board.task-board-dragging{cursor:grabbing;user-select:none;-webkit-user-select:none;}
.task-board.task-board-dragging .task-card{pointer-events:none;}
.task-column{flex:0 0 268px;width:268px;}
.task-column-title{font-family:var(--display);font-size:12px;text-transform:uppercase;letter-spacing:.6px;color:var(--text-dim);margin-bottom:10px;min-height:34px;}
.task-column-actions{gap:2px;}
/* Painel's "high priority / recent" board is a flat list of cards (no
   .task-column wrapper), unlike the Tarefas board above. Grid, not flex —
   flex-wrap's "each row distributes its own leftover space" behavior meant
   a trailing row with fewer cards than a full row (e.g. 2 left over after
   4 fit the row above) stretched those into filling the whole row, so they
   visually spanned ~2 card widths instead of lining up under the columns
   above them. auto-fill (not auto-fit) keeps the same column tracks on
   every row regardless of how many cards land in the last one — a short
   trailing row just leaves the remaining tracks empty instead of stretching
   into them. */
.dash-task-board{display:grid;grid-template-columns:repeat(auto-fill, minmax(260px, 1fr));gap:16px;}
.dash-task-board .task-card{margin-bottom:0;min-width:0;}
.task-column-move{background:none;border:none;color:var(--text-faint);opacity:.5;width:22px;height:22px;}
.task-column-move .nav-icon{width:14px;height:14px;}
.task-column-move:hover{opacity:1;color:var(--text);background:none;}
.task-column-move:disabled{opacity:.18;pointer-events:none;}
@media(max-width:820px){
  .task-board{flex-direction:column;overflow-x:visible;cursor:auto;}
  .task-column{width:100%;flex:1 1 auto;}
}
/* none, not pan-y — pan-y sounds like the fix (let native vertical scroll
   through), but on mobile the board is a single column, so dragging a card
   to a different status is itself mostly a vertical gesture. With pan-y the
   browser can claim that motion for native scroll before the hold timer
   ever gets a say, and once it does, preventDefault() on pointermove stops
   working for the rest of that gesture — drag silently breaks. touch-action
   also can't be switched mid-gesture (browsers lock it in at touchstart),
   so JS owns every touch on a card unconditionally, and drives scrolling
   itself once the hold-cancel decides "this is a scroll" — see
   taskCardPointerMove in app.js. */
.task-card{margin-bottom:18px;cursor:pointer;touch-action:none;user-select:none;-webkit-user-select:none;}
.task-card .card-body{padding:12px 14px;}
.task-card:hover{border-color:var(--text-faint);}
.task-complete-check{display:inline-flex;width:22px;height:22px;color:var(--good);filter:drop-shadow(0 0 5px rgba(var(--good-rgb),.85));}
.task-complete-check svg{width:100%;height:100%;display:block;}
.col-menu{min-width:200px;}
.task-meta-badge{display:inline-flex;align-items:center;gap:4px;}
.task-meta-icon{width:14px;height:14px;flex-shrink:0;}

.task-audio-list{display:flex;flex-direction:column;gap:10px;}
.task-audio-row{background:var(--panel);border:1px solid var(--border-soft);border-radius:var(--radius);padding:8px 10px;}
.task-audio-row-top{display:flex;align-items:center;gap:8px;margin-bottom:6px;flex-wrap:wrap;}
.task-audio-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12.5px;color:var(--text-dim);}
.task-audio-speed{display:flex;gap:2px;flex-shrink:0;background:var(--panel2);border-radius:6px;padding:2px;}
.task-audio-speed-btn{border:none;background:transparent;color:var(--text-faint);font-size:10.5px;font-family:var(--mono);padding:3px 7px;border-radius:4px;cursor:pointer;}
.task-audio-speed-btn:hover{color:var(--text-dim);}
.task-audio-speed-btn.active{background:var(--accent);color:#fff;}
.task-audio-row audio{width:100%;height:32px;display:block;}

.task-attach-list{display:flex;flex-direction:column;gap:6px;}
.task-attach-row{display:flex;align-items:center;justify-content:space-between;gap:10px;background:var(--panel);border:1px solid var(--border-soft);border-radius:var(--radius);padding:8px 10px;}
.task-attach-info{display:flex;align-items:baseline;gap:8px;min-width:0;flex:1;}
.task-attach-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12.5px;color:var(--text-dim);}
.task-attach-size{flex-shrink:0;font-size:10.5px;font-family:var(--mono);color:var(--text-faint);}

/* Collapsible modal sections (Checklist/Áudios/Anexos) — plain <details>,
   styled to match the rest of the modal's label convention instead of the
   browser default marker/summary look. */
.task-section > summary{list-style:none;cursor:pointer;font-size:10px;text-transform:uppercase;letter-spacing:.6px;color:var(--text-faint);font-weight:600;display:flex;align-items:center;justify-content:space-between;}
.task-section > summary::-webkit-details-marker{display:none;}
/* Two separate icons (chevron-right/chevron-down) rather than one rotated
   glyph — CSS just shows whichever matches the <details> element's current
   open state, flipping instantly with the native toggle, no JS involved. */
.task-section-chevron{display:inline-flex;align-items:center;justify-content:center;width:12px;height:12px;flex-shrink:0;color:var(--text-faint);}
.task-section-chevron .nav-icon{width:12px;height:12px;}
.task-section-chevron.chevron-open{display:none;}
.task-section[open] .task-section-chevron.chevron-open{display:inline-flex;}
.task-section[open] .task-section-chevron.chevron-closed{display:none;}
.task-section-body{margin-top:10px;}
.task-section-body:last-of-type{margin-bottom:4px;}
/* Original card while its floating ghost (below) is being dragged. */
.task-card.drag-source{opacity:.35;transform:scale(.97);transition:opacity .15s ease,transform .15s ease;}
.task-column{border-radius:10px;}
.task-column.drag-over{position:relative;}
.task-column.drag-over::after{
  position:absolute;
  display:block;
  content:"";
  top:-8px;
  left:-10px;
  width:100%;
  background:#ffffff17;
  height:100%;
  padding:5px 10px;
  border-radius:18px;
}

/* Custom pointer-driven drag (not native HTML5 DnD, for real visual control):
   a floating clone follows the cursor with a light spring lag, tilting in
   response to how fast it's catching up (see the rAF loop in JS) — the
   "physics" feel. transform is fully JS-owned every frame here (rotate +
   scale together) so a CSS transition on it would fight the per-frame
   writes; only box-shadow/opacity, which just toggle once per class
   change, are safe to transition. */
.task-drag-ghost{
  /* !important because this element also carries .card (for the visual
     chrome — background/border/radius) and .card{position:relative} +
     .card{transition:...} are declared later in this file: same
     specificity, later source order wins, which was silently turning
     this back into position:relative (rendered inert at the bottom of
     <body> instead of floating at the cursor — the ghost never appeared). */
  position:fixed !important;z-index:300;pointer-events:none;margin:0;user-select:none;
  box-shadow:0 2px 6px rgba(var(--shadow-rgb),.3);
  transition:box-shadow .15s ease,opacity .15s ease !important;
}
.task-drag-ghost.lifted{
  box-shadow:0 24px 48px rgba(var(--shadow-rgb),.55),0 4px 14px rgba(var(--shadow-rgb),.35);
}
/* left/top/transform keep animating via the JS spring loop for the full
   drop — this only fades the ghost out near the end of that motion (delay)
   so the snap-into-place is visible before it disappears, rather than
   fading immediately at the drop point. */
.task-drag-ghost.dropping{opacity:0;transition:opacity .15s ease .08s,box-shadow .15s ease !important;}

.task-checklist{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:5px;}
.task-checklist li{display:flex;align-items:center;gap:8px;background:var(--panel2);border:1px solid var(--border-soft);border-radius:8px;padding:6px 8px;}
.task-checklist li.dragging{opacity:.4;}
.task-checklist li.drag-over{border-color:var(--accent);}
.task-checklist .drag-handle{cursor:grab;color:var(--text-faint);flex-shrink:0;user-select:none;font-size:14px;line-height:1;}
.task-checklist .check-toggle{width:auto;flex-shrink:0;}
.task-checklist .check-text{flex:1;font-size:12px;word-break:break-word;cursor:text;}
.task-checklist .check-text.done{color:var(--text-faint);text-decoration:line-through;}
.task-checklist .check-text-input{flex:1;font-size:12px;font-family:inherit;background:var(--panel);border:1px solid var(--border-soft);border-radius:5px;padding:2px 6px;color:var(--text);}
.task-checklist .check-remove{background:none;border:none;color:var(--text-faint);cursor:pointer;flex-shrink:0;padding:2px 6px;font-size:16px;line-height:1;}
.task-checklist .check-remove:hover{color:var(--danger);}
/* Shared "card" recipe — .card, .stat-card (dashboard/inventory) and
   .person-card (Equipe) all wrote out this same background/border/radius/
   transition separately; one rule per shared or differing property now,
   instead of three copies to keep in sync by hand. */
/* Glass surface — translucent panel color + backdrop-filter so the ambient
   blob glow on body (see html,body rule up top) bleeds through faintly at
   the edges instead of every card reading as a flat opaque box. Paired with
   an inset top highlight (light "catching" the card's top edge) and a soft
   two-layer shadow (tight contact + wide ambient) for the raised-glass look
   instead of the single flat drop-shadow this used to have. */
.card, .stat-card, .person-card{
  background:rgba(var(--panel-rgb),.72);
  backdrop-filter:blur(18px) saturate(150%);
  -webkit-backdrop-filter:blur(18px) saturate(150%);
  border:1px solid var(--border-soft);
  border-radius:var(--radius-lg);
  transition:border-color .2s ease,transform .2s ease,box-shadow .2s ease,background .2s ease;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.045),0 1px 2px rgba(var(--shadow-rgb),.35),0 10px 26px rgba(var(--shadow-rgb),.32);
}
.card, .person-card{overflow:hidden;}
.card{position:relative;}
.card.selectable, .person-card.selectable{cursor:pointer;}
.card.selectable:hover, .stat-card.selectable:hover, .person-card.selectable:hover{border-color:var(--text-faint);transform:translateY(-3px);box-shadow:0 14px 28px rgba(var(--shadow-rgb),.35);}
.card-selected{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent) inset;}
.select-check{position:absolute;top:8px;left:8px;z-index:2;width:22px;height:22px;border-radius:50%;background:var(--accent);color:#fff;font-size:12px;font-weight:700;display:flex;align-items:center;justify-content:center;}
/* staggered grid entrance — see itemCard()'s idx param in app.js, which sets
   animation-delay per card so they cascade in rather than all popping at once. */
.card-enter{animation:cardEnter .55s cubic-bezier(.2,.8,.2,1) both;}
@keyframes cardEnter{from{opacity:0;transform:translateY(10px);}to{opacity:1;transform:translateY(0);}}

/* skeleton loaders — see skeletonListHtml()/skeletonGridHtml() in app.js */
.skeleton{background:linear-gradient(90deg,var(--panel2) 25%,var(--panel3) 37%,var(--panel2) 63%);background-size:400% 100%;animation:skeletonShimmer 1.4s ease infinite;border-radius:6px;}
.skeleton-line{height:12px;}
.skeleton-thumb{width:100%;height:130px;border-radius:0;}
@keyframes skeletonShimmer{0%{background-position:100% 0;}100%{background-position:0 0;}}

.card-thumb{width:100%;height:130px;background:var(--panel2);display:flex;align-items:center;justify-content:center;overflow:hidden;position:relative;}
.card-thumb img{width:100%;height:100%;object-fit:cover;}
.card-thumb .ph{color:var(--text-faint);font-size:11px;font-family:var(--mono);}
.stamp{position:absolute;top:8px;right:-6px;font-family:var(--display);font-size:10px;font-weight:700;letter-spacing:1px;text-transform:uppercase;padding:3px 10px;transform:rotate(6deg);border:1.5px solid;border-radius:3px;}
.stamp.maintenance{color:var(--warn);border-color:var(--warn);background:rgba(242,169,59,.08);}
.stamp.borrowed{color:var(--accent);border-color:var(--accent);background:rgba(var(--accent-rgb),.08);}
.card-body{padding:14px 14px 16px;}
.card-tag{font-family:var(--mono);font-size:10px;color:var(--text-faint);letter-spacing:.5px;}
.card-name{font-weight:600;font-size:14px;margin:3px 0 14px;line-height:1.25;}
.card-meta{font-size:11.5px;color:var(--text-dim);line-height:1.7;}
.card-meta b{color:var(--text);font-weight:500;}
.card-actions{display:flex;gap:6px;margin-top:16px;padding-top:12px;border-top:1px solid var(--border-soft);}
.card-reorder{display:flex;gap:2px;margin-right:auto;}
.card-reorder .btn-icon{width:26px;height:26px;}
.card-reorder .btn-icon:disabled{opacity:.3;cursor:default;}

/* empty */
.empty{border:1px dashed var(--border);border-radius:10px;padding:40px 20px;text-align:center;color:var(--text-faint);font-size:12px;animation:emptyIn .3s ease both;}
@keyframes emptyIn{from{opacity:0;transform:translateY(4px);}to{opacity:1;transform:translateY(0);}}
.empty b{color:var(--text-dim);display:block;font-family:var(--display);font-size:15px;text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px;}
/* Same "don't replay on a same-tab re-render" reasoning as .card-enter —
   see suppressCardEntranceAnim in app.js, which toggles this class on
   #content itself instead of touching .empty's many call sites. */
#content.no-entrance-anim .empty{animation:none;}

/* toasts — replaces alert() everywhere; see toast() in app.js */
.toast-stack{position:fixed;top:18px;right:18px;z-index:400;display:flex;flex-direction:column;gap:8px;max-width:min(360px,calc(100vw - 36px));pointer-events:none;}
.toast{pointer-events:auto;display:flex;align-items:flex-start;gap:8px;background:rgba(var(--panel2-rgb),.88);backdrop-filter:blur(20px) saturate(150%);-webkit-backdrop-filter:blur(20px) saturate(150%);border:1px solid var(--border);border-left:3px solid var(--danger);border-radius:8px;padding:11px 10px 11px 13px;box-shadow:0 10px 28px rgba(var(--shadow-rgb),.4);animation:toastIn .22s cubic-bezier(.2,.8,.2,1);}
.toast-msg{flex:1;font-size:12.5px;line-height:1.5;color:var(--text);}
.toast-close{background:none;border:none;color:var(--text-faint);font-size:16px;line-height:1;padding:2px 4px;flex-shrink:0;}
.toast-close:hover{color:var(--text);}
.toast.closing{animation:toastOut .18s ease forwards;}
@keyframes toastIn{from{opacity:0;transform:translateX(24px);}to{opacity:1;transform:translateX(0);}}
@keyframes toastOut{to{opacity:0;transform:translateX(24px) scale(.96);}}
@media (max-width:640px){
  .toast-stack{top:auto;bottom:18px;right:12px;left:12px;max-width:none;}
  @keyframes toastIn{from{opacity:0;transform:translateY(16px);}to{opacity:1;transform:translateY(0);}}
  @keyframes toastOut{to{opacity:0;transform:translateY(16px) scale(.96);}}
}

/* update-available banner — see the polling script in index.html/login.html.
   A persistent top bar rather than a dismissible toast on purpose: it
   should stay until acted on, not vanish after a few seconds and let a
   stale build go unnoticed (see z-index — above the toast stack, since
   this outranks any of those). */
#update-banner{position:fixed;top:0;left:0;right:0;z-index:500;display:flex;align-items:center;justify-content:center;gap:14px;flex-wrap:wrap;padding:10px 16px;background:var(--accent);color:#fff;font-size:12.5px;font-weight:500;box-shadow:0 2px 12px rgba(var(--shadow-rgb),.35);animation:updateBannerIn .3s cubic-bezier(.2,.8,.2,1);}
#update-banner.hidden{display:none;}
#update-banner a{color:#fff;text-decoration:underline;text-underline-offset:2px;opacity:.9;flex-shrink:0;}
#update-banner a:hover{opacity:1;}
#update-banner button{background:#fff;color:var(--accent-dim);border:none;border-radius:var(--radius);padding:6px 14px;font-size:12px;font-weight:700;flex-shrink:0;transition:opacity .15s ease;}
#update-banner button:hover{opacity:.85;}
#update-banner button:disabled{opacity:.6;cursor:default;}
@keyframes updateBannerIn{from{opacity:0;transform:translateY(-100%);}to{opacity:1;transform:translateY(0);}}
/* Both the desktop sidebar (sticky, top:0) and the mobile hamburger header
   (fixed, top:0) would otherwise sit directly under the banner at the exact
   same position — body.has-update-banner (set alongside showing the
   banner — see index.html/login.html) pushes both down out of its way
   instead of letting it cover them. 40px matches the banner's own height;
   the extra 14px on .scrolled matches its own top offset in that state. */
body.has-update-banner{padding-top:40px;}
body.has-update-banner .mobile-header{top:40px;}
body.has-update-banner .mobile-header.scrolled{top:54px;}

/* modal */
.overlay{position:fixed;inset:0;background:rgba(6,6,6,.66);display:flex;align-items:center;justify-content:center;z-index:100;padding:16px;animation:overlayIn .18s ease;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);}
.modal{background:rgba(var(--panel-rgb),.86);backdrop-filter:blur(28px) saturate(150%);-webkit-backdrop-filter:blur(28px) saturate(150%);border:1px solid var(--border);border-radius:var(--radius-lg);max-width:520px;width:100%;max-height:88vh;overflow-y:auto;padding:22px;box-shadow:inset 0 1px 0 rgba(255,255,255,.05),0 24px 60px rgba(var(--shadow-rgb),.5);animation:modalIn .22s cubic-bezier(.2,.8,.2,1);}
/* Closing: reverses the entrance instead of the instant app.innerHTML wipe
   render() would otherwise cause — see closeModal() in app.js, which adds
   this class and waits out the transition before actually nulling `modal`. */
.overlay.closing{animation:overlayOut .15s ease forwards;}
.overlay.closing .modal{animation:modalOut .15s cubic-bezier(.4,0,1,1) forwards;}
@keyframes overlayOut{to{opacity:0;}}
@keyframes modalOut{to{opacity:0;transform:translateY(6px) scale(.98);}}
.modal-lg{max-width:720px;}
.import-diff-list{display:flex;flex-direction:column;gap:6px;max-height:240px;overflow-y:auto;}
.import-diff-row{font-size:12.5px;line-height:1.5;padding:7px 10px;border-radius:var(--radius);background:var(--panel);border:1px solid var(--border-soft);}
.import-diff-tag{display:inline-block;font-size:10px;text-transform:uppercase;letter-spacing:.4px;padding:1px 6px;border-radius:4px;margin-right:8px;font-weight:600;vertical-align:1px;}
.import-diff-added .import-diff-tag{background:rgba(var(--good-rgb),.16);color:var(--good);}
.import-diff-updated .import-diff-tag{background:rgba(var(--warn-rgb),.16);color:var(--warn);}
.import-diff-removed .import-diff-tag{background:rgba(var(--danger-rgb),.16);color:var(--danger);}
.import-diff-changes{margin-top:3px;color:var(--text-faint);font-size:11.5px;}
@keyframes overlayIn{from{opacity:0;}to{opacity:1;}}
@keyframes modalIn{from{opacity:0;transform:translateY(8px) scale(.98);}to{opacity:1;transform:translateY(0) scale(1);}}
/* Dim on purpose — you already clicked "edit" to get here, the title
   doesn't need to compete with the fields below it for attention. */
.modal h2{font-family:var(--display);font-size:16px;text-transform:uppercase;letter-spacing:.6px;margin:0 0 16px;display:flex;justify-content:space-between;align-items:center;color:var(--text-dim);}
.modal-close{background:none;border:none;color:var(--text-faint);font-size:24px;padding:2px 6px;position:relative;}
/* Invisible bigger hit area — the glyph itself is small, this makes it
   easier to actually land a tap/click on it. */
.modal-close::after{position:absolute;content:"";display:block;width:200%;height:175%;top:-36%;left:-50%;}

.modal-group{background:var(--panel2);border:1px solid var(--border-soft);border-radius:10px;padding:10px 10px 6px;margin-bottom:14px;}
.modal-group .field:last-child{margin-bottom:4px;}
.thumb-upload{display:flex;gap:10px;align-items:flex-start;margin-bottom:12px;}
.thumb-preview{width:88px;height:88px;border-radius:6px;background:var(--panel2);border:1px solid var(--border);overflow:hidden;display:flex;align-items:center;justify-content:center;flex-shrink:0;}
.thumb-preview img{width:100%;height:100%;object-fit:cover;}
.thumb-preview.ratio-1-1{width:96px;height:96px;}
.thumb-preview.thumb-preview-round{width:64px;height:64px;border-radius:50%;}
.thumb-preview.ratio-receipt{width:110px;height:150px;}
.thumb-preview.ratio-receipt img{object-fit:contain;}

/* Drag-and-drop target for every portfolio-cms upload widget — .thumb-upload
   already wraps the preview+button for image fields, this just adds the
   drop feedback on top; the video field gets its own dashed-box variant
   since it has no thumb-preview square to anchor against. */
.portfolio-dropzone{border-radius:8px;transition:background .15s ease,box-shadow .15s ease;}
.portfolio-dropzone.drag-over{background:var(--panel2);box-shadow:0 0 0 2px var(--accent) inset;}
.portfolio-dropzone-video{border:1px dashed var(--border);padding:12px;}
.portfolio-dropzone-video.drag-over{border-color:var(--accent);border-style:solid;}
.portfolio-dropzone-multi{border:1px dashed transparent;border-radius:10px;padding:4px;margin:-4px;}
.portfolio-dropzone-multi.drag-over{border-color:var(--accent);background:var(--panel2);}

.upload-progress{height:6px;border-radius:4px;background:var(--panel2);overflow:hidden;margin-top:8px;}
.upload-progress-fill{height:100%;width:0%;background:var(--accent);transition:width .2s ease;}

/* Visual trim popup — a SECOND overlay stacked above the video modal, not
   routed through renderModal()'s single-`modal` state (see trimModalOverlay
   in app.js), same "detached node, own class, immune to a background
   render() wipe" reasoning as .task-tag-popover. Higher z-index than the
   normal .overlay (100) so it sits on top of the still-open video modal. */
.trim-overlay{position:fixed;inset:0;z-index:150;background:rgba(10,10,10,.78);display:flex;
  align-items:center;justify-content:center;padding:16px;backdrop-filter:blur(2px);animation:overlayIn .18s ease;}
.trim-overlay.closing{animation:overlayOut .15s ease forwards;}
.trim-overlay.closing .modal{animation:modalOut .15s cubic-bezier(.4,0,1,1) forwards;}
.trim-video-wrap{position:relative;}
.trim-preview-video{width:100%;max-height:340px;background:#000;border-radius:8px;display:block;object-fit:contain;}
.trim-loading{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:10px;background:rgba(0,0,0,.55);border-radius:8px;color:#fff;font-size:12.5px;transition:opacity .2s ease;pointer-events:none;}
.trim-loading.hidden{opacity:0;}
.trim-spinner{width:26px;height:26px;border-radius:50%;border:2.5px solid rgba(255,255,255,.25);
  border-top-color:var(--accent);animation:ptrSpin .7s linear infinite;}
.trim-buffering{position:absolute;top:10px;right:10px;display:flex;align-items:center;gap:6px;
  background:rgba(0,0,0,.65);border-radius:20px;padding:5px 10px 5px 8px;font-size:11px;color:#fff;
  transition:opacity .15s ease;}
.trim-buffering.hidden{opacity:0;pointer-events:none;}
.trim-buffering .trim-spinner{width:13px;height:13px;border-width:2px;}
.trim-play-btn{background:var(--panel2);border:1px solid var(--border-soft);border-radius:50%;
  width:34px;height:34px;flex:0 0 34px;display:flex;align-items:center;justify-content:center;
  color:var(--text);cursor:pointer;font-size:13px;}
.trim-play-btn:hover{border-color:var(--accent);color:var(--accent);}
.trim-time{font-variant-numeric:tabular-nums;font-size:12.5px;color:var(--text-dim);min-width:38px;}
.trim-timeline{position:relative;height:34px;margin:6px 2px 4px;touch-action:none;user-select:none;}
.trim-track{position:absolute;left:7px;right:7px;top:50%;transform:translateY(-50%);height:6px;
  background:var(--panel2);border:1px solid var(--border-soft);border-radius:4px;cursor:pointer;}
.trim-range{position:absolute;top:50%;transform:translateY(-50%);height:6px;background:var(--accent);
  border-radius:4px;pointer-events:none;}
.trim-playhead{position:absolute;top:2px;bottom:2px;width:2px;background:#fff;pointer-events:none;
  box-shadow:0 0 4px rgba(0,0,0,.7);}
.trim-handle{position:absolute;top:50%;width:14px;height:26px;transform:translate(-50%,-50%);
  background:var(--accent);border-radius:4px;cursor:ew-resize;touch-action:none;
  box-shadow:0 2px 6px rgba(var(--shadow-rgb),.4);}
.trim-handle::before{content:"";position:absolute;left:50%;top:50%;width:2px;height:12px;
  background:rgba(255,255,255,.75);transform:translate(-50%,-50%);}
.trim-labels{display:flex;justify-content:space-between;font-size:12px;color:var(--text-dim);margin:2px 2px 14px;}
.trim-labels b{color:var(--text);font-weight:600;}

.portfolio-thumb-candidates{margin-top:4px;}
.portfolio-thumb-candidate-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:8px;}
.portfolio-thumb-candidate{padding:0;border:2px solid transparent;border-radius:6px;overflow:hidden;cursor:pointer;background:var(--panel2);aspect-ratio:16/9;}
.portfolio-thumb-candidate img{width:100%;height:100%;object-fit:cover;display:block;}
.portfolio-thumb-candidate:hover{border-color:var(--text-faint);}
.portfolio-thumb-candidate.selected{border-color:var(--accent);}
@media(max-width:480px){ .portfolio-thumb-candidate-grid{grid-template-columns:repeat(3,1fr);} }
.thumb-search-results{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px;}
.thumb-search-results .thumb-search-thumb{width:52px;height:52px;object-fit:cover;border-radius:5px;border:1px solid var(--border);cursor:pointer;}
.thumb-search-results .thumb-search-thumb:hover{border-color:var(--accent);}

.address-results{position:absolute;top:100%;left:0;right:0;z-index:20;background:rgba(var(--panel2-rgb),.92);backdrop-filter:blur(16px) saturate(150%);-webkit-backdrop-filter:blur(16px) saturate(150%);border:1px solid var(--border);border-radius:var(--radius);margin-top:4px;max-height:220px;overflow-y:auto;display:none;box-shadow:0 8px 20px rgba(var(--shadow-rgb),.35);}
.address-results.open{display:block;}
.address-result-item{padding:8px 11px;font-size:12.5px;color:var(--text-dim);cursor:pointer;border-bottom:1px solid var(--border-soft);}
.address-result-item:last-child{border-bottom:none;}
.address-result-item:hover{background:var(--panel3);color:var(--text);}
.hint{font-size:11px;color:var(--text-faint);margin-top:4px;}
.radio-inline{display:flex;align-items:center;gap:6px;text-transform:none;font-size:12px;color:var(--text);font-weight:400;margin-bottom:0;cursor:pointer;}
.radio-inline input{width:auto;}

/* team */
.person-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:14px;margin-bottom:8px;}
/* base recipe (background/border/radius/overflow/transition/hover) shared with .card/.stat-card — see that rule */
.person-photo{width:100%;aspect-ratio:1/1;background:var(--panel2);overflow:hidden;}
.person-photo img{width:100%;height:100%;object-fit:cover;display:block;}
.person-photo .avatar{width:100%;height:100%;border-radius:0;font-size:26px;}
.person-body{padding:14px;}
.role-chips{display:flex;flex-wrap:wrap;gap:6px;margin:10px 0 4px;}
.role-chip{font-family:var(--mono);font-size:10px;letter-spacing:.4px;text-transform:uppercase;color:var(--text-dim);background:var(--panel2);border:1px solid var(--border);border-radius:4px;padding:3px 8px;}
.person-links{display:flex;gap:6px;flex-wrap:wrap;margin-top:12px;}
.person-links a{text-decoration:none;}
/* The cog (settings) icon's glyph sits slightly high within its own
   viewBox — barely visible alone, but reads as crooked next to other icons
   on the same row (first spotted on Equipe's edit button). navIcon() in
   app.js adds this class to every use of that one icon, so the nudge is
   app-wide instead of only where someone happened to notice it. */
.nav-icon-cog{padding-top:3px;}

/* floating selection bar (checklist-from-selection) so it doesn't push layout */
.selection-bar{position:fixed;right:20px;bottom:20px;z-index:60;background:rgba(var(--panel2-rgb),.85);backdrop-filter:blur(20px) saturate(150%);-webkit-backdrop-filter:blur(20px) saturate(150%);border:1px solid var(--border);border-radius:10px;padding:10px 14px;box-shadow:0 8px 28px rgba(var(--shadow-rgb),.45);max-width:calc(100vw - 40px);}
/* Native <select> sizes itself to its widest <option> text (e.g. "Carol
   Nascimento (freelancer)") — capped so it doesn't blow up the bar's width
   next to the other buttons; the chosen option still shows in full via
   title on hover/its own tooltip since text-overflow can't ellipsis a
   select's closed-state text cross-browser. */
#bulk-homebase-select{max-width:150px;}
/* Only applied the render it actually appears — see selectionBarWasShown in
   app.js — a plain .selection-bar is a re-render while already shown. */
.selection-bar-enter{animation:selBarIn .2s cubic-bezier(.2,.8,.2,1);}
@keyframes selBarIn{from{opacity:0;transform:translateY(10px);}to{opacity:1;transform:translateY(0);}}

/* filters */
.filters{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:28px;padding-bottom:20px;border-bottom:1px solid var(--border-soft);}
.filters select,.filters input{width:auto;min-width:130px;}
.sort-dir-btn{width:32px;flex-shrink:0;padding:6px 0;font-size:12px;line-height:1;margin:0;justify-content:center;}
.sort-dir-btn .nav-icon{width:14px;height:14px;}

/* checklist */
.checklist-item{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:9px 12px;border:1px solid var(--border-soft);border-radius:8px;margin-bottom:6px;background:var(--panel);}
.checklist-item .name{font-size:13.5px;}
.checklist-item .qty{font-size:11px;color:var(--text-dim);font-family:var(--mono);background:var(--panel2);border:1px solid var(--border);padding:2px 9px;border-radius:20px;flex-shrink:0;}
.checklist-group-title{font-family:var(--display);font-size:12px;text-transform:uppercase;letter-spacing:.8px;color:var(--text-faint);margin:18px 0 8px;padding-bottom:6px;border-bottom:1px solid var(--border-soft);}
.checklist-group-title:first-of-type{margin-top:2px;}
.c-entry-row{display:flex;gap:8px;align-items:center;margin-bottom:8px;}
.c-entry-row .c-entry-label{flex:2;min-width:0;}
.c-entry-row .c-entry-qty{flex:0 0 64px;}
.c-entry-row .c-entry-cat{flex:1.2;min-width:0;}
.checklist-inv-list{max-height:240px;overflow-y:auto;border:1px solid var(--border-soft);border-radius:8px;padding:4px 10px;background:var(--panel2);}
.checklist-inv-row{display:flex;align-items:center;gap:10px;padding:7px 4px;font-size:12px;cursor:pointer;border-radius:6px;}
.checklist-inv-row:hover{background:var(--panel3);}
.checklist-inv-row input[type=checkbox]{width:16px;height:16px;accent-color:var(--accent);flex-shrink:0;}
.checklist-inv-row .c-inv-name{flex:1;}
.checklist-inv-row .c-inv-qty{width:60px;flex-shrink:0;}

/* Checklists de trabalho — live view (jobChecklistLiveModal). Each category
   is its own separate <table class="jc-table"> element (one per group), so
   table-layout:fixed + matching nth-child widths on every instance is what
   keeps columns lined up with each other down the page — a single table
   would auto-size instead. */
/* On a narrow/mobile viewport the fixed+percentage widths below add up to
   more than the modal actually has to give (the 50%/20% columns alone can
   exceed a phone's usable width once the modal's own padding is
   subtracted) — min-width plus the wrapper's overflow-x:auto turns that
   into a horizontal swipe instead of every column, the Carga buttons
   included, getting crushed down to unreadable/untappable slivers. */
.jc-table-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch;}
.jc-table{margin-bottom:18px;table-layout:fixed;width:100%;min-width:480px;}
.jc-table th:nth-child(1),.jc-table td:nth-child(1){width:36px;}
.jc-table th:nth-child(2),.jc-table td:nth-child(2){width:50%;}
.jc-table th:nth-child(3),.jc-table td:nth-child(3){width:20%;}
.jc-table th:nth-child(4),.jc-table td:nth-child(4){width:70px;}
.jc-table th:nth-child(5),.jc-table td:nth-child(5){width:auto;}
.jc-table td{overflow:hidden;text-overflow:ellipsis;}
.jc-table .jc-check{width:16px;height:16px;accent-color:var(--accent);cursor:pointer;}
.jc-row[data-missing="1"]{background:rgba(var(--danger-rgb),.07);}
.jc-row[data-missing="1"] td{color:var(--danger);}
.jc-row[data-missing="1"] td.text-dim{color:var(--danger);opacity:.8;}
.jc-charge-row{display:flex;gap:8px;flex-wrap:wrap;}
/* 17px was under even WCAG's minimum (24px) touch-target guidance — bumped
   to something a thumb can actually land on, not just a mouse cursor. */
.jc-charge-btn{width:26px;height:26px;border-radius:50%;padding:0;box-sizing:border-box;cursor:pointer;flex-shrink:0;transition:transform .1s ease;}
.jc-charge-btn:hover{transform:scale(1.15);}
.jc-charge-btn[data-state="empty"]{border:2px solid var(--danger);background:transparent;}
.jc-charge-btn[data-state="half"]{border:2px solid var(--warn);background:linear-gradient(90deg,var(--warn) 50%,transparent 50%);}
.jc-charge-btn[data-state="full"]{border:2px solid #4bce97;background:#4bce97;}

/* activity */
.log-row{display:flex;gap:10px;padding:8px 0;border-bottom:1px solid var(--border-soft);font-size:12.5px;}
.log-row .t{color:var(--text-faint);font-family:var(--mono);font-size:11px;white-space:nowrap;}

.badge{display:inline-flex;align-items:center;gap:5px;font-size:10.5px;font-weight:600;padding:3px 9px 3px 8px;border-radius:20px;text-transform:uppercase;letter-spacing:.4px;background:rgba(var(--panel2-rgb),.85);border:1px solid var(--border-soft);color:var(--text-dim);--tag-accent:var(--text-faint);}
.badge::before{content:'';width:6px;height:6px;border-radius:50%;background:var(--tag-accent);flex-shrink:0;}
/* accent color pairs (available/maintenance/atrasado) shared with .tag-good/-warn/-bad — see that rule */

.person-chip{display:inline-flex;align-items:center;gap:6px;background:var(--panel2);border:1px solid var(--border);border-radius:20px;padding:5px 10px;font-size:12.5px;}
.cat-name-input{background:none;border:none;color:var(--text);font:inherit;padding:0 1px;min-width:16px;border-radius:3px;transition:background .12s ease,box-shadow .12s ease;}
.cat-name-input:hover{background:var(--panel3);}
.cat-name-input:focus{outline:none;background:var(--panel);box-shadow:0 0 0 2px rgba(var(--accent-rgb),.18);}
/* Auto-grows the input to fit its text, no character-counting needed: the
   ::after ghost (normal flow, invisible) sizes the wrapper via real text
   layout, while the input is taken out of flow entirely (position:absolute)
   so its own default UA sizing can't leak into that calculation — a plain
   width:100% grid/flex sibling still does, which is the usual gotcha here. */
.cat-name-autogrow{position:relative;display:inline-block;vertical-align:middle;height:32px;}
.cat-name-autogrow::after{content:attr(data-value) " ";visibility:hidden;white-space:pre;font:inherit;padding:0 12px;}
.cat-name-autogrow .cat-name-input{position:absolute;inset:0;width:100%;min-width:0;margin-left:-5px;}
.avatar{width:20px;height:20px;border-radius:50%;background:var(--accent);color:#fff;font-weight:700;font-size:10px;display:flex;align-items:center;justify-content:center;font-family:var(--display);}

table.plain{width:100%;border-collapse:collapse;font-size:12px;}
table.plain th{text-align:left;font-size:10.5px;text-transform:uppercase;letter-spacing:.5px;color:var(--text-faint);padding:8px 10px;border-bottom:1px solid var(--border);}
table.plain td{padding:9px 10px;border-bottom:1px solid var(--border-soft);}
table.plain tr:hover td{background:var(--panel2);}
table.plain.budget-table td, table.plain.budget-table th{vertical-align:middle;padding-left:14px;padding-right:14px;}
table.plain.budget-table td input[type=number]{padding:6px 8px;width:100%;box-sizing:border-box;}
table.plain.budget-table tr:hover td{background:none;}
/* Serviço's own text (label + rate hint) doesn't have a fixed width, so on
   a narrow screen this scrolls instead of squeezing the Quantidade/Meia-
   diárias inputs (already narrowed to 70px above) down further. */
.budget-table-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch;}
@media(max-width:480px){
  table.plain.budget-table td, table.plain.budget-table th{padding-left:8px;padding-right:8px;}
}

.budget-total-row .lbl{font-weight:600;}
.budget-total-row .val{font-weight:700;}
.budget-total-row.featured .lbl{font-family:var(--display);text-transform:uppercase;letter-spacing:.6px;color:var(--accent);font-size:14px;}
.budget-total-row.featured .val{font-family:var(--display-strong);font-size:26px;font-weight:800;color:var(--accent);}

.mail-row{margin-bottom:10px;cursor:pointer;max-height:260px;overflow:hidden;transition:opacity .26s ease,transform .26s ease,max-height .26s ease,margin-bottom .26s ease;}
.mail-row:hover{border-color:var(--accent);}
.mail-row.mail-row-removing{opacity:0;transform:scale(.97);max-height:0;margin-bottom:0;pointer-events:none;}
.mail-row.unread .card-name{font-weight:700;}
.mail-row.unread .card-meta{color:var(--text);}
.mail-dot{display:inline-block;width:7px;height:7px;border-radius:50%;background:var(--accent);margin-right:7px;}
.mail-attachment-chips{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px;}
.mail-attachment-chip{display:inline-flex;align-items:center;gap:6px;background:var(--panel2);border:1px solid var(--border);border-radius:20px;padding:4px 6px 4px 12px;font-size:12px;}
.mail-attachment-chip button{background:none;border:none;color:var(--text-faint);cursor:pointer;font-size:15px;line-height:1;padding:2px 4px;}
.mail-attachment-chip button:hover{color:var(--danger);}
.mail-html-frame{width:100%;border:1px solid var(--border-soft);border-radius:8px;min-height:220px;background:#fff;}
.pagination-bar{display:flex;align-items:center;justify-content:center;gap:14px;margin-top:16px;}
.mail-row-date{margin-top:12px;}

/* Reuniões agendadas (/chat) — sits above the join/call UI either way, so
   scheduling or RSVP'ing doesn't require actually being in the call. */
.meeting-list{display:flex;flex-direction:column;gap:10px;}
.meeting-card{cursor:pointer;}
.meeting-card .card-body{padding:12px 14px;}
.meeting-rsvp-row{display:flex;flex-wrap:wrap;gap:10px;}
.meeting-rsvp-person{display:flex;align-items:center;gap:8px;background:var(--panel2);border:1px solid var(--border-soft);border-radius:8px;padding:5px 8px;}
.meeting-rsvp-name{font-size:11.5px;color:var(--text-dim);}
.meeting-rsvp-btns{display:flex;gap:2px;background:var(--panel);border-radius:6px;padding:2px;}
.meeting-rsvp-btn{border:none;background:transparent;color:var(--text-faint);font-size:10.5px;font-weight:600;padding:3px 8px;border-radius:4px;cursor:pointer;}
.meeting-rsvp-btn:hover{color:var(--text-dim);}
.meeting-rsvp-btn.yes.active{background:var(--good);color:#fff;}
.meeting-rsvp-btn.no.active{background:var(--danger);color:#fff;}
.meeting-rsvp-status{font-size:10.5px;font-weight:600;padding:3px 8px;color:var(--text-faint);}
.meeting-rsvp-status.yes{color:var(--good);}
.meeting-rsvp-status.no{color:var(--danger);}

.voz-join-card{max-width:320px;}
.voz-join-card .card-body{display:flex;flex-direction:column;}
.voz-presence-indicator{display:flex;align-items:center;gap:7px;font-size:12.5px;color:var(--text-dim);margin-bottom:14px;}
.voz-presence-dot{width:9px;height:9px;border-radius:50%;background:var(--text-faint);flex-shrink:0;transition:background .15s ease,box-shadow .15s ease;}
.voz-presence-dot.active{background:var(--accent);box-shadow:0 0 0 3px var(--danger-bg);}
.voz-participants{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:14px;}
.voz-chip{display:inline-flex;align-items:center;gap:6px;background:var(--panel2);border:1px solid var(--border);border-radius:12px;padding:6px 14px;font-size:12.5px;}
.voz-chip-self{border-color:var(--accent);color:var(--accent);}
.voz-chip-peer{flex-direction:column;align-items:stretch;gap:6px;padding:8px 12px;min-width:190px;}
.voz-chip-row{display:flex;align-items:center;gap:6px;}
.voz-chip-dot{width:8px;height:8px;border-radius:50%;background:var(--border);flex-shrink:0;transition:background .1s ease,box-shadow .1s ease;}
.voz-chip-dot.speaking{background:var(--good);box-shadow:0 0 0 3px var(--good-bg);}
.voz-chip-status{color:var(--text-faint);font-size:11px;}
.voz-volume-row{display:flex;align-items:center;gap:8px;}
.voz-volume-slider{flex:1;height:4px;accent-color:var(--accent);}
.voz-volume-val{font-size:10.5px;color:var(--text-faint);font-family:var(--mono);min-width:32px;text-align:right;flex-shrink:0;}
.voz-screen-wrap{background:#000;border:1px solid var(--border-soft);border-radius:10px;overflow:hidden;margin-bottom:14px;}
.voz-screen-label{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:6px 8px 6px 12px;font-size:11px;color:var(--text-dim);background:var(--panel);border-bottom:1px solid var(--border-soft);}
.voz-screen-fullscreen-btn{width:26px;height:26px;background:transparent;border:none;flex-shrink:0;}
.voz-screen-fullscreen-btn .nav-icon{width:14px;height:14px;}
.voz-screen-wrap video{display:block;width:100%;max-height:480px;background:#000;}
.voz-chat-log{display:flex;flex-direction:column;gap:12px;background:var(--panel);border:1px solid var(--border-soft);border-radius:10px;padding:14px;height:360px;overflow-y:auto;}
.voz-chat-msg{line-height:1.5;font-size:12px;color:var(--text-dim);}
.voz-chat-msg b{font-size:12px;color:var(--text);}
.voz-chat-msg.mine b{color:var(--accent);}
.voz-chat-msg-time{color:var(--text-faint);font-size:11px;}
.voz-chat-msg-edited{color:var(--text-faint);font-size:10.5px;}
.voz-chat-msg a{color:var(--accent);word-break:break-all;}
.voz-chat-msg a:hover{text-decoration:none;}
.voz-msg-actions{display:inline-flex;gap:4px;margin-left:6px;vertical-align:middle;opacity:.7;}
.voz-msg-actions .btn-icon{width:22px;height:22px;}
.voz-msg-actions:hover{opacity:1;}

/* Floating call avatars — fixed to the viewport, centered at the bottom (not
   the tab content) so they stay visible even while browsing another tab
   during a call, and centered rather than corner-anchored so they don't sit
   over a corner button like /chat's own send button. pointer-events:none
   makes them click-through regardless. Negative z-index was tried as an
   alternative fix so it wouldn't visually sit above UI at all, but real
   testing (sandbox AND real Firefox) showed it renders fully invisible
   instead — html/body both set an explicit opaque background, which ends up
   painting over everything behind it, negative z-index included — so this
   stays a normal positive z-index. */
.voz-avatar-overlay{position:fixed;left:50%;bottom:20px;transform:translateX(-50%);z-index:80;display:flex;pointer-events:none;}
.voz-avatar{width:150px;height:150px;border-radius:50%;object-fit:contain;filter:drop-shadow(0 6px 14px rgba(var(--shadow-rgb),.45));transition:opacity .2s ease,width .15s ease,height .15s ease,transform .15s ease;}
.voz-avatar + .voz-avatar{margin-left:-40px;}
/* Generated avatars (initials on a color circle — see vozAvatarSvgDataUri
   in app.js) have no separate "talking" art to swap to, so speaking reads
   through motion instead: a small scale pulse in sync with the same rhythm
   the presence dot elsewhere already pulses at. */
.voz-avatar.speaking{animation:vozAvatarSpeak 1.1s ease-in-out infinite;}
@keyframes vozAvatarSpeak{0%,100%{transform:scale(1);}50%{transform:scale(1.06);}}
@media(max-width:820px){
  .voz-avatar{width:70px;height:70px;}
  .voz-avatar + .voz-avatar{margin-left:-18px;}
}

/* Render free-tier "server may be sleeping" heads-up — see pingServerHeartbeat
   in app.js. Top-center so it never competes with the voz avatars (bottom)
   or the selection-bar/save-badge (bottom-right / sidebar). */
.server-wake-banner{
  position:fixed;top:16px;left:50%;transform:translateX(-50%) translateY(-12px);
  z-index:150;background:var(--warn-bg);color:var(--warn);border:1px solid var(--warn);
  border-radius:10px;padding:10px 16px;font-size:12px;font-weight:500;
  max-width:calc(100vw - 40px);box-shadow:0 8px 28px rgba(var(--shadow-rgb),.45);
  opacity:0;pointer-events:none;transition:opacity .2s ease,transform .2s ease;
}
.server-wake-banner.open{opacity:1;transform:translateX(-50%) translateY(0);}

/* ---------- Agenda month grid ---------- */
.agenda-toolbar{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;flex-wrap:wrap;gap:10px;}
.agenda-toolbar-title{font-family:var(--display);font-size:19px;font-weight:600;}
.agenda-legend{display:flex;gap:16px;margin-bottom:14px;flex-wrap:wrap;}
.agenda-legend-item{display:flex;align-items:center;gap:6px;font-size:11.5px;color:var(--text-dim);}
.agenda-legend-dot{width:9px;height:9px;border-radius:50%;display:inline-block;flex-shrink:0;}
.agenda-legend-dot-upcoming{background:var(--accent);}
.agenda-legend-dot-past{background:var(--text-dim);}
.agenda-legend-dot-personal{background:var(--warn);}

.agenda-grid{
  --agenda-cell-pad-top:6px;
  --agenda-daynum-h:20px;
  --agenda-bar-h:20px;
  display:flex;flex-direction:column;gap:1px;background:var(--border-soft);
  border:1px solid var(--border-soft);border-radius:10px;overflow:hidden;
}
.agenda-grid-headrow,.agenda-week{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));gap:1px;background:var(--border-soft);}
.agenda-week{position:relative;}
.agenda-grid-headcell{background:var(--panel2);color:var(--text-faint);font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;padding:8px 10px;text-align:center;}
.agenda-day-cell{background:var(--panel);min-height:104px;padding:var(--agenda-cell-pad-top) 6px 8px;cursor:pointer;transition:background .12s ease;display:flex;flex-direction:column;min-width:0;}
.agenda-day-cell:hover{background:var(--panel2);}
.agenda-day-cell-outside{background:var(--bg);}
.agenda-day-cell-outside .agenda-day-num{color:var(--text-faint);}
/* height+margin-bottom sums to --agenda-daynum-h, so bars (positioned via
   that same variable) start exactly where the day number ends. */
.agenda-day-num{height:16px;line-height:16px;margin-bottom:4px;font-size:12.5px;font-weight:600;color:var(--text-dim);}
.agenda-day-num-today{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:50%;background:var(--accent);color:#fff;}
.agenda-day-events{display:flex;flex-direction:column;gap:3px;min-width:0;}
.agenda-day-more{font-size:10.5px;color:var(--text-faint);padding:1px 6px;}

/* The day number always stays put at the top of the cell — multi-day bars
   and single-day chips both stack below it. Bars are a separate overlay
   layer (see .agenda-week-bars), so the chip list gets pushed down to make
   room for however many bar lanes this week is using. */
.agenda-week-lanes-1 .agenda-day-events{margin-top:var(--agenda-bar-h);}
.agenda-week-lanes-2 .agenda-day-events{margin-top:calc(var(--agenda-bar-h)*2);}

/* Multi-day events render once per week as a bar spanning every column they
   touch, positioned with plain percentage math (col/7, span/7) instead of
   one chip repeated per day — the standard approach for month-grid
   calendars. Sits in a pointer-events:none layer so clicks fall through to
   the day-cell underneath, same as clicking a normal single-day chip. */
.agenda-week-bars{position:absolute;inset:0;pointer-events:none;}
.agenda-bar{position:absolute;left:calc(var(--col) / 7 * 100% + 2px);width:calc(var(--span) / 7 * 100% - 4px);top:calc(var(--agenda-cell-pad-top) + var(--agenda-daynum-h) + var(--lane) * var(--agenda-bar-h));white-space:nowrap;overflow:hidden;}

.agenda-chip{display:flex;align-items:center;gap:5px;padding:3px 9px;border-radius:20px;font-size:10px;font-weight:500;line-height:1.2;min-width:0;}
.agenda-chip-title{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.agenda-chip-time{flex-shrink:0;opacity:.85;font-family:var(--mono);font-size:9.5px;}
.agenda-chip-upcoming{background:rgba(255,0,9,.14);color:#FFE5E6;border:1px solid #84171b;}
.agenda-chip-past{background:#323232;color:#848484;border:1px solid #3f3f3f;}
.agenda-chip-personal{background:var(--warn-bg);color:var(--warn);border:1px solid #5F4113;}
/* Continuation caps: a bar that carries on past this week's edge loses the
   rounded corner on that side, so it visually connects with next/previous
   week's segment instead of implying the event ends here. */
.agenda-bar-cont-start{border-top-left-radius:0;border-bottom-left-radius:0;}
.agenda-bar-cont-end{border-top-right-radius:0;border-bottom-right-radius:0;}

.agenda-year-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;}
.agenda-year-cell{background:var(--panel);border:1px solid var(--border-soft);border-radius:10px;padding:14px 14px 12px;cursor:pointer;transition:background .12s ease,border-color .12s ease;}
.agenda-year-cell:hover{background:var(--panel2);}
.agenda-year-cell-current{border-color:var(--accent);}
.agenda-year-cell-name{font-family:var(--display);font-size:14.5px;font-weight:600;margin-bottom:10px;}
.agenda-year-cell-dots{display:flex;gap:12px;flex-wrap:wrap;}
.agenda-year-dot{display:flex;align-items:center;gap:6px;font-size:12.5px;font-family:var(--mono);color:var(--text-dim);}

@media(max-width:820px){
  .agenda-grid{--agenda-cell-pad-top:4px;--agenda-daynum-h:14px;--agenda-bar-h:14px;}
  .agenda-grid-headcell{font-size:9.5px;padding:6px 2px;}
  .agenda-day-cell{min-height:64px;padding:4px 3px 6px;}
  .agenda-day-num{font-size:11px;height:12px;line-height:12px;margin-bottom:2px;}
  .agenda-chip{font-size:9.5px;padding:1px 4px;}
  .agenda-chip-time{display:none;}
  .agenda-day-more{font-size:9px;}
  .agenda-toolbar-title{font-size:16px;}
  .agenda-year-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;}
  .agenda-year-cell{padding:10px 10px 8px;}
}

/* ---------- task tags ---------- */
.card-body-tags{position:absolute;top:-4px;left:0;height:8px;pointer-events:none;}
.card-body-tag{height:8px;width:40px;position:absolute;top:0;left:14px;border-radius:5px;z-index:2;}
.card-body-tag:nth-child(2){left:60px;}
.card-body-tag:nth-child(3){left:106px;}
.card-body-tag:nth-child(4){left:152px;}
.card-body-tag:nth-child(5){left:198px;}
.card-body-tag:nth-child(n+6){display:none;} /* card's too narrow for more than a handful of bars to read cleanly */

.task-tag-pills{display:flex;flex-wrap:wrap;gap:6px;align-items:center;}
.task-tag-pill{display:inline-flex;align-items:center;font-size:11.5px;font-weight:600;color:#fff;padding:4px 10px;border-radius:20px;line-height:1.2;text-shadow:0 1px 1px rgba(var(--shadow-rgb),.25);}
.task-tag-pill-btn{background:none;border:none;padding:0;cursor:pointer;border-radius:20px;}
.task-tag-pill-btn:hover .task-tag-pill{filter:brightness(1.1);}
.task-assignee-pill{background:var(--panel3);color:var(--text);text-shadow:none;}
.task-tag-pill-btn:hover .task-assignee-pill{background:var(--border-soft);}
#tk-tags-add{width:26px;height:26px;border-radius:50%;font-size:15px;line-height:1;}

.task-tag-popover{
  position:fixed;z-index:200;width:300px;max-width:calc(100vw - 24px);
  background:rgba(var(--panel-rgb),.88);backdrop-filter:blur(24px) saturate(150%);-webkit-backdrop-filter:blur(24px) saturate(150%);
  border:1px solid var(--border);border-radius:12px;
  box-shadow:0 20px 50px rgba(var(--shadow-rgb),.5);padding:14px;
  animation:modalIn .16s cubic-bezier(.2,.8,.2,1);
}
.task-tag-popover-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;color:var(--text-dim);}
.task-tag-popover .field{margin-bottom:10px;}
.task-tag-search{margin-bottom:8px;}
.task-tag-list{max-height:260px;overflow-y:auto;display:flex;flex-direction:column;gap:4px;}
.task-tag-row{display:flex;align-items:center;justify-content:space-between;gap:8px;}
.task-tag-row-label{display:flex;align-items:center;gap:8px;flex:1;min-width:0;cursor:pointer;}
.task-tag-row-label input[type=checkbox]{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent);}
.task-tag-row-label .task-tag-pill{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.task-tag-color-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:8px;}
.task-tag-color-swatch{width:100%;aspect-ratio:1;border-radius:6px;border:2px solid transparent;cursor:pointer;color:#fff;font-weight:700;display:flex;align-items:center;justify-content:center;}
.task-tag-color-swatch.selected{border-color:#fff;box-shadow:0 0 0 2px var(--panel);}
.task-tag-color-swatch.selected::after{content:"✓";}

/* ---------- task description: markdown editor + rendered view ---------- */
.task-desc-field{margin:24px 0 26px;}
.task-desc-actions{margin-top:8px;justify-content:flex-end;}
.task-desc-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:2px;margin-bottom:8px;padding:4px;background:var(--panel2);border:1px solid var(--border-soft);border-radius:var(--radius);}
.task-desc-tb-btn{width:28px;height:28px;flex-shrink:0;border:none;background:transparent;color:var(--text-dim);border-radius:6px;font-size:12.5px;cursor:pointer;transition:background .12s ease,color .12s ease;}
.task-desc-tb-btn:hover{background:var(--panel);color:var(--text);}
.task-desc-tb-bold{font-weight:700;}
.task-desc-tb-italic{font-style:italic;}
.task-desc-tb-strike{text-decoration:line-through;}
.task-desc-tb-sep{width:1px;height:18px;background:var(--border-soft);margin:0 4px;flex-shrink:0;}

.md-content,.task-desc-view{font-size:12px;line-height:1.6;color:var(--text-dim);}
.md-content h1,.md-content h2{color:var(--text);font-family:var(--display);margin:14px 0 6px;}
.md-content h1:first-child,.md-content h2:first-child{margin-top:0;}
.md-content h1{font-size:18px;}
.md-content h2{font-size:15px;}
.md-content p{margin:0 0 8px;}
.md-content p:last-child{margin-bottom:0;}
.md-content ul,.md-content ol{margin:0 0 8px;padding-left:22px;}
.md-content li{margin-bottom:2px;}
.md-content blockquote{margin:0 0 8px;padding:4px 12px;border-left:3px solid var(--accent-dim);color:var(--text-faint);}
.md-content a{color:var(--accent);text-decoration:underline;}
.md-content .md-img{max-width:100%;border-radius:8px;display:block;margin:6px 0;}
.md-content .md-empty{color:var(--text-faint);font-style:italic;}

/* ---------- PWA launch intro ---------- */
/* Only ever shown standalone (installed-PWA) launches — see the inline
   script in index.html, which removes this element outright everywhere
   else so browser-tab visitors never pay for it. */
#pwa-intro{
  position:fixed; inset:0; z-index:9999;
  display:flex; align-items:center; justify-content:center; flex-direction:column;
  background:radial-gradient(circle at 50% 46%, #0d0d0f 0%, #050506 55%, #000 100%);
  transition:opacity .5s ease;
}
#pwa-intro.fade-out{opacity:0; pointer-events:none;}
#pwa-intro .vignette{
  position:absolute; inset:0;
  background:radial-gradient(circle at 50% 50%, transparent 40%, rgba(0,0,0,.65) 100%);
  pointer-events:none;
}
#pwa-intro .logo-wrap{
  width:min(34vw, 168px);
  opacity:0;
  transition:opacity 2s linear;
}
#pwa-intro .logo-wrap.in{opacity:1;}
#pwa-intro .logotype{
  width:min(9vw, 46px);
  margin-top:14px;
  opacity:0;
  transition:opacity 1s linear;
}
#pwa-intro .logotype.in{opacity:1;}
#pwa-intro-draw path{
  fill:none;
  stroke-width:1.6;
  vector-effect:non-scaling-stroke;
  transition:stroke-dashoffset 2s cubic-bezier(0.33, 1, 0.68, 1);
}
#pwa-intro .draw-white{stroke:var(--text);}
#pwa-intro .draw-red{stroke:var(--accent);}
#pwa-intro-fillgroup{opacity:0; transition:opacity 2s linear;}
#pwa-intro-draw{transition:opacity 2s linear;}
