/* console.css - the Operations Console's own stylesheet.
 *
 * Moved out of admin/index.html on 2026-09-20 (owner: the codebase should be
 * modular). Byte-identical CSS in the same order; index.html was 4,767 lines
 * and 2,604 of them were this block.
 *
 * LOAD ORDER MATTERS: this file must come BEFORE app.css. Tailwind utilities
 * like `hidden` have to win ties against component classes such as
 * .modal-backdrop - that is the order the old Play CDN produced, and the
 * reason app.css is built without @layer (src/input.css). Swap the two and
 * modals never hide.
 */
      /* ============================================================
         Stellar v3 — global theme (see admin/THEME.md). Mint primary +
         info-blue structure, teal-navy surfaces. Same variable names as
         before so every component that reads var(--bg-card) / --accent
         reskins for free; values are 1:1 with the stellar-v3 reference.
      ============================================================ */
      /* ------------------------------------------------------------------
         TOKENS. :root carries the DARK theme (the default, and the only one
         that existed before light mode). :root[data-theme="light"] further
         down overrides every one of these — so a token defined here without a
         light counterpart is a bug, not a shortcut.

         Tokens are named for the JOB they do, never for the colour they
         happen to be in one theme: --text-faint stays "the quietest text"
         whether it resolves to a pale grey on navy or a slate on white, and
         --accent-light is "the emphasis step above --accent", which means
         BRIGHTER on dark and DEEPER on light. New code uses var(--token);
         raw hex/rgba in a component is a regression (see admin/THEME.md §10).
      ------------------------------------------------------------------ */
      :root {
        color-scheme: dark;

        /* ---- Surfaces ---- */
        --bg-canvas:    #081119;
        --bg-raised:    #0c1e29;
        --bg-card:      #0b1a24;
        --bg-card-2:    #102532;
        --bg-field:     #0e222e;
        --bg-elev:      #122430;
        --bg-well:      #1a3646;
        /* Gradient STOPS live as their own tokens because ~35 call sites
           inline the panel gradient rather than using var(--bg-panel). */
        --panel-a:      rgba(30,55,69,.44);
        --panel-b:      rgba(15,31,41,.52);
        --bg-panel:     linear-gradient(180deg, var(--panel-a), var(--panel-b));
        --shell-a:      #0f2029;
        --shell-b:      #0b1720;
        --bg-shell:     linear-gradient(180deg, var(--shell-a), var(--shell-b));
        --head-a:       #0b1a24;
        --head-b:       #091620;
        --bg-head:      linear-gradient(180deg, var(--head-a), var(--head-b));
        /* Recessed wells (gate fields, key readouts): a dark hole on dark, a
           faint ink wash on light — an inverted job, not an inverted colour. */
        --bg-inset:     rgba(0,0,0,.28);
        --bg-inset-2:   rgba(0,0,0,.32);
        --backdrop:     rgba(8,20,28,.64);
        --backdrop-soft: rgba(8,20,28,.55);

        /* ---- Strokes ---- */
        --stroke:       rgba(255,255,255,.07);
        --stroke-2:     rgba(255,255,255,.14);
        --stroke-soft:  rgba(255,255,255,.06);
        --stroke-strong: rgba(255,255,255,.24);

        /* ---- Overlay scale ----
           ~480 call sites paint rgba(255,255,255,.0x) hover/inset washes.
           On a light canvas a white wash is invisible, so the whole scale
           flips to an ink wash. Steps match the alphas actually in use. */
        --tint-015: rgba(255,255,255,.015);
        --tint-02:  rgba(255,255,255,.02);
        --tint-03:  rgba(255,255,255,.03);
        --tint-04:  rgba(255,255,255,.04);
        --tint-045: rgba(255,255,255,.045);
        --tint-05:  rgba(255,255,255,.05);
        --tint-055: rgba(255,255,255,.055);
        --tint-06:  rgba(255,255,255,.06);
        --tint-07:  rgba(255,255,255,.07);
        --tint-08:  rgba(255,255,255,.08);
        --tint-09:  rgba(255,255,255,.09);
        --tint-10:  rgba(255,255,255,.1);
        --tint-12:  rgba(255,255,255,.12);
        --tint-14:  rgba(255,255,255,.14);
        --tint-16:  rgba(255,255,255,.16);
        --tint-20:  rgba(255,255,255,.2);
        --tint-22:  rgba(255,255,255,.22);
        --tint-24:  rgba(255,255,255,.24);
        --tint-26:  rgba(255,255,255,.26);
        --tint-32:  rgba(255,255,255,.32);

        /* ---- Text ramp (primary -> quietest) ---- */
        --text:           #eef4f6;
        --text-bright:    #dfeaee;
        --text-body:      #cdd9df;
        --text-secondary: #c3d2d9;
        --text-soft:      #9db0ba;
        --text-muted:     #8ea2ac;
        --text-dim:       #7f939d;
        --text-faint:     #6b7f89;
        --text-ghost:     #5c6d76;
        /* Label sitting ON a filled accent/CTA — flips with the theme,
           because the mint it sits on flips from bright to deep. */
        --text-ink:       #08131b;
        /* Label sitting on a SATURATED, theme-independent fill: a violet
           CTA, the env warning banner, a map pin, avatar initials on a
           colour gradient. Those fills are the same in both themes, so
           their label must be too — this is deliberately NOT --text-ink. */
        --text-on-fill:   #ffffff;
        /* Switch/toggle knob. White on both themes (it rides a filled or
           tinted track); light mode gives it a hairline so it still reads
           against a pale unchecked track. */
        --switch-knob:    #ffffff;
        --switch-knob-ring: none;

        /* ---- Two-accent: mint = interactive/CTA; info blue = structure ---- */
        --accent:       #3fd0c9;
        --accent-hover: #33c9c4;
        --accent-light: #6fe0da;
        --accent-soft:  #5fd9d2;
        --accent-pale:  #8fe9e2;
        /* Decorative-only mint (live dot, active rail bar, spinner, 2px
           rules). Brand chroma is kept here because these marks carry no
           text, so they answer to the 3:1 non-text bar, not 4.5:1. */
        --accent-vivid: #3fd0c9;
        --accent-dim:   rgba(63,208,201,.16);
        --accent-info:       #4a90d9;
        --accent-info-light: #6aa6e0;
        --accent-info-soft:  #9fc3ec;

        /* ---- Status ---- */
        --green:       #43c98a;
        --green-soft:  #8fdcb5;
        --amber:       #e9a94b;
        --amber-soft:  #e9b563;
        --amber-pale:  #f0d29a;
        --urgent:      #ef5b5b;
        --urgent-mid:  #ef7a7a;
        --urgent-soft: #ef8b8b;
        --urgent-pale: #fca5a5;
        --violet:      #a892f4;
        --violet-soft: #b3a3f7;

        /* ---- Kanban lanes (KANBAN_COLUMNS in core.js reads these) ---- */
        --lane-new:          #4a90d9;
        --lane-approved:     #3fd0c9;
        --lane-tagged:       #e9a94b;
        --lane-scheduled:    #8b6ef0;
        --lane-verification: #f2799e;
        --lane-complete:     #43c98a;

        /* ---- Lifecycle status dots (STATUSES in core.js reads these) ---- */
        --status-new:       #4a90d9;
        --status-triage:    #6aa6e0;
        --status-dispatch:  #e9a94b;
        --status-scheduled: #a892f4;
        --status-progress:  #e9b563;
        --status-review:    #a892f4;
        --status-approved:  #43c98a;
        --status-closed:    #5c6d76;
        /* Legacy aliases — older call sites still name these. */
        --status-vendor:    var(--status-new);
        --status-owner:     var(--status-dispatch);
        --status-complete:  var(--status-approved);

        /* ---- Depth ----
           Dark theme separates surfaces with GLOW and deep black drop
           shadows; light theme has no glow to give, so it separates with
           hairlines plus a short, tight shadow. */
        --shadow-card:   0 24px 48px -28px rgba(0,0,0,.7);
        --shadow-panel:  0 24px 48px -28px rgba(0,0,0,.7);
        --shadow-modal:  0 40px 120px rgba(0,0,0,.72);
        --shadow-drawer: -28px 0 64px -24px rgba(0,0,0,.72);
        --shadow-pop:    0 30px 70px -34px rgba(0,0,0,.8);
        --glow-accent:   0 8px 20px -8px rgba(63,208,201,.6);
        --glow-rail:     0 0 12px rgba(63,208,201,.8);
        --glow-info:     0 0 10px rgba(74,144,217,.55);

        /* ---- Chrome ---- */
        --scrollbar-thumb:       rgba(255,255,255,.16);
        --scrollbar-thumb-hover: rgba(255,255,255,.26);
        --selection:             rgba(63,208,201,.28);
        /* Translucent pop surfaces (dropdowns, map HUD, glass bars). */
        --glass:        rgba(15,32,41,.72);
        --glass-strong: rgba(11,23,32,.9);
        --surface-pop:  rgba(19,34,44,.97);
        --map-hud:      rgba(10,22,30,.82);
        --chart-rule:   rgba(255,255,255,.3);

        /* ---- Shade scale (drop shadows / scrims) ----
           The counterpart to --tint. Dark theme drops near-opaque black;
           light theme drops a much weaker slate, because a .7 black shadow
           on white reads as dirt rather than depth. */
        --shade-15: rgba(0,0,0,.15);
        --shade-16: rgba(0,0,0,.16);
        --shade-24: rgba(0,0,0,.24);
        --shade-28: rgba(0,0,0,.28);
        --shade-32: rgba(0,0,0,.32);
        --shade-35: rgba(0,0,0,.35);
        --shade-40: rgba(0,0,0,.4);
        --shade-50: rgba(0,0,0,.5);
        --shade-55: rgba(0,0,0,.55);
        --shade-60: rgba(0,0,0,.6);
        --shade-70: rgba(0,0,0,.7);
        --shade-72: rgba(0,0,0,.72);
        --shade-75: rgba(0,0,0,.75);
        --shade-80: rgba(0,0,0,.8);
        --shade-85: rgba(0,0,0,.85);
        --shade-90: rgba(0,0,0,.9);
      }

      /* ==================================================================
         LIGHT THEME — a first-class palette, not an inversion of the dark
         one. Set by data-theme="light" on <html> (see the pre-paint script
         in <head>; `system` resolves to one of the two before first paint).

         Design intent, against THEME.md's: this is an operations console
         people read for hours, so the canvas is a cool off-white and cards
         are pure white, separated by real hairlines and a short shadow
         rather than the dark theme's glow. Every token that can carry text
         is held to WCAG AA (4.5:1) against the WORST of white card, page
         canvas, hover inset, and its own 12% chip fill — which is why the
         accents are deeper here than the brand mint: #3fd0c9 on white is
         1.9:1 and cannot legally carry a label. Brand mint survives as
         --accent-vivid for marks that carry no text.
      ================================================================== */
      :root[data-theme="light"] {
        color-scheme: light;

        --bg-canvas:    #eef2f5;
        --bg-raised:    #ffffff;
        --bg-card:      #ffffff;
        --bg-card-2:    #e7edf1;
        --bg-field:     #f4f7f9;
        --bg-elev:      #f0f4f7;
        --bg-well:      #dbe7ee;
        --panel-a:      rgba(255,255,255,.96);
        --panel-b:      rgba(247,250,252,.96);
        --shell-a:      #ffffff;
        --shell-b:      #f7fafc;
        --head-a:       #ffffff;
        --head-b:       #f3f7f9;
        --bg-inset:     rgba(15,42,58,.05);
        --bg-inset-2:   rgba(15,42,58,.06);
        /* The modal scrim stays dark in both themes — it is a shadow cast
           over the page, not a surface. */
        --backdrop:      rgba(18,38,50,.42);
        --backdrop-soft: rgba(18,38,50,.32);

        --stroke:        rgba(15,42,58,.13);
        --stroke-2:      rgba(15,42,58,.22);
        --stroke-soft:   rgba(15,42,58,.10);
        --stroke-strong: rgba(15,42,58,.32);

        --tint-015: rgba(15,42,58,.02);
        --tint-02:  rgba(15,42,58,.025);
        --tint-03:  rgba(15,42,58,.035);
        --tint-04:  rgba(15,42,58,.045);
        --tint-045: rgba(15,42,58,.05);
        --tint-05:  rgba(15,42,58,.055);
        --tint-055: rgba(15,42,58,.06);
        --tint-06:  rgba(15,42,58,.065);
        --tint-07:  rgba(15,42,58,.075);
        --tint-08:  rgba(15,42,58,.085);
        --tint-09:  rgba(15,42,58,.095);
        --tint-10:  rgba(15,42,58,.10);
        --tint-12:  rgba(15,42,58,.12);
        --tint-14:  rgba(15,42,58,.14);
        --tint-16:  rgba(15,42,58,.16);
        --tint-20:  rgba(15,42,58,.20);
        --tint-22:  rgba(15,42,58,.22);
        --tint-24:  rgba(15,42,58,.24);
        --tint-26:  rgba(15,42,58,.26);
        --tint-32:  rgba(15,42,58,.32);

        --text:           #0d2430;
        --text-bright:    #12303e;
        --text-body:      #1d3d4c;
        --text-secondary: #2c4d5c;
        --text-soft:      #3d5f6c;
        --text-muted:     #476875;
        --text-dim:       #4c6d79;
        --text-faint:     #506f7b;
        /* The bottom of the ramp sits ON the AA floor, so --text-ghost and
           --text-faint deliberately share a value here: a 2% luminance step
           below this is invisible AND illegal. Emphasis at this level is
           carried by weight and size instead. */
        --text-ghost:     #506f7b;
        /* Deep mint CTA -> white label (5.7:1). */
        --text-ink:       #ffffff;
        --text-on-fill:   #ffffff;
        --switch-knob:    #ffffff;
        --switch-knob-ring: 0 0 0 1px rgba(15,42,58,.22);

        --accent:       #08736d;
        --accent-hover: #066059;
        --accent-light: #076661;
        --accent-soft:  #076b66;
        --accent-pale:  #055d58;
        --accent-vivid: #0d8d84;
        --accent-dim:   rgba(8,115,109,.12);
        --accent-info:       #1b64b0;
        --accent-info-light: #175697;
        --accent-info-soft:  #245f99;

        --green:       #0f7350;
        --green-soft:  #0c6244;
        --amber:       #8b5816;
        --amber-soft:  #7a4d12;
        --amber-pale:  #6d450f;
        --urgent:      #c0332f;
        --urgent-mid:  #b32f2b;
        --urgent-soft: #a82b28;
        --urgent-pale: #992623;
        --violet:      #6741d4;
        --violet-soft: #583ab8;

        --lane-new:          #1b64b0;
        --lane-approved:     #08736d;
        --lane-tagged:       #8b5816;
        --lane-scheduled:    #5b3fd0;
        --lane-verification: #b3306a;
        --lane-complete:     #0f7350;

        --status-new:       #1b64b0;
        --status-triage:    #245f99;
        --status-dispatch:  #8b5816;
        --status-scheduled: #6741d4;
        --status-progress:  #7a4d12;
        --status-review:    #6741d4;
        --status-approved:  #0f7350;
        --status-closed:    #506f7b;

        --shadow-card:   0 1px 2px rgba(15,42,58,.06), 0 10px 24px -18px rgba(15,42,58,.20);
        --shadow-panel:  0 1px 2px rgba(15,42,58,.06), 0 10px 24px -18px rgba(15,42,58,.20);
        --shadow-modal:  0 28px 70px -24px rgba(15,42,58,.30);
        --shadow-drawer: -22px 0 52px -28px rgba(15,42,58,.24);
        --shadow-pop:    0 24px 56px -30px rgba(15,42,58,.28);
        /* No glow on light — a halo on white reads as a printing defect. */
        --glow-accent:   0 6px 16px -8px rgba(8,115,109,.40);
        --glow-rail:     none;
        --glow-info:     none;

        --scrollbar-thumb:       rgba(15,42,58,.24);
        --scrollbar-thumb-hover: rgba(15,42,58,.36);
        --selection:             rgba(8,115,109,.20);
        --glass:        rgba(255,255,255,.78);
        --glass-strong: rgba(255,255,255,.92);
        --surface-pop:  rgba(255,255,255,.98);
        --map-hud:      rgba(255,255,255,.90);
        --chart-rule:   rgba(15,42,58,.28);

        --shade-15: rgba(15,42,58,.06);
        --shade-16: rgba(15,42,58,.06);
        --shade-24: rgba(15,42,58,.09);
        --shade-28: rgba(15,42,58,.10);
        --shade-32: rgba(15,42,58,.11);
        --shade-35: rgba(15,42,58,.12);
        --shade-40: rgba(15,42,58,.14);
        --shade-50: rgba(15,42,58,.16);
        --shade-55: rgba(15,42,58,.18);
        --shade-60: rgba(15,42,58,.20);
        --shade-70: rgba(15,42,58,.22);
        --shade-72: rgba(15,42,58,.22);
        --shade-75: rgba(15,42,58,.24);
        --shade-80: rgba(15,42,58,.26);
        --shade-85: rgba(15,42,58,.28);
        --shade-90: rgba(15,42,58,.30);
      }
      html, body { background: var(--bg-canvas); color: var(--text); font-family: 'Poppins', 'Inter', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
      ::-webkit-scrollbar { width: 6px; height: 6px; }
      ::-webkit-scrollbar-track { background: transparent; }
      ::-webkit-scrollbar-thumb { background: var(--scrollbar-thumb); border-radius: 3px; }
      ::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-thumb-hover); }
      .sidebar-link { position:relative; display:flex; align-items:center; gap:13px; height:44px; padding:0 14px; border-radius:12px; color:var(--text-secondary); font-size:13px; font-weight:400; cursor:pointer; transition: background-color .2s ease, color .2s ease, padding .28s cubic-bezier(.4,0,.2,1), gap .28s cubic-bezier(.4,0,.2,1), border-radius .28s cubic-bezier(.4,0,.2,1); }
      .sidebar-link:hover { color:var(--text); background: var(--tint-05); }
      .sidebar-link.active { color:var(--accent); font-weight:500; background: color-mix(in srgb, var(--accent-hover) 13%, transparent); }
      /* 3px mint accent bar on the active item (spec §5) - both rail states. */
      .sidebar-link.active::before { content:''; position:absolute; left:-1px; top:50%; transform:translateY(-50%); width:3px; height:22px; border-radius:3px; background:var(--accent); box-shadow:var(--glow-rail); }
      .sidebar-link .lucide { width:18px; height:18px; flex-shrink:0; color:var(--text-soft); stroke-width:1.8; }
      .sidebar-link:hover .lucide { color:var(--text-secondary); }
      .sidebar-link.active .lucide { color:var(--accent); }
      /* Status / priority badges: 7px radius per spec §3 (chips 7-9px), not full pills. */
      .pill { display:inline-flex; align-items:center; gap:.35rem; padding:.2rem .55rem; border-radius:7px; font-size:.7rem; font-weight:500; font-family:'Poppins','Inter',sans-serif; line-height:1.1; white-space:nowrap; }
      .badge-dot { width:6px; height:6px; border-radius:999px; display:inline-block; }
      /* Notification badges: appear only when there's something NEW since you last
         looked, and clear to nothing once you open the tab. */
      .nav-badge { margin-left:auto; min-width:18px; height:18px; padding:0 5px; border-radius:999px; font-family:'JetBrains Mono',ui-monospace,monospace; font-size:10px; font-weight:700; line-height:18px; text-align:center; color:var(--text-ink); background:var(--accent-info,var(--accent-info)); box-shadow:0 0 0 1.5px color-mix(in srgb, var(--accent-info) 22%, transparent); animation:navPop .18s ease; }
      .nav-badge.action { color:var(--text-ink); background:var(--amber); box-shadow:0 0 0 1.5px color-mix(in srgb, var(--amber) 25%, transparent); }
      @keyframes navPop { from { transform:scale(.6); opacity:0; } to { transform:scale(1); opacity:1; } }
      /* ======================================================== PERSISTENT TOP BAR
         (WO-02007) FIXED full-width strip: brand + workspace switcher on the
         left (its own segment, the rail's exact width with the same hairline,
         so header + rail read as one column), the view controls right-aligned
         exactly as the reference draws them. A translucent canvas wash +
         blur gives it the fill a fixed bar needs, so content scrolls under it
         instead of showing through. --topbar-h is the single source of truth
         for every offset that has to clear the bar (sidebar top, mobile
         backdrop, #mainCol padding, #dataBusy on phones, the kanban height). */
      :root { --topbar-h: 64px; }
      .topbar {
        position: fixed; top: 0; left: 0; right: 0; z-index: 40;
        height: var(--topbar-h);
        display: flex; align-items: stretch;
        background: color-mix(in srgb, var(--bg-canvas) 88%, transparent);
        -webkit-backdrop-filter: blur(18px); backdrop-filter: blur(18px);
        border-bottom: 1px solid var(--tint-06);
      }
      /* Left segment: the rail column, continued into the header. */
      #topBrand {
        position: relative; flex: none;
        width: 232px; height: 100%;
        display: flex; align-items: center;
        gap: 10px; padding: 0 12px 0 14px;
        transition: width .28s cubic-bezier(.4,0,.2,1);
      }
      #topBrand #wsSwitcherBtn {
        position: relative; display: flex; align-items: center; gap: 10px;
        min-width: 0; flex: 1 1 auto; padding: 0; border: none;
        background: none; cursor: pointer;
        transition: gap .28s cubic-bezier(.4,0,.2,1);
      }
      /* ONE badge on the workspace tile for invitations + unread elsewhere
         (this is what _renderWsAttention appends; geometry in CSS so it
         tracks the tile in every size). left 37 sits it on the 44px tile's
         top-right corner (review, #346); the collapsed (36px) and phone
         (30px) tiles carry their own offset below. */
      .ws-attn-dot {
        position: absolute; top: -2px; left: 37px; width: 11px; height: 11px; border-radius: 50%;
        background: var(--accent); pointer-events: none;
        box-shadow: 0 0 0 2.5px var(--bg-card), 0 0 7px color-mix(in srgb, var(--accent) 70%, transparent);
      }
      /* Collapse toggle - the reference's BARE ‹ at the right end of the brand
         row (no pill/border). Only the ICON flips when collapsed. */
      #navChevron { flex: none; width: 32px; height: 32px; align-items: center; justify-content: center; border: none; background: transparent; border-radius: 9px; color: var(--text-soft); cursor: pointer; transition: background-color .15s ease, color .15s ease, width .28s cubic-bezier(.4,0,.2,1), height .28s cubic-bezier(.4,0,.2,1); }
      #navChevron:hover { background: var(--tint-05); color: var(--text); }
      /* Mouse-click must not leave a stray outline box on the collapse control
         (owner, Aug 12: collapsed rail "doesn't look right"); keyboard focus
         still gets a clean accent ring. */
      #navChevron:focus { outline: none; }
      #navChevron:focus:not(:focus-visible) { outline: none; box-shadow: none; }
      #navChevron:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--accent-dim, color-mix(in srgb, var(--accent) 25%, transparent)); }
      #navChevron svg { width: 15px; height: 15px; transition: transform .3s cubic-bezier(.4,1.3,.5,1); }
      /* Reference inset search (§1.2 inset control): 292px, 11px radius. */
      .top-search {
        display: flex; align-items: center; gap: 9px;
        padding: 9px 14px; border-radius: 11px;
        background: var(--tint-03); border: 1px solid var(--tint-08);
        /* A floor, so the clock, live dot and account chip can never squeeze
           the search to nothing (it measured 0px wide at 1024). */
        width: 100%; min-width: 120px; transition: border-color .15s;
      }
      .top-search:focus-within { border-color: color-mix(in srgb, var(--accent) 45%, transparent); }
      .top-search svg { color: var(--text-faint); flex: none; }
      .top-search input {
        flex: 1; min-width: 0; background: transparent; border: none; outline: none;
        font-family: 'Poppins', 'Inter', sans-serif; font-size: 12.5px; color: var(--text);
      }
      .top-search input::placeholder { color: var(--text-faint); }
      .top-kbd {
        flex: none; font-family: 'Poppins', 'Inter', sans-serif; font-size: 9px; color: var(--text-faint);
        border: 1px solid var(--tint-12); border-radius: 5px; padding: 1px 5px;
      }
      /* Bare icon buttons (reference: bell / toggle have no card chrome). */
      .top-icon-btn {
        display: inline-flex; align-items: center; justify-content: center;
        width: 32px; height: 32px; border-radius: 9px; border: none;
        background: transparent; color: var(--text-soft); cursor: pointer;
        transition: color .2s, background .2s;
        flex: none;             /* a crowded bar squeezed these to 16px */
      }
      .top-icon-btn:hover { color: var(--text); background: var(--tint-05); }
      /* Bell count — reference: red +N pill riding the icon's top-right. */
      .notif-count {
        position: absolute; top: -2px; right: -4px;
        font-family: 'Poppins', 'Inter', sans-serif; font-size: 8px; font-weight: 500;
        color: var(--text-on-fill); background: var(--urgent); border-radius: 9px; padding: 0 4px;
        line-height: 12px; min-width: 12px; text-align: center;
      }
      /* Kanban */
      .kanban-col { background: var(--bg-raised); border: 1px solid var(--stroke); border-radius: 12px; align-self: flex-start; display:flex; flex-direction:column; contain: layout style; flex: 1 1 18rem; min-width: 18rem; }
      .kanban-col-header { padding: 9px 12px; border-bottom: 1px solid var(--stroke); display:flex; align-items:center; gap:8px; position: sticky; top: 0; z-index: 2; background: var(--bg-raised); border-radius: 12px 12px 0 0; }
      .kanban-col-title { font-size: 11px; font-weight: 700; letter-spacing: .06em; color: var(--text); }
      .kanban-col-count { margin-left:auto; font-size: 11px; font-family: 'JetBrains Mono', monospace; color: var(--text-muted); background: var(--bg-card); padding: 1px 7px; border-radius: 999px; }
      .kanban-col-body { padding: 8px; display:flex; flex-direction:column; gap:7px; }
      /* Embedded profile boards (user/site 360): a fixed-height widget where each
         column scrolls internally instead of growing the whole page. */
      .kanban-embedded { height: 60vh; min-height: 360px; }
      .kanban-embedded .kanban-col { align-self: stretch; height: 100%; }
      .kanban-embedded .kanban-col-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
      /* Board fills the viewport (horizontal scrollbar stays reachable) and scrolls VERTICALLY as one unit; columns size to content and move together. */
      /* No scroll-behavior: smooth here (owner, 2026-10-13): it made the lanes
         glide behind the scrollbar while it was dragged. Lane jumps
         (scrollToLane) ask for smooth themselves. */
      #kanban { overflow-x: auto; overflow-y: hidden; align-items: stretch; height: calc(100dvh - var(--topbar-h) - 88px); }
      /* Grab the board to scroll it (board-pan.js): empty board space shows a
         grab hand; cards keep their pointer and their own drag. */
      body:not([data-board-view="list"]):not([data-board-view="calendar"]) #kanban, #complianceKanban.flex { cursor: grab; }
      #kanban.is-panning, #kanban.is-panning *, #complianceKanban.is-panning, #complianceKanban.is-panning * { cursor: grabbing !important; user-select: none; }
      /* List reuses the #kanban container. The kanban's fixed viewport height
         + inner scrollbar made the List view feel broken ("can't scroll down,
         bottom cut off") — a table should just grow the page and scroll with
         the window like every other list view. */
      body[data-board-view="list"] #kanban { height: auto !important; overflow: visible; }
      /* Calendar keeps sizeKanban's viewport height (was height:auto !important
         from the content-sized era, which beat the inline height and collapsed
         day view to its 40px-row floors). Grids scroll internally. */
      body[data-board-view="calendar"] #kanban { overflow: visible; }
      /* Each column scrolls on its own with a pinned header (Trello/Linear feel)
         so a tall column never drags the short ones into a giant empty scroll.
         The Compliance tab's register board is the same board (owner,
         2026-10-01): sizeComplianceKanban() already gives it the viewport
         height, and these rules give each lane its own scroll the same way.
         Scoped to .flex — set only in the lanes presentation — so the List and
         Calendar views still grow the page and scroll with the window. */
      #kanban > .kanban-col, #complianceKanban.flex > .kanban-col { align-self: stretch; height: 100%; }
      #kanban > .kanban-col > .kanban-col-body, #complianceKanban.flex > .kanban-col > .kanban-col-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }
      #complianceKanban.flex { overflow-y: hidden; }
      /* Drag the handle below the board to resize the whole board's height. */
      .board-resizer { height: 16px; margin-top: 4px; display: flex; align-items: center; justify-content: center; cursor: row-resize; touch-action: none; border-radius: 8px; }
      .board-resizer::before { content: ''; width: 48px; height: 4px; border-radius: 999px; background: var(--stroke); transition: background .12s ease, width .12s ease; }
      .board-resizer:hover::before, .board-resizer.is-resizing::before { background: var(--accent); width: 72px; }
      body.board-resizing { cursor: row-resize !important; user-select: none; }
      /* Skeleton shimmer for first-load loading states. */
      @keyframes sk-shimmer { 0% { background-position: -480px 0; } 100% { background-position: 480px 0; } }
      .sk-bar { display:inline-block; height:11px; max-width:100%; border-radius:6px; background:linear-gradient(90deg, color-mix(in srgb, var(--text-muted) 10%, transparent) 25%, color-mix(in srgb, var(--text-muted) 24%, transparent) 37%, color-mix(in srgb, var(--text-muted) 10%, transparent) 63%); background-size:480px 100%; animation: sk-shimmer 1.2s ease-in-out infinite; }
      #kanban::-webkit-scrollbar { height: 12px; }
      #kanban::-webkit-scrollbar-track { background: var(--tint-03); border-radius: 6px; }
      #kanban::-webkit-scrollbar-thumb { background: var(--tint-22); border-radius: 6px; border: 3px solid transparent; background-clip: padding-box; }
      #kanban::-webkit-scrollbar-thumb:hover { background: var(--tint-32); background-clip: padding-box; }
      /* Collapsible left navigation (desktop): a 74px icon rail, 1:1 with the
         stellar-v3 reference's own rail geometry (46x46 / 14px-radius icon
         buttons, teal active-glow bar, teal quick-add button).
         Collapse/expand is driven by an explicit CHEVRON toggle on the RIGHT of
         the sidebar header (#navChevron -> toggleDesktopNav, persisted), instead
         of the old hover-to-peek. Because it's a real persisted state now (not a
         transient hover), the board (#mainCol)
         genuinely reflows to reclaim the space - 240px expanded, 74px collapsed.
         Labels still VISIBLY SLIDE (max-width + opacity transition, animatable -
         NOT display:none, which can't be transitioned); the transition now
         fires on the class toggle (the chevron click) rather than on hover.
         Mobile's separate off-canvas toggle (closeSidebar/-translate-x-full)
         is untouched by any of this - different mechanism, gated to < md. */
      #sidebar { transition: width .28s cubic-bezier(.4,0,.2,1), padding .28s cubic-bezier(.4,0,.2,1); }
      /* The main column starts BELOW the fixed top bar (WO-02007); the rail
         still offsets it on the left exactly as before. */
      #mainCol { transition: margin-left .28s cubic-bezier(.4,0,.2,1); padding-top: var(--topbar-h); }
      /* Global "New Work Order" create button - ALWAYS present in the left rail
         (reference: the teal + below the brand). Expanded = full-width teal
         button with label; collapsed = 46x46 teal + square (rules below). This
         is now the single, always-available create affordance; the top-bar
         duplicate is retired. */
      .rail-quick-add { display: flex; align-items: center; gap: 10px; flex-shrink: 0; margin: 4px 14px 18px; height: 46px; padding: 0 16px; border: none; border-radius: 13px; background: var(--accent); color: var(--text-ink); font-family: 'Poppins','Inter',sans-serif; font-size: 13px; font-weight: 600; line-height: 1; cursor: pointer; white-space: nowrap; box-shadow: var(--glow-accent); transition: background .15s, padding .28s cubic-bezier(.4,0,.2,1), gap .28s cubic-bezier(.4,0,.2,1), border-radius .28s cubic-bezier(.4,0,.2,1); }
      .rail-quick-add:hover { background: var(--accent-hover); }
      /* Split-create geometry (moved out of inline styles so the collapsed
         rules below can actually win the cascade): the primary spans the rail
         with clearance for the chevron; the chevron is the small bordered
         square riding its right edge. */
      #createSplit .rail-quick-add { width: calc(100% - 28px); padding-right: 44px; }
      #createMoreBtn { display: flex; }
      /* The rail button is the ONE create affordance, collapsed or not: it is
         present on every view, where the board toolbar's copy only exists on
         the Board. Hiding this on collapse (Aug 8) left every other view with
         no way to create — restored, and the toolbar duplicate retired below. */
      .rail-quick-add svg { width: 18px; height: 18px; flex-shrink: 0; }
      .rail-quick-add .sidebar-label { color: var(--text-ink); }
      /* Floating rail (reference): the sidebar is transparent on desktop so it
         sits on the shared app field instead of reading as its own dark panel
         with a hard left edge - just a hairline right separator like the mock's
         `border-right:1px solid var(--tint-045)`. Mobile keeps an opaque
         bg since there it's an off-canvas overlay ABOVE the content. */
      /* WO-02007: the rail starts BELOW the fixed top bar (its brand row now
         lives up there) and gets a touch of breathing room before the create
         button; the mobile sheet slides in under the bar the same way. */
      #sidebar { background: var(--bg-canvas); top: var(--topbar-h); padding-top: 10px; }
      #sidebarBackdrop { top: var(--topbar-h); }
      /* Rail scrollbar - spec §9: 6px, thumb var(--tint-16), no track.
         (The default 8px slate thumb read as a fat white bar inside the rail.) */
      #sidebar nav { scrollbar-width: thin; scrollbar-color: var(--tint-16) transparent; }
      #sidebar nav::-webkit-scrollbar { width: 6px; }
      #sidebar nav::-webkit-scrollbar-track { background: transparent; }
      #sidebar nav::-webkit-scrollbar-thumb { background: var(--scrollbar-thumb); border-radius: 3px; }
      #sidebar nav::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-thumb-hover); }
      /* Brand mark shrinks in step when the rail collapses (the tile rides the
         header segment, so it tracks the 232 -> 74px narrowing). */
      #topBrand #wsSwitcherBtn > div:first-child,
      #topBrand #wsSwitcherBtn > div:first-child img {
        transition: width .32s cubic-bezier(.16,1,.3,1), height .32s cubic-bezier(.16,1,.3,1), border-radius .32s ease;
      }
      @media (min-width: 768px) {
        #sidebar { background: transparent; border-right: 1px solid var(--tint-045); }
        /* The brand segment continues the rail's hairline up through the header
           (the reference divider after the switcher). */
        #topBrand { border-right: 1px solid var(--tint-045); }
        body.nav-collapsed #sidebar { width: 74px; }
        body.nav-collapsed #mainCol { margin-left: 74px; }
        /* Collapsed: the header's brand segment narrows to the rail width; the
           tile shrinks to 36 and the chevron to 26 so both fit 74px
           (5 + 36 + 2 + 26 + 5), and the icon flips to ›. */
        body.nav-collapsed #topBrand { width: 74px; padding: 0 5px; gap: 2px; }
        body.nav-collapsed #topBrand #wsSwitcherBtn { gap: 0; flex: none; padding: 0; }
        body.nav-collapsed #navChevron { width: 26px; height: 26px; margin-left: auto; }
        body.nav-collapsed #navChevron svg { transform: rotate(180deg); }
        body.nav-collapsed #topBrand #wsSwitcherBtn > div:first-child { width: 36px; height: 36px; border-radius: 11px; }
        body.nav-collapsed #topBrand #wsSwitcherBtn > div:first-child img { width: 36px; height: 36px; }
        body.nav-collapsed .ws-attn-dot { left: 29px; }
        /* The switcher menu lives inside the 74px segment when collapsed:
           left-3/right-3 would leave it ~50px wide with every name truncated
           (review, #346). Pin a real width instead of the segment's. */
        body.nav-collapsed #wsMenu { right: auto; width: 300px; }
        body.nav-collapsed #createMoreBtn, body.nav-collapsed #createMenu { display: none; }

        /* Collapsed: the create button becomes the reference's 46x46 teal +
           square (label hidden by the .sidebar-label collapse rule). */
        /* Collapsed: icon centered via symmetric padding (base margin:0 14px
           already yields a 46px-wide button at a 74px rail) — no justify-content
           flip or fixed-width snap, so it shrinks smoothly with the rail. */
        body.nav-collapsed #sidebar .rail-quick-add { padding: 0; gap: 0; border-radius: 14px; justify-content: center; }
        body.nav-collapsed #sidebar .rail-quick-add svg { width: 24px; height: 24px; }

        /* Label reveal: finite max-width + opacity on BOTH endpoints so the
           chevron toggle animates in both directions (0 <-> 200px). Applied
           only in the collapsible desktop context; kept generous so no label
           truncates when expanded. The workspace name block rides the header's
           brand segment now (WO-02007), so it collapses with the same rule. */
        #sidebar .sidebar-label,
        #topBrand #wsSwitcherBtn > .min-w-0,
        #sidebar #userPill > *:not(#userAvatar) {
          max-width: 200px; overflow: hidden;
          transition: max-width .32s cubic-bezier(.16,1,.3,1), opacity .16s ease, padding .32s ease, margin .32s ease;
        }
        /* margin:0 kills the "SOON" badge's ml-auto (Reports link) when collapsed
           - otherwise it eats the flex free space and shoves that icon off-center. */
        body.nav-collapsed #sidebar .sidebar-label,
        body.nav-collapsed #topBrand #wsSwitcherBtn > .min-w-0 { max-width: 0; opacity: 0; margin: 0; }
        body.nav-collapsed #sidebar .nav-badge { display: none; }
        /* Fixed 46px pill centered in the 74px rail with a centered icon (the
           reference's nav-link geometry), teal active glow bar + tint. The
           nav's scrollbar is hidden here so its reserved width can't shove the
           pill/icon left (the off-center highlight). Scroll still works via
           wheel/trackpad. margin-left/right (not shorthand) keeps space-y gaps. */
        body.nav-collapsed #sidebar nav { scrollbar-width: none; }
        body.nav-collapsed #sidebar nav::-webkit-scrollbar { width: 0; height: 0; }
        body.nav-collapsed #sidebar .sidebar-link { height: 46px; width: 46px; margin-left: auto; margin-right: auto; padding: 0; gap: 0; border-radius: 14px; white-space: nowrap; position: relative; justify-content: center; }
        body.nav-collapsed #sidebar .sidebar-link.active { background: color-mix(in srgb, var(--accent-hover) 13%, transparent); color: var(--accent); box-shadow: none; }
        body.nav-collapsed #sidebar .sidebar-link.active::before {
          content: ''; position: absolute; left: -1px; top: 50%; transform: translateY(-50%);
          width: 3px; height: 22px; border-radius: 3px; background: var(--accent); box-shadow: var(--glow-rail);
        }

        /* Footer user pill collapses to just the centered avatar. */
        body.nav-collapsed #sidebar #userPill { gap: 0; justify-content: center; }
        body.nav-collapsed #sidebar #userPill > *:not(#userAvatar) { max-width: 0; opacity: 0; padding: 0; margin: 0; }
      }
      @media (min-width: 768px) and (prefers-reduced-motion: reduce) {
        #sidebar, #mainCol, #topBrand, #topBrand #wsSwitcherBtn,
        #navChevron, #navChevron svg, #topBrand #wsSwitcherBtn > .min-w-0,
        #sidebar .sidebar-label, #sidebar #userPill > *:not(#userAvatar) { transition-duration: .01s; }
      }
      /* The old per-icon INLINE tooltip spans (.rail-tip/.rail-arrow) stay
         hidden - their absolute left:calc(100%+…) boxes were extending the
         scroll area and forcing a phantom horizontal scrollbar. Their TEXT is
         still read by initRailTooltips() to feed the shared #railTip below. */
      .rail-tip, .rail-arrow { display: none; }
      /* Collapsed-rail hover tooltip: ONE shared fixed-position pill (JS-driven,
         initRailTooltips) so the rail's overflow:hidden can't clip it and it
         never widens the scroll area. Fades + slides in on icon hover, collapsed
         only (expanded already shows labels inline). */
      #railTip {
        position: fixed; z-index: 60; pointer-events: none;
        background: var(--accent); color: var(--text-ink);
        font-family: 'Poppins', 'Inter', sans-serif; font-size: 12px; font-weight: 500;
        padding: 6px 11px; border-radius: 8px; white-space: nowrap;
        box-shadow: 0 8px 20px var(--shade-40); transform-origin: left center;
        opacity: 0; transform: translateY(-50%) translateX(-6px) scale(.85);
        transition: opacity .2s ease, transform .36s cubic-bezier(.2,1.5,.4,1);
      }
      #railTip::before {
        content: ''; position: absolute; left: -5px; top: 50%; transform: translateY(-50%);
        border: 5px solid transparent; border-right-color: var(--accent);
      }
      #railTip.show { opacity: 1; transform: translateY(-50%) translateX(0) scale(1); }
      @media (prefers-reduced-motion: reduce) { #railTip { transition: opacity .12s ease; } }
      /* Smoother collapse: hint width/margin as will-change and contain the
         active content's layout for the ~.34s toggle so the board's reflow
         doesn't stutter the slide. Cleared as soon as the animation ends. */
      body.nav-animating #sidebar { will-change: width; }
      body.nav-animating #mainCol { will-change: margin-left; contain: layout; }
      /* Reference card: 14px radius, 13x14 padding, spacing via explicit 9px
         margins in the markup (head/wo/chips) — flex gap stays 0 so the values
         match the reference's margin rhythm 1:1 instead of double-spacing. */
      .task-card { background: var(--bg-card); border: 1px solid var(--stroke); border-radius: 12px; padding: 9px 11px; cursor: pointer; transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease; display:flex; flex-direction:column; gap:0; contain: layout style; }
      .task-card:hover { border-color: var(--stroke-2); transform: translateY(-1px); box-shadow: 0 8px 24px -12px var(--shade-50); }
      .task-card.overdue { border-color: color-mix(in srgb, var(--urgent) 55%, transparent); }
      .task-card-head { display:flex; align-items:center; gap:7px; font-size: 11px; margin-bottom: 5px; }
      .site-code { font-family: 'JetBrains Mono', monospace; font-weight: 600; color: var(--status-complete); }
      .task-card-title { font-size: 13px; font-weight: 500; color: var(--text); line-height: 1.3; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
      .vendor-pill { display:inline-flex; align-items:center; gap:6px; padding: 3px 9px; border-radius: 999px; font-size: 11px; font-weight: 600; }
      .vendor-pill .dot { width:6px; height:6px; border-radius:999px; flex-shrink:0; }
      .overdue-chip { color: var(--urgent); font-weight: 600; font-size: 11px; }
      /* Work-order drawer: slides in from the right (owner preference over the
         centered-modal experiment - reverted Jul 8). Shell + sections match
         stellar-v3 (same surface as #userManageDrawer / .wo-modal-*). */
      .drawer { transition: transform .3s cubic-bezier(.32,.72,0,1); transform: translateX(100%); }
      .drawer.open { transform: translateX(0); }
      #drawer {
        background: var(--bg-shell);
        border-left: 1px solid var(--tint-08);
        color: var(--text);
        font-family: 'Poppins', 'Inter', sans-serif;
        box-shadow: var(--shadow-drawer);
      }
      #drawerBackdrop { background: var(--backdrop-soft); }
      #drawer .drawer-head {
        padding: 20px 20px 16px;
        border-bottom: 1px solid var(--tint-08);
        background: var(--bg-head);
        position: sticky; top: 0; z-index: 2;
      }
      #drawer .drawer-block,
      .wo-modal-rail .drawer-block {
        margin: 12px 16px 0;
        padding: 14px;
        border: 1px solid var(--tint-06);
        border-radius: 16px;
        background: var(--bg-panel);
        box-shadow: var(--shadow-card);
      }
      .wo-modal-rail .drawer-block { margin: 12px 0 0; }
      #drawer .drawer-block:last-child { margin-bottom: 20px; }
      #drawer .drawer-label,
      .drawer-block .drawer-label,
      .wo-modal-rail .drawer-label,
      .cm-card .drawer-label {
        font-family: 'Poppins', sans-serif;
        font-size: 9px; letter-spacing: 1.3px; text-transform: uppercase;
        color: var(--text-muted); margin-bottom: 11px; font-weight: 600;
      }
      /* Read-only detail as rows, not cells. The location card used to be a
         2-col grid, which gave an address, a creation stamp and a KPI the same
         weight and read as four unrelated facts. Rows (label left, value
         right, hairline between) let the whole card scan as one list. Applied
         to the children so the KPI/equipment cells keep rendering their own
         markup unchanged. Both presentations share these — the modal rail is a
         narrower column, so it gets the same treatment rather than its own. */
      #drawer .drawer-rows > div,
      .wo-modal-rail .drawer-rows > div,
      .cm-card .drawer-rows > div {
        display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
        border-top: 1px solid var(--tint-06); margin-top: 12px; padding-top: 12px;
      }
      #drawer .drawer-rows > div > .drawer-label,
      .wo-modal-rail .drawer-rows > div > .drawer-label,
      .cm-card .drawer-rows > div > .drawer-label { margin-bottom: 0; flex: 0 0 auto; }
      #drawer .drawer-rows > div > :last-child,
      .wo-modal-rail .drawer-rows > div > :last-child,
      .cm-card .drawer-rows > div > :last-child { text-align: right; min-width: 0; }
      /* A section that follows another section inside one card, instead of
         bringing its own card chrome. */
      #drawer .drawer-section,
      .wo-modal-rail .drawer-section {
        border-top: 1px solid var(--tint-06); margin-top: 14px; padding-top: 14px;
      }
      /* Whichever section leads a card must not draw a divider against the
         card's own top edge. Which one that is depends on the ticket: the
         schedule renders nothing on a closed ticket with no recurrence, so the
         attachments take the lead instead. */
      #drawer .drawer-block > .drawer-section:first-child,
      .wo-modal-rail .wo-modal-section > .drawer-section:first-child {
        border-top: none; margin-top: 0; padding-top: 0;
      }
      /* Primary actions pinned to the bottom of the panel. They used to sit in
         the scroll flow at roughly the 1000px mark, under a thread that is
         routinely 900px+, so on a busy ticket you scrolled past the entire
         conversation to reach the button that moves the job forward. Sticky
         keeps them reachable without taking them out of the DOM order (so
         tab order and the inline onclick= handlers are unchanged). */
      #drawer .drawer-actions {
        position: sticky; bottom: 0; z-index: 3;
        margin: 12px 0 0; padding: 12px 16px 16px;
        background: var(--bg-shell);
        border-top: 1px solid var(--tint-08);
        box-shadow: 0 -10px 22px -14px var(--shade-50);
      }
      #drawer .drawer-actions .drawer-block { margin: 0; }
      /* "Show earlier" control for the conversation. The thread is by far the
         tallest thing in the panel and grows without limit; collapsing it
         behind this keeps the panel scannable on the tickets with the most
         history, which are the ones people actually struggle to read.
         Deliberately NOT scoped to #drawer: the permit's thread reuses this
         class, and while the rule was drawer-only the permit's copy silently
         fell back to a default button — pale text, shrunk against the left
         edge instead of centred in the column. */
      .wo-thread-earlier {
        display: block; width: 100%; text-align: center; cursor: pointer;
        font-family: 'Poppins', 'Inter', sans-serif; font-size: 11px; font-weight: 500;
        color: var(--accent); background: none; border: none;
        padding: 2px 0 10px;
      }
      .wo-thread-earlier:hover { text-decoration: underline; }
      #drawer .drawer-chip {
        display: inline-flex; align-items: center; gap: 5px;
        font-family: 'Poppins', sans-serif; font-size: 11px; font-weight: 500;
        border-radius: 6px; padding: 3px 8px;
      }
      #drawer .drawer-chip.muted { color: var(--text-secondary); background: var(--tint-06); border: 1px solid var(--tint-12); }
      #drawer .drawer-chip.code { color: var(--text-ink); background: var(--green); border: none; }
      #drawer .drawer-alert {
        border-radius: 12px; padding: 12px 14px; margin-top: 14px;
        border: 1px solid var(--tint-10);
      }
      #drawer .drawer-alert.amber { background: color-mix(in srgb, var(--amber) 10%, transparent); border-color: color-mix(in srgb, var(--amber) 28%, transparent); color: var(--amber-pale); }
      #drawer .drawer-alert.purple { background: color-mix(in srgb, var(--violet) 10%, transparent); border-color: color-mix(in srgb, var(--violet) 28%, transparent); color: var(--violet); }
      #drawer .drawer-close {
        width: 38px; height: 38px; border-radius: 10px; flex-shrink: 0;
        background: var(--tint-04); border: 1px solid var(--tint-12);
        display: flex; align-items: center; justify-content: center;
        color: var(--text-secondary); cursor: pointer; transition: background .12s, border-color .12s, color .12s;
      }
      #drawer .drawer-close:hover { background: color-mix(in srgb, var(--urgent) 14%, transparent); border-color: color-mix(in srgb, var(--urgent) 40%, transparent); color: var(--urgent-pale); }
      #drawer ::-webkit-scrollbar { width: 6px; }
      #drawer ::-webkit-scrollbar-thumb { background: var(--scrollbar-thumb); border-radius: 3px; }
      .scrollbar-thin::-webkit-scrollbar { width:6px; }
      /* Liveness. The dot is bound to the REAL channel status via
         setRealtimeState() (core.js) — it used to be a decorative ping with no
         connection to the socket, so a dropped channel and a healthy one looked
         identical, and "the board stopped updating" was indistinguishable from
         "the board is fine". Tokens only, so light mode works. */
      .live-dot { position:relative; width:8px; height:8px; border-radius:999px; background:var(--accent); flex:none; }
      .live-dot::after { content:''; position:absolute; inset:0; border-radius:999px; background:var(--accent); opacity:.4; animation: ping 1.6s cubic-bezier(0,0,.2,1) infinite; }
      /* Reconnecting: amber, slower ping — visibly alive, visibly not healthy. */
      .live-dot.is-retry { background: var(--amber); }
      .live-dot.is-retry::after { background: var(--amber); animation-duration: 2.6s; }
      /* Socket down, timer still running: grey and STILL. The absence of motion
         is the signal — a ping here would claim a connection we do not have. */
      .live-dot.is-off { background: var(--text-faint); }
      .live-dot.is-off::after { display: none; }
      @media (prefers-reduced-motion: reduce) { .live-dot::after { animation: none; opacity: 0; } }
      @keyframes ping { 0% { transform: scale(1); opacity:.4; } 80%, 100% { transform: scale(2.6); opacity:0; } }
      .spark path { stroke: var(--accent); stroke-width: 1.5; fill: none; }
      .spark-fill path { fill: color-mix(in srgb, var(--accent) 12%, transparent); }
      [data-view] { display:none; }
      [data-view].active { display:block; }
      input, select, button, textarea { font-family: inherit; color-scheme: inherit; }
      /* Inset controls are 11px radius per spec §3 (search/selects/inputs). */
      .input { width:100%; padding:.55rem .75rem; background: var(--bg-card); border: 1px solid var(--stroke); border-radius: 11px; font-size:.875rem; outline:none; color: var(--text); transition: border-color .15s, box-shadow .15s; }
      .input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-dim); }
      .input::placeholder { color: var(--text-muted); }
      ::selection { background: var(--selection); }
      ::-moz-selection { background: var(--selection); }
      /* Mention accent on feed hosts — info blue tuned for dark panels. */
      #notifList .text-sky-400, #overviewActivity .text-sky-400, #activityFull .text-sky-400 { color: var(--accent-info-light); }
      /* The bell dropdown is a dark v3 surface, but its rows come from the same
         activityRowHTML template the (white) Activity view uses — remap the
         light-theme ink utilities to dark equivalents inside the panel only. */
      #notifList .text-ink-900 { color:var(--text); }
      #notifList .text-ink-600, #notifList .text-ink-700 { color:var(--text-soft); }
      #notifList .text-ink-400 { color:var(--text-faint); }
      #notifList .bg-ink-50 { background:var(--tint-04); }
      #notifList .hover\:bg-ink-50:hover { background:var(--tint-05); }
      #notifList .bg-ink-100 { background:var(--tint-08); }
      #notifList .hover\:bg-ink-200:hover { background:var(--tint-12); }
      #notifList .border-ink-100 { border-color:var(--tint-08); }
      #notifList .bg-emerald-50 { background:color-mix(in srgb, var(--green) 12%, transparent); }
      #notifList .hover\:bg-emerald-100:hover { background:color-mix(in srgb, var(--green) 20%, transparent); }
      #notifList .text-emerald-700 { color:var(--green); }
      /* CTA buttons are 10px radius per spec §3 (reference New Work Order). */
      .btn { display:inline-flex; align-items:center; gap:.4rem; padding:.625rem 1.05rem; font-size:.875rem; font-weight:500; font-family:'Poppins','Inter',sans-serif; border-radius:10px; cursor:pointer; transition: all .12s; border:1px solid transparent; white-space:nowrap; min-height:2.25rem; }
      .btn-primary { background: var(--accent); color: var(--text-ink); }
      .btn-primary:hover { background: var(--accent-hover); }
      .btn-ghost { background: var(--bg-card); color: var(--text); border-color: var(--stroke); }
      .btn-ghost:hover { background: var(--bg-card-2); border-color: var(--stroke-2); }
      .btn-success { background: var(--green); color: var(--text-ink); }
      .btn-success:hover { background: var(--green); }
      .btn-danger { background: transparent; color:var(--urgent-pale); border-color: color-mix(in srgb, var(--urgent) 40%, transparent); }
      .btn-danger:hover { background: color-mix(in srgb, var(--urgent) 12%, transparent); }
      /* Reference chip language: 10px-radius inset (not a full pill). */
      .chip { display:inline-flex; align-items:center; gap:7px; padding: 7px 13px; border-radius: 10px; font-family:'Poppins','Inter',sans-serif; font-size: 12px; font-weight: 400; cursor:pointer; transition: all .12s; color: var(--text-soft); border: 1px solid var(--tint-08); background: var(--tint-03); }
      .chip:hover { color: var(--text); border-color: var(--tint-14); }
      .chip.active { color: var(--text); font-weight: 500; background: var(--bg-well); border-color: var(--tint-14); }
      .stat-card { background: var(--bg-card); border:1px solid var(--stroke); border-radius:16px; padding:14px; transition: border-color .15s; }
      .stat-card:hover { border-color: var(--stroke-2); }
      .kpi-tile { position: relative; overflow: hidden; background: var(--bg-card); border:1px solid var(--stroke); border-radius: 14px; padding: 14px; }
      .kpi-tile::before { content:''; position:absolute; top:0; left:0; right:0; height:2px; background: linear-gradient(90deg, var(--accent), transparent 70%); opacity:.65; }
      .kpi-tile .num { font-family: 'JetBrains Mono', monospace; font-size: 27px; font-weight: 700; line-height: 1; color: var(--text); letter-spacing: -.01em; font-variant-numeric: tabular-nums; }
      .kpi-tile .lbl { font-size: 10px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--text-soft); margin-top: 7px; }
      .kpi-tile .kpi-ic { position: absolute; top: 11px; right: 11px; opacity: .5; line-height: 0; pointer-events: none; }
      table { width:100%; border-collapse: collapse; }
      th { text-align:left; font-size:.7rem; font-weight:600; text-transform:uppercase; letter-spacing:.04em; color: var(--text-muted); padding:.55rem .75rem; border-bottom:1px solid var(--stroke); }
      td { font-size:.875rem; padding:.7rem .75rem; border-bottom:1px solid var(--stroke); color: var(--text); }
      tr:hover td { background: var(--tint-02); }
      .anim-fadein { animation: fadein .25s ease-out; }
      @keyframes fadein { from { opacity:0; transform: translateY(4px); } to { opacity:1; transform:none; } }
      /* Gooey sliding tab indicator (stellar-v3 reference). A segmented group
         wraps its buttons with position:relative + one .tab-blob-goo layer
         BEHIND them (z-index:0, buttons at z-index:10); JS (positionTabBlob in
         board.js) measures the active button and slides+resizes .tab-blob to
         match. The shared #ccGoo filter melts the blob's edges into the next
         position instead of a hard-edged box sliding across. */
      .tab-blob-goo { position:absolute; inset:0; filter:url(#ccGoo); pointer-events:none; z-index:0; }
      .tab-blob { position:absolute; top:0; left:0; width:0; height:0; border-radius:999px; background: var(--bg-card-2); transition: transform 480ms cubic-bezier(.34,1.56,.64,1), width 480ms cubic-bezier(.34,1.56,.64,1); }
      @media (prefers-reduced-motion: reduce) { .tab-blob { transition: none; } }
      /* Compat: any leftover light-mode Tailwind utilities get dark equivalents
         so we don't have to touch every existing class in the markup. */
      .bg-white         { background: var(--bg-card) !important; }
      .bg-ink-50        { background: var(--bg-raised) !important; }
      .bg-ink-100       { background: var(--bg-card-2) !important; }
      .border-ink-100   { border-color: var(--stroke) !important; }
      .border-ink-50    { border-color: var(--stroke) !important; }
      .text-ink-900     { color: var(--text) !important; }
      .text-ink-800     { color: var(--text) !important; }
      .text-ink-700     { color: var(--text) !important; }
      .text-ink-600     { color: var(--text-soft) !important; }
      .text-ink-500     { color: var(--text-muted) !important; }
      .text-ink-400     { color: var(--text-faint) !important; }
      .text-ink-300     { color: var(--text-faint) !important; }
      .divide-ink-50 > * + * { border-color: var(--stroke) !important; }
      /* DM chat surfaces reuse light-mode utilities the block above missed:
         row hover, the thread backdrop wash, and input borders were still
         light-on-dark (names unreadable, grey chat panel). */
      .hover\:bg-ink-50:hover { background: var(--bg-raised) !important; }
      .bg-ink-50\/40    { background: var(--tint-03) !important; }
      .border-ink-200   { border-color: var(--stroke-2) !important; }

      /* The ink ramp is used BOTH ways round in this codebase: ink-50/100 as
         light surfaces (remapped above), and ink-700..950 as DARK chrome.
         The dark half was never remapped because on a dark canvas it already
         looked right — on a light canvas those are near-black boxes, so they
         need the same treatment. Tailwind compiles each opacity modifier to
         its own class, so every variant actually used in the markup is listed. */
      .bg-ink-950       { background: var(--bg-canvas) !important; }
      .bg-ink-900       { background: var(--bg-card) !important; }
      .bg-ink-800       { background: var(--bg-card-2) !important; }
      .bg-ink-700       { background: var(--bg-elev) !important; }
      .hover\:bg-ink-800:hover { background: var(--bg-card-2) !important; }
      .bg-ink-950\/50   { background: var(--tint-05) !important; }
      .bg-ink-950\/40   { background: var(--tint-04) !important; }
      .bg-ink-900\/60   { background: var(--tint-06) !important; }
      .bg-ink-800\/80   { background: var(--bg-card-2) !important; }
      .bg-ink-800\/70   { background: var(--tint-07) !important; }
      .bg-ink-800\/60   { background: var(--tint-06) !important; }
      .bg-ink-800\/50   { background: var(--tint-05) !important; }
      .bg-ink-800\/40   { background: var(--tint-04) !important; }
      .hover\:bg-ink-800\/60:hover { background: var(--tint-06) !important; }
      .hover\:bg-ink-800\/40:hover { background: var(--tint-04) !important; }
      .border-ink-700   { border-color: var(--stroke-2) !important; }
      .border-ink-800   { border-color: var(--stroke) !important; }
      .divide-ink-800 > * + * { border-color: var(--stroke) !important; }
      .divide-ink-800\/60 > * + * { border-color: var(--stroke) !important; }
      .text-ink-100     { color: var(--text) !important; }
      .text-ink-200     { color: var(--text-body) !important; }
      /* `hover:text-white` is always chrome brightening on hover here, so on a
         light canvas it must resolve to the DARKEST text, not white-on-white.
         Plain `text-white` is deliberately NOT remapped: it rides avatar
         gradients, brand fills and map pins that stay saturated in both themes. */
      .hover\:text-white:hover { color: var(--text) !important; }

      /* Selected segment of a segmented control (Locations list/cards,
         Compliance list/calendar/inbox). Replaces the old
         `bg-ink-900 text-white`, which became white-on-white in light mode. */
      .seg-on { background: var(--accent) !important; color: var(--text-ink) !important; }

      /* `text-white` was the console's shorthand for "primary text" back when
         every surface was dark — it is on 12 page-title <h1>s, three modal
         titles, the whole invite flow and several name rows. On a white card
         all of those vanish, which is exactly how the Invoices and Compliance
         headings went blank in light mode. So it resolves to the text ramp:
         in dark that is #eef4f6, near enough to white that nothing shifts.

         The exception is text that genuinely rides a SATURATED fill — avatar
         initials on a colour gradient, a sent DM on the brand blue. Those
         fills are identical in both themes, so their label must stay white;
         the two-class selectors below outrank the rule above and restore it. */
      .text-white { color: var(--text) !important; }
      .bg-linear-to-br.text-white,
      .bg-brand-500.text-white,
      .bg-brand-600.text-white { color: var(--text-on-fill) !important; }

      /* The pale end of the Tailwind palette (300/400) was chosen to glow on a
         dark canvas — emerald-400 on white is about 1.9:1. These are not
         decoration: they carry form errors, "Resend code", the modal eyebrows
         and the "+ Add new location" links. Point them at the status tokens,
         which are AA in both themes. (The 600/700 steps are mapped further
         up; this is the same idea for the lighter half.) */
      .text-emerald-400, .text-emerald-300 { color: var(--green) !important; }
      .hover\:text-emerald-300:hover { color: var(--green-soft) !important; }
      .text-rose-400, .text-rose-300, .text-red-400 { color: var(--urgent) !important; }
      .text-sky-400 { color: var(--accent-info) !important; }
      .text-amber-400, .text-amber-300 { color: var(--amber) !important; }
      .text-indigo-400 { color: var(--violet) !important; }

      /* Light Tailwind tints → stellar-v3 dark tints (THEME.md). Keeps
         leftover bg-*-50/100 markup readable on the dark canvas without
         rewriting every call site. */
      .bg-amber-50, .bg-amber-100 { background: color-mix(in srgb, var(--amber) 12%, transparent) !important; }
      .bg-amber-200 { background: color-mix(in srgb, var(--amber) 22%, transparent) !important; }
      .text-amber-600, .text-amber-700, .text-amber-800 { color: var(--amber-soft) !important; }
      .text-amber-900 { color: var(--amber-pale) !important; }
      .border-amber-200 { border-color: color-mix(in srgb, var(--amber) 35%, transparent) !important; }
      .bg-rose-50, .bg-rose-100, .bg-red-50, .bg-red-100 { background: color-mix(in srgb, var(--urgent) 12%, transparent) !important; }
      .text-rose-600, .text-rose-700, .text-red-600, .text-red-700 { color: var(--urgent-mid) !important; }
      .border-rose-200, .border-red-200 { border-color: color-mix(in srgb, var(--urgent) 35%, transparent) !important; }
      .bg-emerald-50, .bg-emerald-100, .bg-green-50, .bg-green-100 { background: color-mix(in srgb, var(--green) 12%, transparent) !important; }
      .text-emerald-600, .text-emerald-700, .text-green-600, .text-green-700 { color: var(--green) !important; }
      .border-emerald-200 { border-color: color-mix(in srgb, var(--green) 35%, transparent) !important; }
      .bg-sky-50, .bg-sky-100, .bg-blue-50, .bg-blue-100 { background: color-mix(in srgb, var(--accent-info) 12%, transparent) !important; }
      .text-sky-600, .text-sky-700, .text-blue-600, .text-blue-700 { color: var(--accent-info-light) !important; }
      .border-sky-200, .border-sky-300 { border-color: color-mix(in srgb, var(--accent-info) 35%, transparent) !important; }
      .bg-violet-50, .bg-violet-100, .bg-purple-50, .bg-purple-100, .bg-indigo-50, .bg-indigo-100 { background: color-mix(in srgb, var(--violet) 12%, transparent) !important; }
      .text-violet-600, .text-violet-700, .text-purple-600, .text-purple-700, .text-indigo-600, .text-indigo-700 { color: var(--violet) !important; }
      .border-violet-200, .border-purple-200, .border-indigo-200 { border-color: color-mix(in srgb, var(--violet) 35%, transparent) !important; }
      .bg-cyan-50, .bg-cyan-100 { background: color-mix(in srgb, var(--accent) 12%, transparent) !important; }
      .text-cyan-600, .text-cyan-700 { color: var(--accent) !important; }
      .bg-orange-50, .bg-orange-100 { background: color-mix(in srgb, var(--amber) 12%, transparent) !important; }
      .text-orange-600, .text-orange-700 { color: var(--amber) !important; }
      .hover\:bg-amber-100:hover { background: color-mix(in srgb, var(--amber) 20%, transparent) !important; }
      .hover\:bg-emerald-100:hover { background: color-mix(in srgb, var(--green) 20%, transparent) !important; }
      .hover\:bg-violet-100:hover { background: color-mix(in srgb, var(--violet) 20%, transparent) !important; }
      .hover\:bg-sky-50:hover { background: color-mix(in srgb, var(--accent-info) 14%, transparent) !important; }

      /* ============================================================
         Mission-control polish: HUD section markers + accent details.
      ============================================================ */
      [data-view] h1 { position: relative; padding-left: 15px; }
      [data-view] h1::before {
        content:''; position:absolute; left:0; top:50%; transform: translateY(-50%);
        width:4px; height:0.82em; border-radius:2px; background: var(--accent-info);
        box-shadow: var(--glow-info);
      }
      /* Redesigned screens render their own color-coded accent bar (.title-rule)
         next to the title block, so the global h1::before above would double it
         up — a teal bar AND a blue bar. Detect the explicit bar instead of
         listing the views: a new screen that ships a .title-rule suppresses the
         global marker on its own, and one without it keeps the global marker as
         its only bar. This was previously a hand-maintained view blocklist,
         which every new screen (Users, then Billing) had to remember to join. */
      section[data-view]:has(.title-rule) h1 { padding-left: 0; }
      section[data-view]:has(.title-rule) h1::before { display: none; }
      .kanban-col-header { background: linear-gradient(180deg, color-mix(in srgb, var(--accent) 4%, transparent), var(--bg-raised) 70%) !important; }
      .stat-card:hover { border-color: var(--accent); }
      .conn-card:hover { border-color: color-mix(in srgb, var(--accent) 40%, transparent) !important; }
      /* (The `.live-dot, .live-dot::after { background: var(--accent) !important }`
         override that used to live here is gone. It duplicated the base rule at
         §787-788 verbatim, so it changed nothing except by BEING !important —
         which would have beaten the is-retry / is-off state colours above.) */
      .cal-cell { transition: border-color .15s; }
      .cal-cell:hover { border-color: var(--stroke-2) !important; }
      .cal-chip { transition: filter .15s; }
      .cal-chip:hover { filter: brightness(1.22); }

      /* ============================================================
         App backdrop — reference-exact (spec §1.2 surface ladder).
         ONE full-bleed field behind everything: 158deg navy gradient +
         two radial washes, plus the two soft blurred glow discs from the
         reference markup (top-right mint 420px, bottom-left blue 380px).
         Views sit DIRECTLY on this field — no per-view rounded frames.
      ============================================================ */
      .aurora {
        position: fixed; inset: 0; z-index: 0; pointer-events: none;
        overflow: hidden;
        background:
          radial-gradient(120% 85% at 88% -12%, color-mix(in srgb, var(--accent-hover) 16%, transparent), transparent 46%),
          radial-gradient(90% 70% at 8% 110%, color-mix(in srgb, var(--accent-info) 10%, transparent), transparent 52%),
          linear-gradient(158deg, var(--bg-raised) 0%, var(--bg-card) 55%, var(--bg-canvas) 100%);
      }
      .aurora::before {
        content: ''; position: absolute; top: -140px; right: -80px;
        width: 420px; height: 420px; border-radius: 50%;
        background: radial-gradient(circle, color-mix(in srgb, var(--accent-hover) 22%, transparent), transparent 62%);
        filter: blur(20px);
      }
      .aurora::after {
        content: ''; position: absolute; bottom: -160px; left: 180px;
        width: 380px; height: 380px; border-radius: 50%;
        background: radial-gradient(circle, color-mix(in srgb, var(--accent-info) 12%, transparent), transparent 62%);
        filter: blur(30px);
      }
      .aurora-mid, .aurora-grain { display: none; }

      body { position: relative; }
      #authGate, #app { position: relative; z-index: 2; }
      /* The bar is fixed, so anchored scrolling must clear it (review, #346):
         scrollIntoView targets (the roles manager, permissions-matrix.js) and
         keyboard focus near the top would otherwise land UNDER the bar. */
      html { scroll-padding-top: calc(var(--topbar-h) + 8px); }
      /* Session-restore pane: while a cached session is being verified we show
         "checking session" instead of flashing the sign-in form. */
      /* During session-restore we show the full-page Stellar loader (#appLoader)
         instead of a gate card, so hide the whole gate until we know the
         outcome (bootstrap mounts the app, or showGateForm reveals the form). */
      html.session-cached #authGate { display: none; }

      .glass-card {
        background: var(--glass);
        backdrop-filter: blur(20px) saturate(160%);
        -webkit-backdrop-filter: blur(20px) saturate(160%);
        border: 1px solid var(--tint-06);
        border-radius: 18px;
        box-shadow: 0 30px 60px -30px var(--shade-55);
      }
      .glass-strong {
        background: var(--glass-strong);
        backdrop-filter: blur(18px) saturate(160%);
        -webkit-backdrop-filter: blur(18px) saturate(160%);
        border: 1px solid var(--tint-07);
      }
      .pressable { transition: transform .14s ease, box-shadow .14s ease; }
      .pressable:active { transform: scale(.97); }
      /* Approval queue tabs */
      .apq-tab { flex:1; display:flex; align-items:center; justify-content:center; gap:.5rem; padding:.8rem 1rem; font-size:.8125rem; font-weight:600; cursor:pointer; transition: all .12s; color: var(--text-soft); border-bottom: 2px solid transparent; background: transparent; border-right: 1px solid var(--stroke); }
      .apq-tab:last-child { border-right: none; }
      .apq-tab:hover { color: var(--text); background: var(--bg-card-2); }
      .apq-tab.active { color: var(--text); border-bottom-color: var(--accent); background: var(--bg-card-2); }
      .ring-focus:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--accent-dim); border-color: var(--accent); }

      /* Auth tabs */
      .auth-tab { font-size:.75rem; font-weight:600; padding:.5rem .9rem; border-radius:999px; color:var(--text-soft); cursor:pointer; transition:all .15s; }
      .auth-tab:hover { color:var(--text); }
      .auth-tab.active { background: var(--bg-card); color:var(--text); box-shadow: 0 0 0 1px var(--stroke-2); }

      /* OTP digit cells */
      .otp-row { display:flex; gap:.5rem; justify-content:center; }
      .otp-cell {
        width: 44px; height: 54px; display:flex; align-items:center; justify-content:center;
        background: var(--bg-raised); border: 1px solid var(--stroke); border-radius: 10px;
        font-family: 'JetBrains Mono', monospace; font-size: 22px; font-weight: 600; color: var(--text);
        transition: all .15s;
      }
      .otp-cell.is-active { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-dim); }
      .otp-hidden { position: absolute; left: -9999px; opacity: 0; }

      /* Modal */
      .modal-backdrop {
        position: fixed; inset: 0; z-index: 60;
        background: var(--backdrop); backdrop-filter: blur(6px);
        display:flex; align-items:flex-end; justify-content:center;
        animation: ccFadeIn .2s ease both;
      }
      @media (min-width: 768px) { .modal-backdrop { align-items: center; } }
      #confirmModal { z-index: 150; }
      /* The unit detail (equipment-detail.js) opens the edit form and its own
         move dialog — and, since 287, is itself opened from the centred
         work-order modal (the unit's cell and its Unit photos row). Every
         .modal-backdrop shares one z-index, so DOM order used to decide, and
         the detail sat earlier than the edit form: an Edit click opened a
         modal underneath it and read as nothing happening. The order is
         explicit now: the work-order modal keeps 60, the detail sits one step
         above it (61), and the two dialogs the detail opens sit one step
         above that (62). */
      #equipmentDetailModal { z-index: 61; }
      #equipmentModal { z-index: 62; }
      #equipmentMoveModal { z-index: 62; }
      /* The unit detail's widths. It is the third surface on the shared
         Panel/Modal preference (owner, 2026-10-01), so its card follows the
         compliance detail's two shapes exactly: the roomy centred Modal, and
         the .as-drawer Panel at the same 520px the compliance Panel uses. The
         widths live here, not inline in index.html, because an inline style
         beats every selector and would also flatten the Panel. */
      #equipmentDetailModal .modal-card { max-width: 760px; }
      #equipmentDetailModal.as-drawer .modal-card { max-width: 520px; }
      /* Offline (owner, 2026-10-02): a quiet pill at the top while the browser is
         offline or a write waits to send (offline.js). Above modals, below toasts. */
      .offline-banner { position: fixed; top: calc(10px + env(safe-area-inset-top, 0px)); left: 50%; transform: translateX(-50%); z-index: 90; max-width: min(92vw, 560px); padding: 8px 14px; border-radius: 999px; background: color-mix(in srgb, var(--amber) 16%, var(--bg-shell)); border: 1px solid color-mix(in srgb, var(--amber) 45%, transparent); color: var(--amber-pale); font: 500 12.5px/1.35 'Poppins','Inter',sans-serif; text-align: center; box-shadow: var(--shadow-pop); }
      .offline-banner.hidden { display: none; }
      @keyframes backdrop-in { from { opacity:0; } to { opacity:1; } }
      .modal-card {
        width: 100%; max-width: 560px;
        background: var(--bg-shell);
        border: 1px solid var(--tint-08);
        border-top-left-radius: 22px; border-top-right-radius: 22px;
        box-shadow: 0 -20px 60px -20px var(--shade-70);
        max-height: 92vh; display:flex; flex-direction:column;
        animation: ccModalIn .28s cubic-bezier(.22,.61,.36,1) both;
        color: var(--text); font-family: 'Poppins', 'Inter', sans-serif;
      }
      @media (min-width: 768px) { .modal-card { border-radius: 22px; } }
      @keyframes modal-in { from { transform: translateY(24px); opacity:.4; } to { transform: none; opacity:1; } }
      /* Panel (drawer) presentation for a modal — right-side slide-in, mirroring
         the work-order Panel/Modal preference. Toggled via .as-drawer on the
         backdrop (compliance detail). Higher specificity than .modal-card + the
         .modal-backdrop media query, so it wins without !important. */
      .modal-backdrop.as-drawer { align-items: stretch; justify-content: flex-end; }
      .as-drawer .modal-card {
        width: 100%; max-width: 460px; height: 100%; max-height: 100%;
        border-radius: 0; border-top: none; border-right: none;
        border-left: 1px solid var(--tint-08);
        box-shadow: -24px 0 60px -24px var(--shade-70);
        animation: cmDrawerIn .3s cubic-bezier(.32,.72,0,1);
      }
      @keyframes cmDrawerIn { from { transform: translateX(100%); } to { transform: none; } }
      @media (prefers-reduced-motion: reduce) { .as-drawer .modal-card { animation-duration: .01s; } }
      .modal-section-label { font-size:.7rem; font-weight:700; letter-spacing:.08em; color: var(--text-muted); text-transform: uppercase; }

      /* Pending-invite "Revoke" — verbatim from the spec. */
      .pi-revoke {
        flex:none; font-family:'Poppins','Inter',sans-serif; font-weight:500; font-size:12px;
        padding:8px 16px; border-radius:9px; cursor:pointer;
        background:color-mix(in srgb, var(--urgent-mid) 8%, transparent); border:1px solid color-mix(in srgb, var(--urgent-mid) 40%, transparent); color:var(--urgent-soft);
        transition: background .15s ease;
      }
      .pi-revoke:hover { background:color-mix(in srgb, var(--urgent-mid) 16%, transparent); }
      .pi-revoke:focus-visible { outline:none; box-shadow:0 0 0 3px color-mix(in srgb, var(--urgent-mid) 20%, transparent); }
      /* Reusable join links (272): the Users > People card, the new-link form
         and a link's panel (js/join-links.js). Tokens only. */
      .jl-row { display:flex; align-items:center; gap:12px; padding:11px 18px; }
      .jl-ico { display:inline-flex; align-items:center; justify-content:center; flex:none; width:32px; height:32px; border-radius:10px;
        background:var(--accent-dim); border:1px solid color-mix(in srgb, var(--accent) 28%, transparent); color:var(--accent-light); }
      .jl-chip { flex:none; display:inline-flex; align-items:center; font-family:'Poppins','Inter',sans-serif; font-size:10.5px; font-weight:600;
        padding:2px 9px; border-radius:999px; border:1px solid transparent; white-space:nowrap; }
      .jl-note { display:flex; gap:10px; align-items:flex-start; padding:11px 13px; border-radius:12px; border:1px solid;
        font-size:12.5px; line-height:1.55; color:var(--text-body); }
      .jl-setting { display:flex; align-items:center; gap:14px; padding:12px 14px; border-radius:12px;
        background:var(--tint-03); border:1px solid var(--tint-08); }
      .jl-list { border:1px solid var(--tint-08); border-radius:12px; overflow:hidden; }
      .jl-person { display:flex; align-items:center; gap:12px; padding:10px 13px; }
      .jl-person + .jl-person { border-top:1px solid var(--tint-05); }
      .jl-switch { position:relative; flex:none; width:40px; height:23px; border-radius:999px; cursor:pointer;
        background:var(--bg-well); border:1px solid var(--stroke-2); transition:background .18s ease, border-color .18s ease; }
      .jl-switch .jl-knob { position:absolute; top:2px; left:2px; width:17px; height:17px; border-radius:50%;
        background:var(--switch-knob); box-shadow:0 1px 3px var(--shade-32); transition:transform .18s ease; }
      .jl-switch.on { background:var(--accent); border-color:var(--accent); }
      .jl-switch.on .jl-knob { transform:translateX(17px); }
      .jl-switch:focus-visible { outline:none; box-shadow:0 0 0 3px var(--accent-dim); }
      @media (prefers-reduced-motion: reduce) {
        .jl-switch, .jl-switch .jl-knob { transition-duration:.01s; }
        #joinLinks * { animation-duration:.01s !important; animation-delay:0s !important; }
      }
      /* "Resend" sits beside Revoke and must not compete with it: same box,
         neutral until hover, mint on hover (it is the safe action of the two). */
      .pi-resend {
        flex:none; font-family:'Poppins','Inter',sans-serif; font-weight:500; font-size:12px;
        padding:8px 16px; border-radius:9px; cursor:pointer;
        background:var(--tint-03); border:1px solid var(--tint-12); color:var(--text-soft);
        transition: background .15s ease, color .15s ease, border-color .15s ease;
      }
      .pi-resend:hover { background:color-mix(in srgb, var(--accent) 10%, transparent); border-color:color-mix(in srgb, var(--accent) 40%, transparent); color:var(--accent-light); }
      .pi-resend:focus-visible { outline:none; box-shadow:0 0 0 3px color-mix(in srgb, var(--accent) 20%, transparent); }
      .pi-resend:disabled { opacity:.5; cursor:default; background:var(--tint-03); border-color:var(--tint-12); color:var(--text-soft); }

      /* ---- Shared modal chrome: close button, brand chips, location rows ----
         Used by the invite modal and the assign-locations (bulk edit) modal so
         both read as one component family. */
      .modal-x {
        width:32px; height:32px; border-radius:10px; display:flex; align-items:center; justify-content:center;
        background: var(--bg-card-2); border:1px solid var(--stroke); color: var(--text-soft); cursor:pointer;
        transition: color .15s ease, border-color .15s ease;
      }
      .modal-x:hover { color: var(--text); border-color: var(--stroke-2); }

      .brand-chip {
        font-family:'Poppins','Inter',sans-serif; font-size:11.5px;
        padding:6px 12px; border-radius:9px; cursor:pointer; white-space:nowrap;
        background:var(--tint-03); border:1px solid var(--tint-10); color:var(--text-soft);
        display:inline-flex; align-items:center; gap:7px; transition: background .16s, border-color .16s, color .16s;
      }
      .brand-chip:hover { border-color:var(--tint-20); color:var(--text-bright); }
      .brand-chip .n { font-family:'Poppins','Inter',sans-serif; font-size:10px; opacity:.7; }
      .brand-chip.on { background:color-mix(in srgb, var(--accent) 14%, transparent); border-color:color-mix(in srgb, var(--accent) 45%, transparent); color:var(--accent-pale); }

      /* ---- Location picker, verbatim from the "User Invite & Polish" spec ----
         Each location is a bordered cell that fills teal when selected; picks
         also mirror into a chip tray above the grid. Shared by the invite and
         assign-locations modals. */
      .pp-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(150px,1fr)); gap:8px; }
      .pp-cell {
        display:flex; align-items:center; gap:10px; padding:9px 11px; border-radius:10px; cursor:pointer;
        background:var(--tint-02); border:1px solid var(--tint-07);
        transition: background .16s, border-color .16s;
      }
      .pp-cell:hover { border-color:var(--tint-16); }
      .pp-cell.on { background:color-mix(in srgb, var(--accent) 10%, transparent); border-color:color-mix(in srgb, var(--accent) 42%, transparent); }
      .pp-box {
        width:17px; height:17px; border-radius:5px; flex:none;
        display:flex; align-items:center; justify-content:center;
        font-size:11px; font-weight:700; color:var(--text-ink); line-height:1;
        background:transparent; border:1px solid var(--tint-24);
        transition: background .16s, border-color .16s;
      }
      .pp-cell.on .pp-box { background:var(--accent); border-color:var(--accent); }
      .pp-code { font-family:'JetBrains Mono','Poppins',monospace; font-size:11.5px; font-weight:600; color:var(--text-muted); flex:none; transition:color .16s; }
      .pp-cell.on .pp-code { color:var(--accent); }
      .pp-name { font-family:'Poppins','Inter',sans-serif; font-size:11px; color:var(--text-dim); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }

      .pp-tray { display:flex; flex-wrap:wrap; gap:7px; padding:11px; border-radius:11px; background:color-mix(in srgb, var(--accent) 5%, transparent); border:1px solid color-mix(in srgb, var(--accent) 20%, transparent); }
      .pp-chip {
        display:inline-flex; align-items:center; gap:6px; padding:4px 9px; border-radius:7px;
        background:color-mix(in srgb, var(--accent) 14%, transparent); border:1px solid color-mix(in srgb, var(--accent) 40%, transparent);
        font-family:'Poppins','Inter',sans-serif; font-size:11px; font-weight:500; color:var(--accent-pale); cursor:pointer;
        transition: background .15s, border-color .15s, color .15s;
      }
      .pp-chip:hover { background:color-mix(in srgb, var(--urgent-mid) 16%, transparent); border-color:color-mix(in srgb, var(--urgent-mid) 45%, transparent); color:var(--urgent-soft); }

      /* Spec search field: teal hairline even at rest. */
      .pp-search {
        width:100%; box-sizing:border-box; font-family:'Poppins','Inter',sans-serif; font-size:12.5px; color:var(--text);
        background:var(--shade-28); border:1px solid color-mix(in srgb, var(--accent) 32%, transparent); border-radius:11px;
        padding:11px 13px 11px 35px; outline:none;
      }
      .pp-search::placeholder { color:var(--text-faint); }
      .pp-input {
        width:100%; box-sizing:border-box; font-family:'Poppins','Inter',sans-serif; font-size:13px; color:var(--text);
        background:var(--shade-28); border:1px solid var(--tint-12); border-radius:11px;
        padding:12px 13px; outline:none;
      }
      .pp-input:focus { border-color:color-mix(in srgb, var(--accent) 50%, transparent); }
      .pp-input::placeholder { color:var(--text-faint); }

      /* Role selector chips (invite modal) — a segmented set, not a <select>. */
      .role-chip {
        font-family:'Poppins','Inter',sans-serif; font-size:12px;
        padding:8px 14px; border-radius:9px; cursor:pointer; white-space:nowrap;
        background:var(--tint-03); border:1px solid var(--tint-10); color:var(--text-soft);
        transition: background .16s, border-color .16s, color .16s;
      }
      .role-chip:hover { border-color:var(--tint-20); color:var(--text-bright); }
      .role-chip.on { background:color-mix(in srgb, var(--accent) 14%, transparent); border-color:color-mix(in srgb, var(--accent) 50%, transparent); color:var(--accent-pale); font-weight:500; }
      .role-chip:focus-visible { outline:none; box-shadow:0 0 0 3px var(--accent-dim); }
      /* A role that can't be picked here (join links): shown, with its reason. */
      .role-chip:disabled, .role-chip:disabled:hover { cursor:not-allowed; opacity:.55; border-style:dashed; border-color:var(--tint-10); color:var(--text-faint); }
      .role-chip-why { font-size:11px; color:var(--text-faint); }

      /* AI agents (193): row actions and the show-once key. Ghost recipe from
         THEME.md §7, 44px targets on the phone layer. */
      .ag-act {
        display:inline-flex; align-items:center; justify-content:center; gap:5px;
        min-height:34px; padding:0 11px; border-radius:9px; cursor:pointer; white-space:nowrap;
        font-family:'Poppins','Inter',sans-serif; font-size:11.5px; font-weight:500; color:var(--text-secondary);
        background:var(--bg-card); border:1px solid var(--stroke-2);
        transition: background .15s, border-color .15s, color .15s;
      }
      .ag-act:hover { background:var(--bg-card-2); color:var(--text); }
      .ag-act:focus-visible { outline:none; border-color:var(--accent); box-shadow:0 0 0 3px var(--accent-dim); }
      .ag-act.danger { color:var(--urgent-pale); border-color:color-mix(in srgb, var(--urgent) 40%, transparent); }
      .ag-act.danger:hover { background:color-mix(in srgb, var(--urgent) 12%, transparent); }
      .ag-act:disabled { opacity:.5; cursor:default; }
      @media (max-width: 767px) { .ag-act { min-height:44px; min-width:44px; } }
      .ag-key {
        font-family:'JetBrains Mono',ui-monospace,monospace; font-size:12.5px; line-height:1.5; color:var(--text);
        background:var(--shade-32); border:1px solid color-mix(in srgb, var(--accent) 35%, transparent); border-radius:11px; padding:12px 14px;
        word-break:break-all; overflow-wrap:anywhere; user-select:all; -webkit-user-select:all;
      }
      .ag-field-msg { font-family:'Poppins','Inter',sans-serif; font-size:11px; line-height:1.4; margin-top:6px; min-height:14px; }

      /* Spec eyebrow: 9px / 1.2px tracking. */
      .modal-label { font-family:'Poppins','Inter',sans-serif; font-size:9px; letter-spacing:1.2px; text-transform:uppercase; color:var(--text-faint); }

      /* Spec modal footer buttons. */
      .pp-cancel { padding:11px 20px; border-radius:10px; background:var(--tint-05); border:1px solid var(--tint-12); font-family:'Poppins','Inter',sans-serif; font-weight:500; font-size:12.5px; color:var(--text-bright); cursor:pointer; transition:background .15s; }
      .pp-cancel:hover { background:var(--tint-09); }
      .pp-cta { display:inline-flex; align-items:center; gap:7px; padding:11px 20px; border-radius:10px; background:var(--accent); color:var(--text-ink); font-family:'Poppins','Inter',sans-serif; font-weight:600; font-size:12.5px; cursor:pointer; border:none; box-shadow:0 0 0 1px color-mix(in srgb, var(--accent) 40%, transparent),0 8px 22px -6px color-mix(in srgb, var(--accent) 70%, transparent); transition:background .15s; }
      .pp-cta:hover { background:var(--accent-hover); }
      .pp-cta:disabled { opacity:.55; cursor:default; }

      /* ---- Auth gate, verbatim from the Sign up & Sign in Flow spec ---- */
      .gate-head { font-family:'Poppins','Inter',sans-serif; font-weight:600; font-size:26px; color:var(--text); letter-spacing:-.01em; text-align:center; }
      .gate-sub  { font-family:'Poppins','Inter',sans-serif; font-size:13.5px; color:var(--text-muted); margin-top:9px; line-height:1.55; text-align:center; }
      .gate-card {
        background:var(--bg-panel);
        border:1px solid var(--tint-08); border-radius:18px;
        padding:24px 24px 26px; box-shadow:var(--shadow-pop);
      }
      .gate-lbl { display:block; font-family:'Poppins','Inter',sans-serif; font-size:11px; letter-spacing:.5px; color:var(--text-muted); margin-bottom:7px; text-transform:uppercase; }
      .gate-fld {
        width:100%; box-sizing:border-box; font-family:'Poppins','Inter',sans-serif; font-size:13.5px; color:var(--text);
        background:var(--shade-28); border:1px solid var(--tint-10); border-radius:11px; padding:12px 13px;
        outline:none; transition:border-color .2s, background .2s;
      }
      .gate-fld::placeholder { color:var(--text-faint); }
      .gate-fld:focus { border-color:color-mix(in srgb, var(--accent) 55%, transparent); background:color-mix(in srgb, var(--accent) 5%, transparent); }
      .gate-cta {
        width:100%; display:flex; align-items:center; justify-content:center; gap:8px;
        padding:14px; border-radius:12px; border:none; background:var(--accent); color:var(--text-ink);
        font-family:'Poppins','Inter',sans-serif; font-weight:600; font-size:14px; cursor:pointer;
        box-shadow:0 12px 26px -10px color-mix(in srgb, var(--accent) 70%, transparent);
        transition:transform .16s, box-shadow .2s, background .2s;
      }
      .gate-cta:hover { transform:translateY(-1px); background:var(--accent-hover); }
      .gate-cta:disabled { opacity:.55; cursor:default; transform:none; }
      /* Provider sign-in. THEME.md "Ghost" recipe deliberately, not the mint
         CTA: the password button stays the one primary action on the card. */
      .gate-oauth {
        width:100%; display:flex; align-items:center; justify-content:center; gap:9px;
        padding:12px; border-radius:12px; cursor:pointer;
        background:var(--bg-card); border:1px solid var(--stroke); color:var(--text);
        font-family:'Poppins','Inter',sans-serif; font-weight:600; font-size:13.5px;
        transition:background .18s, border-color .18s;
      }
      .gate-oauth + .gate-oauth { margin-top:9px; }
      .gate-oauth:hover { background:var(--tint-06); border-color:color-mix(in srgb, var(--text) 22%, transparent); }
      .gate-oauth:disabled { opacity:.55; cursor:default; }
      .gate-oauth svg { width:17px; height:17px; flex:none; }
      .gate-or { display:flex; align-items:center; gap:12px; margin:18px 0 14px; }
      .gate-or::before, .gate-or::after { content:''; flex:1; height:1px; background:var(--tint-08); }
      .gate-or span { font-family:'Poppins','Inter',sans-serif; font-size:10.5px; letter-spacing:.06em; text-transform:uppercase; color:var(--text-faint); }
      .gate-foot { text-align:center; font-family:'Poppins','Inter',sans-serif; font-size:12.5px; color:var(--text-muted); margin-top:20px; }
      .gate-link { color:var(--accent); cursor:pointer; background:none; border:none; font:inherit; padding:0; transition:color .18s; }
      .gate-link:hover { color:var(--accent-pale); }
      /* Release number (filled from APP_VERSION in core.js): a quiet pill beside
         the wordmark on the sign-in top bar, and a mono caption under Help /
         Settings that stays readable on the collapsed rail. */
      .gate-version { font-family:'JetBrains Mono',ui-monospace,monospace; font-size:10px; color:var(--text-muted); padding:2px 7px; border-radius:6px; border:1px solid var(--tint-08); background:var(--tint-03); }
      .rail-version { font-family:'JetBrains Mono',ui-monospace,monospace; font-size:10px; letter-spacing:.02em; color:var(--text-faint); padding:6px 12px 0; white-space:nowrap; }
      body.nav-collapsed #sidebar .rail-version { padding-left:0; padding-right:0; text-align:center; }

      /* Spec screen-entry motion: every view/pane switch rises in (ccRise),
         matching the prototypes. Re-triggered from switchView/switchAuthTab. */
      .view-enter { animation: ccRise .35s ease both; }

      /* Spec motion (ccFadeIn/ccModalIn/ccRise staggers) collapses to nothing
         for users who ask for reduced motion. */
      @media (prefers-reduced-motion: reduce) {
        .modal-backdrop, .modal-card, .pp-cell, .view-enter, #pendingInvites * {
          animation-duration: .01s !important;
          animation-delay: 0s !important;
        }
      }

      /* Manage-user drawer action buttons (Add location / Bulk edit). */
      .ua-btn {
        display:inline-flex; align-items:center; gap:6px; white-space:nowrap;
        font-family:'Poppins','Inter',sans-serif; font-size:12px; font-weight:500;
        padding:8px 13px; border-radius:10px; cursor:pointer;
        background: var(--bg-card); border:1px solid var(--stroke); color: var(--text-soft);
        transition: all .15s ease;
      }
      .ua-btn:hover { border-color: var(--stroke-2); color: var(--text); }
      .ua-btn-accent { background: var(--accent-dim); border-color: color-mix(in srgb, var(--accent) 45%, transparent); color:var(--accent-light); font-weight:600; }
      .ua-btn-accent:hover { border-color: color-mix(in srgb, var(--accent) 70%, transparent); color:var(--accent-pale); }

      /* ===== Compliance permit-detail modal (stellar-v3 "V3 Full" spec) =====
         Scoped to .cm-modal so the shared .modal-card is untouched. Wide
         centered detail card: header (fixed) · two-column body (independent
         scroll) · compact comments strip · action bar. Columns stack when the
         card is narrow (drawer / mobile) via flex-wrap. */
      /* ONE scroll, the whole panel — same as the work order (owner, Aug 21:
         "it should scroll like the whole thing. not just the middle part").
         The columns used to scroll independently inside a fixed-height card, so
         a permit scrolled in a short window in the middle while the header, the
         composer and the action bar sat still around it, and a field could be
         cut in half at the boundary. Now the card scrolls and only the header
         and the action bar stay put. */
      .cm-modal { max-width: 980px !important; width: 100%; height: 86vh; max-height: 820px;
        background: var(--head-b); border: 1px solid var(--tint-12); border-radius: 20px;
        box-shadow: var(--shadow-modal); overflow-y: auto; overscroll-behavior: contain;
        display: flex; flex-direction: column; }
      .cm-head { position: sticky; top: 0; z-index: 3; }
      .cm-actionbar { position: sticky; bottom: 0; z-index: 3; }
      .as-drawer .cm-modal { max-width: 520px !important; height: 100%; max-height: 100%; border-radius: 0; }
      .cm-head { flex: none; padding: 20px 24px 16px; border-bottom: 1px solid var(--tint-08); background: var(--bg-head); }
      .cm-chip { font-family:'Poppins','Inter',sans-serif; font-size:11px; font-weight:500; color:var(--text-secondary); background:var(--tint-06); border:1px solid var(--tint-12); border-radius:6px; padding:3px 8px; white-space:nowrap; }
      .cm-chip-prop { color:var(--text-ink); background:var(--green); border:none; }
      /* A one-row textarea rather than an input: an input scrolls sideways and
         can never wrap, so a long permit name was only partly readable. Strip
         the field chrome so it still reads as the page heading. */
      .cm-title { display:block; width:100%; box-sizing:border-box; background:transparent; border:none; outline:none; resize:none; overflow:hidden; padding:1px 0; border-radius:6px;
        font-family:'Poppins','Inter',sans-serif; font-weight:600; font-size:25px; letter-spacing:-.01em; line-height:1.18; color:var(--text); }
      .cm-title::placeholder { color:var(--text-ghost); }
      /* The work order's .drawer-chip metrics, not a pill: 11px label,
         3px 8px padding, 6px radius. The owner read the old one as bigger than
         a work order's status. It stays a <select> so the lane is still
         editable from here, with the caret saying so. */
      /* The lane's tint and border arrive as custom properties
         (cmSyncStatusUI), not as inline background/border, so the :hover
         border below can still win over them. */
      .cm-statuspill { display:flex; align-items:center; gap:5px; padding:3px 8px; border-radius:6px; font-size:11px; background:var(--cm-pill-bg, var(--tint-04)); border:1px solid var(--cm-pill-bd, var(--tint-14)); cursor:pointer; transition:border-color .15s; }
      /* The category chip's display lives here rather than inline, so the
         .hidden toggle (app.css loads after this file) can hide it. */
      .cm-metachip { display:inline-flex; align-items:center; gap:5px; }
      /* It is a menu, so it says so: the pill had a border and a pointer but no
         caret, and read as a status badge you could not press. */
      .cm-statuspill:hover { border-color:color-mix(in srgb, var(--accent) 45%, transparent); }
      .cm-statuspill > svg { flex:none; opacity:.6; color:var(--text-muted); transition:opacity .15s; }
      .cm-statuspill:hover > svg { opacity:1; }
      /* The same for a select inside a card. It needs a wrapper because a
         <select> cannot carry a pseudo-element, and it is an inline <svg> rather
         than a background image because only an inline one can take currentColor
         and follow the theme — a data-URI background would have to hardcode two
         colours, one per theme. */
      .cm-select { position:relative; display:block; }
      /* Scoped through .cm-field on purpose: .cm-field select and its :focus
         rule come LATER in this file at the same specificity, so an unscoped
         .cm-select > select loses the tie and the padding that clears the caret
         is reset to 0 (and to 6px on focus). */
      .cm-field .cm-select > select { width:100%; padding-right:15px; }
      .cm-field .cm-select > select:focus { padding-right:15px; }
      .cm-select > svg { position:absolute; right:1px; top:50%; margin-top:-5px; pointer-events:none; opacity:.55; color:var(--text-muted); transition:opacity .15s, color .15s; }
      .cm-select:hover > svg { opacity:1; color:var(--text); }
      /* LOCATION is BOXED, so it reads as a dropdown rather than as a value: it
         is the field people change most, it spans the card's full width, and
         sitting directly above the address a bare location name read as another
         line of that address. TYPE and RESPONSIBLE keep the bare treatment on
         purpose — they sit in the 2-column grid, where a box would set them
         against the plain inputs beside them and break the row rhythm. */
      .cm-select-box { border:1px solid var(--tint-12); border-radius:10px; background:var(--tint-03); padding:0 13px; transition:border-color .15s, background .15s; }
      .cm-select-box:hover { border-color:color-mix(in srgb, var(--accent) 45%, transparent); }
      .cm-select-box:focus-within { border-color:color-mix(in srgb, var(--accent-info) 45%, transparent); background:color-mix(in srgb, var(--accent-info) 8%, transparent); }
      /* The field rules pad every value to 2px and bleed the focus highlight out
         with a negative margin; inside a box that collapses the padding the caret
         clears, so the boxed select carries its own. focus-within restyles the
         box instead, which is what a real field does. */
      .cm-field .cm-select-box > select,
      .cm-field .cm-select-box > select:focus { padding:9px 15px 9px 0; margin:0; background:transparent; }
      /* right is measured from the padding box, so 13px sits it on the content
         edge, clear of the border and the wrapper's own padding. */
      .cm-select.cm-select-box > svg { right:13px; }
      .cm-statuspill select { background:transparent; border:none; outline:none; color:var(--text-body); font-family:'Poppins','Inter',sans-serif; font-weight:500; font-size:11px; cursor:pointer; -webkit-appearance:none; appearance:none; padding-right:2px; }
      .cm-statuspill select option { background:var(--bg-field); color:var(--text); }
      .cm-x { width:38px; height:38px; border-radius:10px; background:var(--tint-04); border:1px solid var(--tint-12); display:flex; align-items:center; justify-content:center; cursor:pointer; color:var(--text-secondary); transition:background .15s,border-color .15s; }
      .cm-x:hover { background:color-mix(in srgb, var(--urgent) 14%, transparent); border-color:color-mix(in srgb, var(--urgent) 40%, transparent); }
      /* Above the title (owner, Sep 25), so the gap is below it, not above,
         and at the work order's own text scale: 11px beside a 9px eyebrow and
         a 13px icon, rather than the 12.5px panel it started as. */
      .cm-next { display:flex; align-items:center; gap:7px; margin:0 0 12px; padding:6px 10px; border-radius:8px; background:color-mix(in srgb, var(--accent-info) 7%, transparent); border:1px solid color-mix(in srgb, var(--accent-info) 22%, transparent); }
      /* nowrap is load-bearing, not tidiness. In a WRAP-enabled flex container
         the line grows to fit its tallest item, so the two columns never get a
         definite height — overflow-y:auto then has nothing to scroll against and
         both simply grew past the body, painting over the comments strip and the
         action bar below them. Measured at 1280x900: body 412px tall, columns
         840px, action bar at y=775 with rail content running to y=1074.
         The narrow-card case that wrap was there for is handled below by an
         explicit column direction + a scrolling modal. */
      /* nowrap stays load-bearing: in a wrap-enabled flex container the line
         grows to fit its tallest item, which is what let the columns escape the
         body and paint over the footer. */
      .cm-body { flex:none; display:flex; flex-wrap:nowrap; }
      /* One column: the attachment, the tags and the destructive controls all
         moved into the flow, so there is no rail to sit beside. */
      .cm-details { flex:1; min-width:0; padding:18px 22px 20px; }
      /* The canvas fill belongs to the SIDE COLUMN — it is what a work order's
         rail wears, and it is what makes the two-column body read as a body plus
         a sidebar. Every stacked layout below sets width:100% and cancels it
         there: as a full-width band the same fill is just a darker stripe across
         the middle of the panel, which is what made a permit look like it had
         three different backgrounds while a work order had one. */
      .cm-eyebrow { display:flex; align-items:center; gap:9px; margin-bottom:15px; }
      .cm-eyebrow > span:first-child { font-family:'Poppins','Inter',sans-serif; font-size:10px; letter-spacing:1.6px; color:var(--text-muted); white-space:nowrap; }
      .cm-eyebrow > span:last-child { height:1px; flex:1; background:var(--tint-07); }
      .cm-grid { display:grid; grid-template-columns:1fr 1fr; gap:2px 32px; }
      /* One card per group of fields — same chrome as a work order's facts card
         (.wo-modal-section / .drawer-block): hairline border, soft fill, 13px
         radius. The form was one flat 12-field grid before. */
      .cm-card { border:1px solid var(--tint-08); border-radius:13px; background:var(--bg-field); padding:14px 16px 6px; margin-bottom:12px; }
      .cm-card:last-child { margin-bottom:0; }
      .cm-card-label { font-family:'Poppins','Inter',sans-serif; font-size:9px; letter-spacing:1.3px; color:var(--text-muted); margin-bottom:6px; text-transform:uppercase; }

      /* WO-01956 (324): the permit's documents. The current one keeps the
         card's own look (preview, Replace/Remove, Current tag); the rest sit
         under a quiet "Other documents (n)" row - collapsed by default -
         with Add beside it. Nothing is ever deleted by a Replace. */
      .current-tag { font-family:'Poppins','Inter',sans-serif; font-size:9px; font-weight:600; letter-spacing:.8px; text-transform:uppercase;
        color:var(--accent); background:var(--accent-dim); border:1px solid color-mix(in srgb, var(--accent) 30%, transparent);
        border-radius:6px; padding:1px 6px; flex:none; }
      .cm-doc-others { margin-top:10px; border-top:1px solid var(--tint-055); padding-top:8px; }
      .cm-doc-others-head { display:flex; align-items:center; gap:4px; }
      .cm-doc-others-btn { display:flex; align-items:center; gap:9px; flex:1; min-width:0; padding:7px 9px; border-radius:9px;
        background:transparent; border:1px solid transparent; cursor:pointer; text-align:left;
        font-family:'Poppins','Inter',sans-serif; font-size:11.5px; font-weight:500; color:var(--text-soft);
        transition:background .15s ease, border-color .15s ease, color .15s ease; }
      .cm-doc-others-btn:hover { background:var(--tint-05); border-color:var(--tint-07); color:var(--text-bright); }
      .cm-doc-others-btn .cm-doc-chev { margin-left:auto; transition:transform .18s ease; color:var(--text-muted); }
      .cm-doc-others-btn.open .cm-doc-chev { transform:rotate(90deg); }
      .cm-doc-count { font-family:'Poppins','Inter',sans-serif; font-size:10px; font-weight:600; padding:1px 7px; border-radius:999px;
        background:var(--tint-06); border:1px solid var(--tint-12); color:var(--text-soft); }
      .cm-doc-add { display:inline-flex; align-items:center; gap:6px; flex:none; padding:6px 10px; border-radius:8px; cursor:pointer;
        font-family:'Poppins','Inter',sans-serif; font-size:10.5px; font-weight:500;
        background:transparent; border:1px dashed var(--tint-14); color:var(--text-soft);
        transition:background .15s ease, border-color .15s ease, color .15s ease; }
      .cm-doc-add:hover { background:var(--tint-05); border-color:color-mix(in srgb, var(--accent) 40%, transparent); color:var(--text-bright); }
      .cm-doc-list { margin-top:4px; }
      .cm-doc-list.hidden { display:none; }
      .cm-doc-row { display:flex; align-items:center; gap:10px; padding:8px 9px; border-radius:10px; transition:background .15s ease; }
      .cm-doc-row:hover { background:var(--tint-04); }
      .cm-doc-row + .cm-doc-row { border-top:1px solid var(--tint-045); border-radius:0; }
      .cm-doc-ico { width:30px; height:30px; flex:none; border-radius:8px; display:flex; align-items:center; justify-content:center;
        background:color-mix(in srgb, var(--accent-info) 12%, transparent); color:var(--accent-info);
        border:1px solid color-mix(in srgb, var(--accent-info) 26%, transparent); }
      .cm-doc-name { font-size:12px; color:var(--text-body); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
      .cm-doc-meta { font-size:10px; color:var(--text-muted); margin-top:1px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
      .cm-doc-acts { display:flex; align-items:center; gap:6px; flex:none; }
      .cm-doc-act { display:inline-flex; align-items:center; gap:5px; padding:4px 9px; border-radius:8px; cursor:pointer;
        font-family:'Poppins','Inter',sans-serif; font-size:10.5px; font-weight:500;
        background:var(--tint-04); border:1px solid var(--tint-10); color:var(--text-soft);
        transition:background .15s ease, border-color .15s ease, color .15s ease; }
      .cm-doc-act:hover { background:var(--tint-06); border-color:var(--tint-14); color:var(--text-bright); }
      .cm-doc-act.danger:hover { border-color:color-mix(in srgb, var(--urgent) 45%, transparent); color:var(--urgent-soft); }
      .cm-doc-act.icon { padding:5px; }
      .cm-doc-empty { font-size:11px; color:var(--text-muted); font-style:italic; padding:6px 9px; }
      /* Hairline between rows, not after the last one — the same rule the work
         order's .drawer-rows uses, so a card never ends on a dangling border. */
      .cm-field { padding:11px 0; border-top:1px solid var(--tint-055); min-width:0; }
      .cm-grid > .cm-field:first-child { border-top:none; padding-top:4px; }
      .cm-field.cm-col2 { grid-column:1 / -1; }
      .cm-field > label { display:block; font-family:'Poppins','Inter',sans-serif; font-size:9px; letter-spacing:1px; color:var(--text-dim); margin-bottom:5px; }
      /* value inputs: read like the V3 text values but stay editable */
      .cm-field input, .cm-field select, .cm-field textarea { width:100%; box-sizing:border-box; background:transparent; border:none; outline:none;
        font-family:'Poppins','Inter',sans-serif; font-size:15px; color:var(--text); padding:2px 0; border-radius:6px; transition:background .15s; -webkit-appearance:none; appearance:none; }
      .cm-field select option { background:var(--bg-field); color:var(--text); }
      .cm-field input::placeholder, .cm-field textarea::placeholder { color:var(--text-ghost); font-style:italic; }
      .cm-field input:focus, .cm-field select:focus, .cm-field textarea:focus { background:color-mix(in srgb, var(--accent-info) 8%, transparent); padding:2px 6px; margin:0 -6px; }
      .cm-field input[type="date"] { color-scheme: inherit; }
      /* No font-family: the note reads in whatever the activity rows read in
         (owner, Sep 25). The textarea only had Poppins because it was set here. */
      .cm-notes textarea { width:100%; box-sizing:border-box; background:var(--tint-03); border:1px solid var(--tint-10); border-radius:11px; padding:13px 15px;
        font-size:12px; line-height:1.6; color:var(--text-secondary); outline:none; resize:none; overflow:hidden; min-height:64px; }
      .cm-notes textarea:focus { border-color:color-mix(in srgb, var(--accent-info) 40%, transparent); }
      /* A section inside a card rather than a card of its own — the same idea as
         the work order's .drawer-section. Used by the permit's Renewal block
         and by the rail's Tags. */
      .cm-section { border-top:1px solid var(--tint-055); margin-top:12px; padding-top:12px; }
      /* A section's own first row draws no hairline: the divider above it
         already separates it, and two lines 2px apart read as a mistake. Two
         children to a row is this grid's own shape (.cm-grid is 2 columns). */
      .cm-grid-section > .cm-field:nth-child(-n+2) { border-top:none; padding-top:4px; }
      .cm-railcard { border:1px solid var(--tint-08); border-radius:13px; background:var(--bg-field); padding:14px; margin-bottom:12px; }
      .cm-railcard .cm-rail-eyebrow { font-family:'Poppins','Inter',sans-serif; font-size:9px; letter-spacing:1.3px; color:var(--text-muted); }
      /* The danger block a work order ends its panel with: tinted border, the
         label in the urgent tone, one full-width button, one line on what goes.
         Selector is 0-3-0 to beat .cm-railcard .cm-rail-eyebrow's colour. */
      .cm-railcard.cm-danger { border-color:color-mix(in srgb, var(--urgent) 20%, transparent); }
      .cm-railcard.cm-danger .cm-rail-eyebrow { color:var(--urgent-soft); }
      .cm-danger-note { font-family:'Poppins','Inter',sans-serif; font-size:10.5px; line-height:1.5; color:var(--text-faint); margin-top:9px; }
      /* Activity is a card in the main column (index.html), so this only spaces
         its parts. It used to be a full-width band on --bg-card, which put a
         third surface in a modal that has two — the column and the rail — and
         cut straight across both of them. */
      .cm-comments { padding-bottom:14px; }
      .cm-composer { margin-top:14px; padding-top:14px; border-top:1px solid var(--tint-08); }
      /* The SAME fill .cm-modal uses, so this is the modal's own surface with a
         hairline above it rather than a third colour stacked under the body. A
         work order's footer does the same: .drawer-actions wears the drawer's
         own background and lets the border do the separating. It has to stay
         explicit — the bar is sticky, so it must be opaque or content scrolls
         under it. */
      /* One card holding full-width stacked buttons, the way a work order's
         .drawer-actions does it: the actions are the last thing in the panel, so
         they read as one object rather than as a row of chips. */
      .cm-actionbar { flex:none; padding:14px 22px; border-top:1px solid var(--tint-10); background:var(--head-b); }
      .cm-action-card { display:flex; flex-direction:column; gap:8px; border:1px solid var(--tint-06); border-radius:16px; background:var(--bg-panel); padding:14px; }
      .cm-btn { display:flex; align-items:center; gap:8px; padding:9px 18px; border-radius:10px; cursor:pointer; font-family:'Poppins','Inter',sans-serif; font-weight:500; font-size:12.5px; border:1px solid var(--tint-14); background:var(--tint-04); color:var(--text-secondary); transition:background .15s; }
      .cm-btn:hover { background:var(--tint-08); }
      .cm-btn-danger { background:color-mix(in srgb, var(--urgent) 8%, transparent); border-color:color-mix(in srgb, var(--urgent) 28%, transparent); color:var(--urgent); }
      .cm-btn-danger:hover { background:color-mix(in srgb, var(--urgent) 16%, transparent); }
      .cm-btn-primary { background:var(--accent); border-color:transparent; color:var(--text-ink); }
      .cm-btn-primary:hover { background:var(--accent-hover); }
      /* Narrow card: stack the two columns and let the WHOLE modal scroll, the
         same shape the drawer uses. Stacking without that would just move the
         overflow problem into the column axis. */
      @media (max-width: 760px) {
        .cm-modal { overflow-y: auto; overscroll-behavior: contain; }
        .cm-body { flex-direction: column; flex: none; }
        .cm-details { flex: none; overflow: visible; }
        .cm-head { position: sticky; top: 0; z-index: 3; }
        .cm-actionbar { position: sticky; bottom: 0; z-index: 3; }
      }
      /* Panel (drawer) mode is narrow (Panel is the default view) — stack the
         columns into one vertical scroll so the fixed-width rail can't wrap onto
         a second row with a dead gutter + an orphan border-right on details. */
      .as-drawer .cm-body { flex-direction: column; flex-wrap: nowrap; }
      .as-drawer .cm-details { flex: none; min-width: 0; overflow: visible; }
      /* The PANEL's palette, not the modal's. This detail was built out of the
         work order's MODAL surfaces — a --head-b shell under --bg-field cards —
         but it renders in the panel, where a work order wears --bg-shell under
         --bg-panel cards. #091620 is what made a permit read darker than a work
         order, and it is not a one-line swap: a solid --bg-field card on the
         lighter shell would come out DARKER than its own background and read as
         a hole, so the shell and the cards move together.
         The head needs nothing. .cm-head already wears --bg-head, the same
         surface .drawer-head wears, and in both panels that header is darker
         than the body under it. The cards' border and radius are left alone
         too — this is the fill, not the shape. */
      .as-drawer .cm-modal,
      .as-drawer .cm-actionbar { background: var(--bg-shell); }
      .as-drawer .cm-card,
      .as-drawer .cm-railcard { background: var(--bg-panel); }
      /* ONE scroll, like the work order panel. The body used to be the only
         scrolling zone, with the comments and the action bar pinned below it —
         so a permit scrolled in a short window in the middle and cut a field in
         half at its boundary, while the composer sat in dead space underneath
         (owner, Aug 20: "only the middle part scrolls ... not like design the
         way work order is"). Now the whole panel scrolls and only the header
         and the action bar stay put, which is what a drawer should do. */
      /* The single-scroll rules moved to .cm-modal above — the drawer was just
         the first surface to need them. Only the stacking is drawer-specific. */
      /* Short viewports (landscape phone / high browser zoom): the fixed head,
         comments and action-bar zones can exceed the height-capped card and clip
         the action bar (Save/Cancel unreachable). Let the whole modal scroll. */
      @media (max-height: 640px) {
        #complianceModal { align-items: flex-start; overflow-y: auto; padding-top: 12px; padding-bottom: 12px; }
        .cm-modal { height: auto; max-height: none; }
        .as-drawer .cm-modal { min-height: calc(100% - 24px); }
        .cm-body { flex: none; }
      }

      /* Segmented track: 11px track / 8px pill (spec §3), inset var(--bg-card-2) fill. */
      .seg { display:inline-flex; background: var(--bg-card-2); border:1px solid var(--tint-07); border-radius:11px; padding:3px; gap:2px; }
      .seg-opt {
        padding:.45rem .85rem; font-size:.78rem; font-weight:400; font-family:'Poppins','Inter',sans-serif; color: var(--text-muted);
        border-radius:8px; cursor:pointer; transition: all .15s; white-space:nowrap;
      }
      .seg-opt:hover { color: var(--text); }
      .seg-opt.active { background: var(--bg-well); color:var(--text); font-weight:500; }
      .seg-opt[data-tint="URGENT"].active { background: color-mix(in srgb, var(--urgent) 18%, transparent); box-shadow: 0 0 0 1px color-mix(in srgb, var(--urgent) 45%, transparent); color:var(--urgent-mid); }
      .seg-opt[data-tint="MED"].active    { background: color-mix(in srgb, var(--accent-info) 16%, transparent); box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent-info) 40%, transparent);   color:var(--accent-info-light); }
      .seg-opt[data-tint="LOW"].active    { background: color-mix(in srgb, var(--text-muted) 22%, transparent); box-shadow: 0 0 0 1px color-mix(in srgb, var(--text-muted) 40%, transparent); color:var(--text-body); }

      /* 205 put the AI agents panel in the Stella chat column (the chat and its
         column were retired in 263), and it was the first child
         here that is genuinely tall — three bots is ~350px. As flex:none it
         blew the fixed height: the total ran past the container, #aiConnectMount
         above it was squeezed to ZERO height so its card painted straight over
         the agents table, and the composer was pushed off screen entirely
         (measured at 1400x700: chat box bottom 827px in a 700px viewport).
         So the panel shrinks and scrolls inside itself instead of pushing, and
         the transcript gives up some of its floor while the panel is on screen.
         Both of those together are what keep the composer visible. */
      /* Second attempt (Sep 10): a shrinkable mount was no better — with a long
         transcript the chat box took the space and the panel shrank to a
         sliver painted over the Connect card. So the panel is a disclosure
         like the Connect card: the header row never shrinks, the body only
         exists while open and scrolls inside a capped height. Neither can
         ever squeeze its neighbour to nothing again. */
      /* The AI agents panel went back to the Users tab (Sep 10) — a normal
         scrolling page, so it needs no cap of its own. What it kept from its
         day here is the disclosure. */
      .ag-head .ag-chev { flex:none; transition:transform .2s ease; }
      .ag-panel.is-open .ag-head .ag-chev { transform:rotate(180deg); }
      .ag-head:focus-visible { outline:none; box-shadow:inset 0 0 0 3px var(--accent-dim); }
      @media (prefers-reduced-motion: reduce) { .ag-head .ag-chev { transition:none; } }

      /* ---------------------------------------------------------------
         Connect your AI (owner, Sep 6 & Sep 10). Opens in a modal dialog
         from the Help modal's "Connect your AI" button; it was the whole
         Stellar AI tab (Your AI until 2026-09-30) until that tab retired
         2026-10-06 (the chat it sat above was retired with the company AI
         key, 263).
         Steps inside the modal are clean, readable numbered steps without
         checkboxes.
      --------------------------------------------------------------- */
      .ai-connect-ico { width:36px; height:36px; border-radius:11px; flex:none; display:flex; align-items:center; justify-content:center; background: var(--accent-dim); border:1px solid color-mix(in srgb, var(--accent) 32%, transparent); color: var(--accent); }

      /* Modal contents for Connect your AI */
      .ai-connect-link { display:flex; flex-direction:column; gap:8px; padding:0 0 14px; }
      .ai-connect-copy { align-self:flex-start; min-height:40px; padding:0 16px; font-size:13px; font-weight:600; box-shadow: var(--glow-accent); }
      .ai-connect-url {
        display:block; font-family:'JetBrains Mono',ui-monospace,monospace; font-size:11.5px; line-height:1.5; color: var(--text-soft);
        background: var(--shade-35); border:1px solid var(--stroke); border-radius:10px; padding:8px 11px;
        overflow-wrap:anywhere; word-break:break-all; user-select:all; -webkit-user-select:all;
      }
      .ai-connect-lede { margin:0; font-size:12.5px; color: var(--text-soft); }
      .ai-connect-tabs { flex-wrap:wrap; max-width:100%; margin-bottom:10px; }
      .ai-connect-tabs .seg-opt { border:0; background:transparent; font:inherit; font-family:'Poppins','Inter',sans-serif; font-size:.78rem; color: var(--text-muted); min-height:36px; }
      .ai-connect-tabs .seg-opt.active { background:var(--bg-well); color: var(--text); font-weight:500; }
      .ai-connect-tabs .seg-opt:focus-visible { outline:none; box-shadow: 0 0 0 3px var(--accent-dim); }
      .ai-connect-hint { margin:8px 0 0; font-size:11.5px; color: var(--text-faint); }

      /* Clean instruction steps (no checkboxes, owner Sep 10) */
      .ai-connect-steps-clean { list-style:none; margin:10px 0 0; padding:0; display:flex; flex-direction:column; gap:8px; }
      .ai-connect-step-clean {
        display:flex; align-items:flex-start; gap:12px; padding:10px 13px; border-radius:11px;
        background: var(--tint-03); border:1px solid var(--stroke);
        transition: background .15s, border-color .15s;
      }
      .ai-connect-step-clean:hover {
        background: var(--tint-045); border-color: var(--stroke-2);
      }
      .ai-connect-step-clean .ai-connect-num {
        width:24px; height:24px; border-radius:8px; flex:none; display:flex; align-items:center; justify-content:center;
        font-family:'JetBrains Mono',ui-monospace,monospace; font-size:11.5px; font-weight:700; color: var(--accent);
        background: color-mix(in srgb, var(--accent) 12%, transparent); border:1px solid color-mix(in srgb, var(--accent) 30%, transparent);
        margin-top: 1px;
      }
      .ai-connect-step-clean .ai-connect-text {
        font-size:13px; line-height:1.5; color: var(--text); min-width:0; overflow-wrap:anywhere;
      }
      .ai-connect-foot { margin-top:14px; padding-top:12px; border-top:1px solid var(--stroke-soft); display:flex; flex-direction:column; gap:6px; font-size:12px; color: var(--text-muted); line-height:1.5; }
      .ai-connect-foot strong { color: var(--text-soft); font-weight:500; }
      .ai-connect-guide { display:inline-flex; align-items:center; gap:6px; min-height:36px; color: var(--accent); font-size:12px; text-decoration:none; }
      .ai-connect-guide:hover { color: var(--accent-light); text-decoration:underline; }
      .ai-connect-guide:focus-visible { outline:none; box-shadow: 0 0 0 3px var(--accent-dim); border-radius:8px; }

      /* Smart AI Provider badge */
      .ai-provider-badge {
        display:inline-flex; align-items:center; gap:6px; font-size:11.5px; font-weight:500;
        padding:3px 9px; border-radius:8px; margin-left:4px;
        background: var(--tint-05); border:1px solid var(--tint-12); color:var(--text-secondary);
      }
      .ai-provider-badge .ai-badge-dot {
        width:7px; height:7px; border-radius:50%; flex:none;
      }
      .seg-opt-custom {
        font-weight: 500; color: var(--accent) !important;
      }

      @media (max-width: 767px) {
        .ai-connect-copy { align-self:stretch; justify-content:center; }
        .ai-connect-tabs { width:100%; }
        .ai-connect-tabs .seg-opt { flex:1 1 auto; justify-content:center; min-height:40px; }
      }
      @media (prefers-reduced-motion: reduce) {
        .ai-connect-step-clean { transition:none; }
      }

      /* KPI roll-in */
      .kpi-num { transition: color .25s; }
      .kpi-num.is-pulse { animation: kpi-pulse .6s ease-out; }
      @keyframes kpi-pulse { 0% { transform: scale(1); } 30% { transform: scale(1.06); } 100% { transform: scale(1); } }

      /* Drag & drop */
      .task-card { user-select: none; }
      .task-card[draggable="true"] { cursor: grab; }
      .task-card.is-dragging {
        opacity: .35;
        transform: scale(.98);
        cursor: grabbing;
      }
      .task-card.is-saving { opacity: .6; pointer-events: none; }
      .kanban-col.is-drop-target {
        outline: 2px dashed color-mix(in srgb, var(--green) 55%, transparent);
        outline-offset: -2px;
        background: color-mix(in srgb, var(--green) 6%, transparent);
        transition: background .15s, outline-color .15s;
      }
      .kanban-col.is-drop-target .kanban-col-body { background: color-mix(in srgb, var(--green) 3%, transparent); }
      .kanban-col[data-no-drop="true"].is-drop-target {
        outline: 2px dashed color-mix(in srgb, var(--urgent) 55%, transparent);
        background: color-mix(in srgb, var(--urgent) 7%, transparent);
      }
      .drop-placeholder {
        border: 1.5px dashed var(--status-complete);
        border-radius: 12px;
        background: color-mix(in srgb, var(--green) 8%, transparent);
        min-height: 70px;
        animation: ph-pulse .8s ease-in-out infinite alternate;
      }
      @keyframes ph-pulse {
        from { opacity: .6; } to { opacity: 1; }
      }

      /* ============================================================
         Map view (Leaflet) — dark slate skin
      ============================================================ */
      #mapContainer { height: calc(100vh - 220px); min-height: 460px; border-radius: 16px; overflow: hidden; border: 1px solid var(--stroke); background: var(--bg-raised); }
      .leaflet-container { background: var(--bg-canvas); font-family: 'Inter', sans-serif; }
      .leaflet-tile { filter: brightness(0.78) saturate(0.85) contrast(1.05); }
      .leaflet-popup-content-wrapper, .leaflet-popup-tip { background: var(--bg-card); color: var(--text); border: 1px solid var(--stroke); box-shadow: 0 12px 32px -16px var(--shade-70); }
      .leaflet-popup-content-wrapper { border-radius: 12px; }
      .leaflet-popup-content { margin: 12px 14px; font-size: 13px; }
      .leaflet-container a.leaflet-popup-close-button { color: var(--text-soft); }
      /* Google Maps InfoWindow: Google reserves an empty header strip above the
         content (.gm-style-iw-ch) for its close button, which left a big gap at
         the top of our pin popup. Collapse it, tighten padding, drop the inner
         scroll, and hide Google's default X since we render our own. */
      .gm-style-iw-ch { padding-top: 0 !important; }
      .gm-style-iw-c {
        padding: 12px 14px !important; border-radius: 14px !important;
        background: var(--bg-card) !important; border: 1px solid var(--stroke) !important;
        box-shadow: 0 16px 40px -18px var(--shade-75) !important;
      }
      .gm-style-iw-d { overflow: hidden !important; max-height: none !important; background: transparent !important; }
      /* the pointer/tail under the bubble was Google's default white triangle */
      .gm-style .gm-style-iw-tc::after { background: var(--bg-card) !important; }
      .gm-style .gm-style-iw button.gm-ui-hover-effect { display: none !important; }
      .leaflet-bar a, .leaflet-bar a:hover { background: var(--bg-card); color: var(--text); border-bottom-color: var(--stroke); }
      .leaflet-control-attribution { background: var(--glass) !important; color: var(--text-muted) !important; }
      .leaflet-control-attribution a { color: var(--text-soft) !important; }
      .map-marker-dispatch { position:absolute; top:-6px; right:-6px; width:12px; height:12px; border-radius:999px; border:2px solid var(--bg-card); transform: rotate(45deg); }
      .map-pop-code { font-family:'JetBrains Mono', monospace; font-weight:700; color: var(--status-complete); }
      .map-pop-btn { display:block; width:100%; box-sizing:border-box; text-align:center; margin-top:10px; padding:9px 11px; border-radius:9px; background:var(--accent); color:var(--text-ink); font-weight:700; font-size:12.5px; cursor:pointer; }
      /* Reference vendor chip: 7px radius, 4x9 padding, blue-tint fill/hairline. */
      .vendor-co-pill { display:inline-flex; align-items:center; gap:6px; padding:4px 9px; border-radius:7px; font-family:'Poppins','Inter',sans-serif; font-size:10.5px; font-weight:500; background:color-mix(in srgb, var(--accent-info) 10%, transparent); color:var(--accent-info-light); border:1px solid color-mix(in srgb, var(--accent-info) 25%, transparent); }
      .vendor-co-pill .dot { width:5px; height:5px; border-radius:50%; background:var(--accent-info); flex-shrink:0; }
      /* The x on an agent's auto chip — takes it off this one ticket (132). */
      .vendor-co-pill .x { margin-left:2px; padding:0 2px; cursor:pointer; opacity:.65;
                           font-size:12px; line-height:1; transition:opacity .14s ease, color .14s ease; }
      .vendor-co-pill .x:hover { opacity:1; color:var(--lane-verification); }

      /* ============================================================
         Operations Board redesign (Jul 2026 design pass, "stellar-v3").
         Scoped to THIS view only — every other view keeps the mission-
         control navy theme until it gets the same pass. Because almost
         every shared component (kanban-col, task-card, kpi-tile, chip,
         stat-card, pill) already reads its colors from the --bg, --stroke
         and --accent custom properties, re-declaring just those properties
         on the active Board section reskins the whole view for free — no
         component CSS had to be duplicated.

         Second pass (Jul 14): pulled exact values from the reference file
         instead of eyeballing a charcoal reskin. The reference actually runs
         TWO accents, not one: teal var(--accent) for primary/interactive surfaces
         (tabs, the New Work Order button, card hover), and blue var(--accent-info) for
         informational structure (title rule, vendor pills, the WO detail's
         NEXT/waiting banner) — --accent and --accent-info below, respectively.
      ============================================================ */
      /* Board view — reference has NO view frame: the screen body sits
         directly on the shared app backdrop (.aurora). The old 20px-radius
         inset frame + its own gradient copy are removed (they double-layered
         the design instead of replacing it). */
      section[data-view="tasks"].active {
        font-family: 'Poppins', 'Inter', sans-serif;
        position: relative;
      }
      section[data-view="tasks"].active #boardTitle,
      :is(section[data-view="tasks"],section[data-view="compliance"]).active .kanban-col-title,
      :is(section[data-view="tasks"],section[data-view="compliance"]).active .task-card-title { font-family: 'Poppins', 'Inter', sans-serif; }
      /* Title rule is the explicit .board-title-rule bar (reference), not ::before. */
      section[data-view="tasks"].active .btn-primary { background: var(--accent); color: var(--text-ink); }
      section[data-view="tasks"].active .btn-primary:hover { background: var(--accent-hover); }
      :is(section[data-view="tasks"],section[data-view="compliance"]).active .task-card { background: var(--bg-card); border-color: var(--stroke); border-radius: 14px; padding: 13px 14px; }
      :is(section[data-view="tasks"],section[data-view="compliance"]).active .task-card:hover { border-color: color-mix(in srgb, var(--accent) 50%, transparent); background: var(--bg-card-2); }
      /* Compliance cards are keyboard-reachable (role="button"). A
         keyboard-only ring: the accent border plus a 1px accent shadow (2px of
         solid accent, 3:1 and up against the lane in both themes) and the
         --accent-dim halo of the focus recipe. Scoped like the two rules above,
         or their border-color wins; after :hover, so a focused card under the
         pointer keeps it. The lane body's 8px/12px padding holds the 4px ring,
         and an expired/expiring card's inline left stripe keeps its colour
         inside it. */
      :is(section[data-view="tasks"],section[data-view="compliance"]).active .task-card:focus-visible { outline: none; border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent), 0 0 0 4px var(--accent-dim); }
      /* ---- Board header / toolbar 1:1 from stellar-v3 reference ---- */
      section[data-view="tasks"].active .board-title-block { margin-bottom: 16px; }
      section[data-view="tasks"].active .board-title-row { display:flex; align-items:center; gap:11px; }
      section[data-view="tasks"].active .board-title-rule { width:4px; height:24px; border-radius:2px; background: var(--accent-info); box-shadow: var(--glow-info); flex:none; }
      section[data-view="tasks"].active .board-title { font-family:'Poppins','Inter',sans-serif; font-weight:600; font-size:28px; letter-spacing:-.01em; color: var(--text); line-height:1.15; margin:0; }
      section[data-view="tasks"].active .board-subtitle { font-family:'Poppins','Inter',sans-serif; font-size:13px; color:var(--text-muted); margin:5px 0 0; padding-left:15px; }
      section[data-view="tasks"].active .board-toolbar { gap:11px; margin-bottom:16px; }
      section[data-view="tasks"].active .board-toolbar-cluster { gap:11px; }
      /* Segmented tracks: 11px radius (NOT full pill), blob 8px — reference exact. */
      :is(section[data-view="tasks"],section[data-view="compliance"]).active .board-seg,
      section[data-view="tasks"].active #boardViewSeg {
        gap: 2px; background: var(--bg-card-2) !important; border: 1px solid var(--tint-07);
        border-radius: 11px; padding: 3px;
      }
      :is(section[data-view="tasks"],section[data-view="compliance"]).active .tab-blob { background: var(--bg-well); border-radius: 8px; transition: transform 480ms cubic-bezier(.34,1.56,.64,1), width 480ms cubic-bezier(.34,1.56,.64,1), height 480ms cubic-bezier(.34,1.56,.64,1); }
      /* The generic .tab-blob reduced-motion rule loses to the one above (more
         specific, later), so it is repeated at this weight (THEME §6). */
      @media (prefers-reduced-motion: reduce) { :is(section[data-view="tasks"],section[data-view="compliance"]).active .tab-blob { transition: none; } }
      /* One toolbar height, 36px (owner, Aug 28: "these sizes should match").
         The row used to run three: segmented tracks 40.75px (7px button
         padding + 3px track padding + hairline), chips and .btn 36px, the
         More button 34px. The segmented BUTTON carries an explicit 28px box
         so its track lands on 36 exactly — and the sliding blob measures that
         button, so it follows without touching positionTabBlob. */
      :is(section[data-view="tasks"],section[data-view="compliance"]).active .board-seg-btn {
        position: relative; z-index: 1; display: inline-flex; align-items: center;
        height: 28px; padding: 0 14px; border-radius: 8px; cursor: pointer;
        font-family: 'Poppins','Inter',sans-serif; font-size: 12.5px; font-weight: 400;
        color: var(--text-muted); background: transparent; border: none; transition: color .28s ease;
      }
      section[data-view="tasks"].active #boardViewSeg .board-seg-btn { padding: 0 13px; }
      :is(section[data-view="tasks"],section[data-view="compliance"]).active .board-seg-btn[data-active="1"] { font-weight: 500; color: var(--text); }
      :is(section[data-view="tasks"],section[data-view="compliance"]).active .board-seg-btn:hover { color: var(--text); }
      /* Focus stays keyboard-only — no bright native ring on click (the reason the
         Recurring group tabs read as off-style). */
      :is(section[data-view="tasks"],section[data-view="compliance"]).active .board-seg-btn:focus { outline: none; }
      :is(section[data-view="tasks"],section[data-view="compliance"]).active .board-seg-btn:focus-visible { outline: 2px solid color-mix(in srgb, var(--accent) 60%, transparent); outline-offset: 2px; }
      /* @ Mentions chip — reference mint pill with glow dot + count badge. */
      .chip-quiet { display:inline-flex; align-items:center; gap:7px; padding:8px 12px; border-radius:10px; font-size:12px; font-weight:400; font-family:'Poppins','Inter',sans-serif; cursor:pointer; transition: all .2s ease; color:var(--accent-pale); border:1px solid color-mix(in srgb, var(--accent) 28%, transparent); background:color-mix(in srgb, var(--accent) 6%, transparent); white-space:nowrap; }
      .chip-quiet:hover { border-color: color-mix(in srgb, var(--accent) 45%, transparent); color:var(--accent); }
      /* Active = a FILLED pill, not a slightly brighter outline. The old
         active/idle pair differed by ~8% alpha and read as the same chip
         (owner, Aug 11: "can't seem to distinguish if @mentioned is working"). */
      .chip-quiet.active { background:var(--accent); border-color:var(--accent); color:var(--text-ink); font-weight:500; box-shadow:0 6px 18px -8px color-mix(in srgb, var(--accent) 55%, transparent); }
      .chip-quiet.active:hover { background:var(--accent-hover); border-color:var(--accent-hover); color:var(--text-ink); }
      .chip-quiet.active .mention-dot { background:var(--text-ink); box-shadow:none; }
      .chip-quiet .chip-x { opacity:.55; margin-left:1px; }
      .chip-quiet.active:hover .chip-x { opacity:1; }
      .chip-quiet .mention-dot { width:6px; height:6px; border-radius:50%; background:var(--accent); box-shadow:0 0 6px color-mix(in srgb, var(--accent) 80%, transparent); flex:none; }
      .chip-quiet .num { display:inline-flex; align-items:center; justify-content:center; min-width:17px; height:17px; padding:0 4px; border-radius:9px; font-family:'Poppins',sans-serif; font-weight:600; font-size:10px; line-height:1; background:color-mix(in srgb, var(--accent) 22%, transparent); color:var(--accent); }
      .chip-quiet.active .num { background:var(--stroke-2); color:var(--text-ink); }
      /* Quiet chips in More menu (@ Mentioned / Active / Updates / All
         workspaces) stay neutral inset, not mint. */
      #boardQuickFiltersMore .chip-quiet { color: var(--text-soft); border: 1px solid var(--stroke); background: var(--bg-card); }
      #boardQuickFiltersMore .chip-quiet:hover { color: var(--text); border-color: var(--stroke-2); }
      #boardQuickFiltersMore .chip-quiet.active { color: var(--text-ink); background: var(--accent); border-color: var(--accent); }
      #boardQuickFiltersMore .chip-quiet .num { background: transparent; color: inherit; min-width:0; height:auto; padding:0; font-family:'JetBrains Mono',monospace; font-weight:700; font-size:12px; }
      /* Vendor / location filter triggers — reference 10px inset pills. */
      .board-filter-btn {
        display:inline-flex; align-items:center; gap:7px; padding:8px 13px; border-radius:10px;
        background:var(--tint-03); border:1px solid var(--tint-08);
        font-family:'Poppins','Inter',sans-serif; font-size:12px; color:var(--text-soft); cursor:pointer;
        transition: color .15s, border-color .15s, background .15s; white-space:nowrap;
      }
      .board-filter-btn:hover { color:var(--text); border-color:var(--tint-14); }
      .board-filter-btn.is-on { color:var(--text); border-color:color-mix(in srgb, var(--accent-info) 45%, transparent); background:color-mix(in srgb, var(--accent-info) 8%, transparent); }
      .board-filter-btn .chev { font-size:9px; color:var(--text-faint); line-height:1; }
      /* ---------------------------------------------------------------
         ONE toolbar language, every screen (owner, Aug 29: "make sure in
         all screens things like this are same size", and "not the same
         size and ui design and color scheme like board list matrix
         calendar" — the segmented control is the reference).

         Measured before this rule, a filter row could run ten different
         heights: 28.5 (.kpi-toggle "Stats") · 29 (sites view toggle) · 30
         (input.input.text-xs) · 32.5 (select.input.text-xs — Blink forces
         line-height:normal on select, so it never matches the input beside
         it) · 34 (recur chip) · 35.5 · 36 (.board-seg, .chip-quiet,
         .board-filter-btn) · 38 · 41.1 (bare .input select) · 43 (plain
         .btn — Tailwind preflight's line-height:1.5 makes min-height:2.25rem
         a floor it never reaches down to).

         Colour was as split as the box: .btn-ghost paints var(--text), a
         near-white, next to the muted var(--text-soft) of the filter pills, which is
         why "Categories" read as the loud one.

         Everything in a .filter-bar now lands on the segmented control's
         36px, its radius and its muted palette. Layout (display/gap) is
         deliberately NOT set here — rows keep their own spacing. */
      .filter-bar .input,
      .filter-bar .btn,
      .filter-bar .board-filter-btn,
      .filter-bar .chip-quiet,
      .filter-bar .kpi-toggle,
      .filter-bar .board-more-btn,
      .filter-bar > button {
        height: 36px; min-height: 36px;
        padding-top: 0; padding-bottom: 0;
        border-radius: 10px;
        font-family: 'Poppins','Inter',sans-serif; font-size: 12.5px; line-height: 1;
      }
      .filter-bar .board-seg { height: 36px; }
      .filter-bar .board-more-btn { width: 36px; }
      /* Muted by default; the accent belongs to the ACTIVE state, not to
         whichever control happens to be a <button>. */
      .filter-bar .input,
      .filter-bar .btn-ghost {
        background: var(--tint-03);
        border-color: var(--tint-08);
        color: var(--text-soft);
      }
      .filter-bar .btn-ghost:hover,
      .filter-bar .input:hover {
        background: var(--tint-05);
        border-color: var(--tint-14);
        color: var(--text);
      }
      .filter-bar .input:focus { border-color: var(--accent); color: var(--text); }
      /* A select keeps room for its native arrow. */
      .filter-bar select.input { padding-right: 2rem; }
      /* A checkbox label is a control on the row too — same box, and the box
         must not stretch the checkbox inside it. */
      .filter-bar > label {
        display: inline-flex; align-items: center; gap: 7px;
        height: 36px; padding: 0 13px; border-radius: 10px;
        background: var(--tint-03); border: 1px solid var(--tint-08);
        font-family: 'Poppins','Inter',sans-serif; font-size: 12.5px; line-height: 1;
        color: var(--text-soft); cursor: pointer; white-space: nowrap;
      }
      .filter-bar > label:hover { color: var(--text); border-color: var(--tint-14); }
      .filter-bar > label input[type="checkbox"] { height: auto; min-height: 0; margin: 0; flex: none; }
      /* The primary CTA keeps its fill — only its box is normalised. */
      .filter-bar .btn-primary { color: var(--text-ink); }

      /* ---------------------------------------------------------------
         Stella's per-ticket switch (owner, Aug 29: "this should be like a
         switch button per work order, kind of hard to see").

         It was one grey 11px line with an inline text link — the same
         weight as the help text under it, so the one control that decides
         whether an AI agent speaks on this ticket read as a caption. A
         switch says "this is a control", says which way it is set without
         being read, and is a 40x22 target instead of a phrase. */
      .agent-switch {
        display: flex; align-items: center; gap: 11px; margin-top: 10px;
        padding: 9px 12px; border-radius: 11px;
        background: var(--tint-03); border: 1px solid var(--tint-08);
        transition: border-color .18s ease, background .18s ease;
      }
      .agent-switch:hover { border-color: var(--tint-14); }
      .agent-switch .as-avatar {
        width: 26px; height: 26px; border-radius: 8px; flex: none;
        display: inline-flex; align-items: center; justify-content: center;
        font-family: 'Poppins','Inter',sans-serif; font-size: 10px; font-weight: 600;
        color: var(--text-ink); background: linear-gradient(135deg,var(--accent-info),var(--accent));
      }
      .agent-switch .as-body { min-width: 0; flex: 1; }
      .agent-switch .as-title {
        font-family: 'Poppins','Inter',sans-serif; font-size: 12.5px; color: var(--text-secondary);
      }
      .agent-switch .as-sub {
        font-family: 'Poppins','Inter',sans-serif; font-size: 11px; color: var(--text-faint); margin-top: 1px;
      }
      .agent-switch .as-toggle {
        position: relative; width: 40px; height: 22px; flex: none; padding: 0;
        border-radius: 999px; cursor: pointer;
        background: var(--tint-07); border: 1px solid var(--tint-16);
        transition: background .18s ease, border-color .18s ease;
      }
      .agent-switch .as-toggle::after {
        content: ''; position: absolute; top: 2px; left: 2px;
        width: 16px; height: 16px; border-radius: 50%; background: var(--text-muted);
        transition: transform .18s ease, background .18s ease;
      }
      .agent-switch .as-toggle:focus-visible {
        outline: none; box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 25%, transparent);
      }
      .agent-switch.is-on { border-color: color-mix(in srgb, var(--accent) 28%, transparent); background: color-mix(in srgb, var(--accent) 5%, transparent); }
      .agent-switch.is-on .as-title { color: var(--text); }
      .agent-switch.is-on .as-toggle {
        background: color-mix(in srgb, var(--accent) 28%, transparent); border-color: color-mix(in srgb, var(--accent) 60%, transparent);
      }
      .agent-switch.is-on .as-toggle::after { transform: translateX(18px); background: var(--accent); }
      .agent-switch.is-busy { opacity: .6; pointer-events: none; }
      @media (prefers-reduced-motion: reduce) {
        .agent-switch, .agent-switch .as-toggle, .agent-switch .as-toggle::after { transition: none; }
      }

      /* Reveal control for a password field. A password you cannot read is a
         password you mistype — and on a sign-up form the cost of a typo is an
         account you cannot get back into (owner, Sep 1: "ability to see the
         password before input"). Sits inside the field, never over the text. */
      .pw-eye {
        position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
        width: 30px; height: 30px; display: flex; align-items: center; justify-content: center;
        border-radius: 8px; border: 0; background: transparent; cursor: pointer;
        color: var(--text-faint); transition: color .15s ease, background .15s ease;
      }
      .pw-eye:hover { color: var(--text-secondary); background: var(--tint-06); }
      .pw-eye:focus-visible { outline: none; box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 25%, transparent); }

      /* Thread message actions (181): like / edit / delete.
         WO-02006 (owner, via Shahrukh): they sit BELOW the message body, not
         in the name line; iPhone parity keeps the same order, like first.
         Edit and delete stay hover-revealed — they are destructive-ish and
         should not clutter a conversation. The LIKE does not hide, because a
         count nobody can see until they hover is a count nobody knows exists. */
      .wo-msg-acts { display:inline-flex; align-items:center; gap:2px; margin-top:2px; }
      .wo-msg-act { padding:3px; border-radius:6px; background:transparent; border:0; cursor:pointer; line-height:0; }
      .wo-msg-act:hover { background:var(--tint-06); }
      /* WO-02006 review: hover-reveal alone leaves keyboard and touch users
         with invisible buttons — tabbing lands on a 0-opacity control, and a
         touch device sticks whatever was hovered. Reveal on focus (the button
         itself, or the row when the pencil takes focus into the editor) and
         keep edit/delete permanently visible where there is no hover.
         The touch rule is `.wo-msg-acts .wo-msg-act`, not `.wo-msg-act`:
         app.css loads AFTER console.css and Tailwind's `.opacity-0` is the
         same specificity (0,1,0), so the later rule won — edit/delete stayed
         invisible on phones (review, round 2). Two classes beat one. */
      .wo-msg-act:focus-visible, .group:focus-within .wo-msg-act { opacity:1; }
      @media (hover: none) { .wo-msg-acts .wo-msg-act { opacity:1; } }
      .wo-msg-like {
        display:inline-flex; align-items:center; gap:4px; padding:3px 7px; border-radius:999px;
        background:transparent; border:1px solid transparent; cursor:pointer; line-height:1;
        color:var(--text-faint); font-family:'Poppins','Inter',sans-serif; font-size:11px;
        transition: color .15s ease, background .15s ease, border-color .15s ease;
      }
      .wo-msg-like:hover { color:var(--text-soft); background:var(--tint-05); }
      .wo-msg-like.is-liked { color:var(--accent); border-color:color-mix(in srgb, var(--accent) 35%, transparent); background:color-mix(in srgb, var(--accent) 8%, transparent); }
      .wo-like-n { font-weight:600; }
      .wo-edited { margin-left:6px; font-size:10.5px; color:var(--text-faint); font-style:italic; }

      /* Editing happens in the bubble (183). The browser's own prompt() cannot
         hold a second line, so editing any multi-line comment silently
         flattened it — and it looked nothing like the rest of the console. */
      .wo-edit-wrap { margin-top:4px; }
      .wo-edit-box {
        width:100%; resize:none; overflow-y:auto; min-height:38px; max-height:260px;
        border-radius:10px; border-top-left-radius:3px;
        background:var(--ink-900,var(--bg-card)); border:1px solid color-mix(in srgb, var(--accent) 35%, transparent);
        padding:8px 12px; font-size:13px; line-height:1.5; color:var(--text);
        font-family:'Inter',system-ui,sans-serif;
      }
      .wo-edit-box:focus { outline:none; border-color:var(--accent); box-shadow:0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent); }
      .wo-edit-actions { display:flex; align-items:center; gap:8px; margin-top:6px; }
      .wo-edit-hint { flex:1; font-size:10.5px; color:var(--text-ghost); }
      .wo-edit-cancel, .wo-edit-save {
        border-radius:7px; padding:4px 12px; font-size:11.5px; font-weight:600; cursor:pointer;
        font-family:'Poppins','Inter',sans-serif; transition:background .15s ease, color .15s ease;
      }
      .wo-edit-cancel { background:transparent; border:1px solid var(--tint-12); color:var(--text-soft); }
      .wo-edit-cancel:hover { background:var(--tint-06); color:var(--text-secondary); }
      .wo-edit-save { background:var(--accent); border:1px solid var(--accent); color:var(--text-ink); }
      .wo-edit-save:hover { background:var(--accent-light); }

      /* App-only More button — same inset language as filters. */
      .board-more-btn {
        display:inline-flex; align-items:center; justify-content:center; width:36px; height:36px;
        padding:0; border-radius:10px; background:var(--tint-03); border:1px solid var(--tint-08);
        color:var(--text-soft); cursor:pointer; transition: color .15s, border-color .15s;
      }
      .board-more-btn:hover { color:var(--text); border-color:var(--tint-14); }
      /* When the sidebar is EXPANDED it already shows the full-label "New Work
         Order" CTA, so the board toolbar's duplicate is redundant — and it's
         exactly what pushed the Panel/Modal group onto a choppy second line in
         the narrower expanded layout (the sidebar eats ~166px). Hide the toolbar
         duplicate while expanded; it returns the moment the sidebar collapses to
         icons, where its bare "+" is no substitute. */
      /* Kanban geometry 1:1 (reference): fixed 288px lanes, 14px gutters,
         16px radius, .06 hairline; header is part of the lane surface (no
         divider bar, no count pill) — dot 8px + tracked 10.5px label + plain
         10px count. Column padding 12px = header 16/18/12 + body 0/12/12. */
      section[data-view="tasks"].active #kanban { gap: 14px; }
      /* flex-grow: 1 (was flex: 0 0 288px): lanes share leftover width on wide and
         ultrawide monitors instead of leaving a dead zone right of COMPLETED;
         288px stays the floor so laptops still get horizontal scroll. */
      :is(section[data-view="tasks"],section[data-view="compliance"]).active .kanban-col { background: var(--tint-015); border-color: var(--tint-06); border-radius: 16px; flex: 1 0 288px; min-width: 288px; }
      :is(section[data-view="tasks"],section[data-view="compliance"]).active .kanban-col-header { background: var(--tint-015) !important; border-color: transparent; padding: 16px 18px 4px; border-radius: 16px 16px 0 0; }
      :is(section[data-view="tasks"],section[data-view="compliance"]).active .kanban-col-title { font-size: 10.5px; font-weight: 400; letter-spacing: 1.5px; color: var(--text-secondary); }
      :is(section[data-view="tasks"],section[data-view="compliance"]).active .kanban-col-count { font-family: 'Poppins','Inter',sans-serif; font-size: 10px; color: var(--text-muted); background: transparent; padding: 0; }
      /* padding-top gives scroll-room so a hovered/highlighted first card isn't
         clipped at the top by the body's overflow-y:auto (8px moved down from
         the header's bottom padding above, so the visual gap is unchanged). */
      :is(section[data-view="tasks"],section[data-view="compliance"]).active .kanban-col-body { padding: 8px 12px 12px; gap: 11px; }
      :is(section[data-view="tasks"],section[data-view="compliance"]).active .board-empty-lane { padding: 34px 0; text-align: center; font-family: 'Poppins','Inter',sans-serif; font-size: 12px; color: var(--text-ghost); border: 1px dashed var(--tint-07); border-radius: 12px; }
      /* The Scheduled lane's last line ("12 more scheduled after Dec 22 - see
         Calendar"): quiet text under the cards, a link rather than another card. */
      .lane-beyond { display: block; width: 100%; padding: 8px 6px 2px; background: transparent; border: 0; border-radius: 8px; font-family: 'Poppins','Inter',sans-serif; font-size: 11.5px; line-height: 1.4; color: var(--text-muted); text-align: center; cursor: pointer; transition: color .15s ease; }
      .lane-beyond:hover { color: var(--accent); }
      .lane-beyond:focus-visible { outline: none; color: var(--accent); box-shadow: 0 0 0 3px var(--accent-dim); }
      @media (prefers-reduced-motion: reduce) { .lane-beyond { transition: none; } }
      /* Stat row: Task Distribution / Closed-per-Week / Needs attention. Reference
         surface is a soft vertical gradient panel, not a flat card. */
      /* Collapse/expand motion for the KPI strip. The board resizes on every
         frame of this (see _animateKpi) so the calendar grows with it. */
      /* 180ms, deliberately. It was .26s, but the JS ended the animation on
         opacity's transitionend at .18s -- so what shipped for months, and what
         the board is tuned to feel like, was 180ms with the last third clipped.
         Making the motion honestly 180ms keeps that snap without the clipping. */
      .kpi-anim { transition: max-height .18s cubic-bezier(.32,.72,0,1), opacity .14s ease; }
      @keyframes kpiBarIn { from { opacity:0; transform:translateY(-4px); } to { opacity:1; transform:none; } }
      .kpi-bar-in { animation: kpiBarIn .22s cubic-bezier(.32,.72,0,1) both; }
      @media (prefers-reduced-motion: reduce) {
        .kpi-anim { transition:none !important; }
        .kpi-bar-in { animation:none !important; }
      }
      /* Expanded state: just the control, no panel chrome. */
      .kpi-peek-bare { background:none !important; border:none !important; padding:0 !important; margin-top:-6px; }
      /* Compliance type (PERMIT / LICENSE) on its own line — it used to share a
         row with the ref number, doc icon and renew-by date and truncated to
         "PER…", hiding the one thing the card is scanned for. */
      .cmp-type-chip { display:inline-flex; align-items:center; font-family:'Poppins','Inter',sans-serif; font-size:9.5px; font-weight:700; letter-spacing:1.1px; text-transform:uppercase; color:var(--violet-soft); background:color-mix(in srgb, var(--violet) 13%, transparent); border:1px solid color-mix(in srgb, var(--violet) 32%, transparent); border-radius:6px; padding:3px 8px; }
      .kpi-peek { display:flex; align-items:center; gap:14px; flex-wrap:wrap; padding:9px 14px; border-radius:12px; background:var(--bg-panel); border:1px solid var(--tint-05); }
      .kpi-peek b { font-family:'Poppins','Inter',sans-serif; font-size:13px; font-weight:600; color:var(--text); }
      .kpi-peek span.lbl { font-family:'Poppins','Inter',sans-serif; font-size:11px; color:var(--text-muted); }
      .kpi-peek .sep { width:1px; height:13px; background:var(--tint-10); }
      .kpi-toggle { margin-left:auto; display:inline-flex; align-items:center; gap:6px; padding:5px 11px; border-radius:8px; border:1px solid var(--tint-10); background:var(--tint-03); color:var(--text-soft); font-family:'Poppins','Inter',sans-serif; font-size:11px; cursor:pointer; }
      .kpi-toggle:hover { border-color:color-mix(in srgb, var(--accent) 40%, transparent); color:var(--text); }
      /* In the toolbar it is one control among several, not the trailing
         item of a summary bar, so it drops the auto left margin. */
      .kpi-toggle-inline { margin-left:0; }
      .board-stat-card { background: var(--bg-panel); border: 1px solid var(--tint-055); border-radius: 16px; padding: 15px 18px; box-shadow: var(--shadow-card); }
      /* Reference tile label: 9px / 1.5px tracked / regular weight - and the
         week delta is a bare colored glyph+number, NOT a pill. */
      .board-stat-head { display:flex; align-items:flex-start; justify-content:space-between; gap:8px; font-family:'Poppins','Inter',sans-serif; font-size: 9px; font-weight: 400; letter-spacing: 1.5px; text-transform: uppercase; color: var(--text-muted); margin-bottom: 12px; }
      .board-stat-delta { display:inline-flex; align-items:center; gap:3px; font-family:'Poppins','Inter',sans-serif; font-size: 9.5px; font-weight: 400; letter-spacing: 0; }
      .board-stat-delta.up   { color:var(--green); }
      .board-stat-delta.down { color:var(--urgent); }
      /* Entrance animation (stellar-v3 "ccRise"): stat row + columns fade+rise
         in staggered, once, only on the FIRST paint of a board visit - JS
         (playBoardEntrance in board.js) adds/removes .board-anim-run on
         #boardKPIs and #kanban so a realtime refresh mid-session never
         replays it (that read as a jarring flash in the first redesign pass). */
      /* KPI strip proportions: wide "distribution" + "closed/week", narrower
         gauge (reference is ~2:2:1, not equal thirds). Real CSS rule (id
         beats the .md:grid-cols-3 fallback) since the Tailwind CDN doesn't JIT
         arbitrary grid-template values reliably. minmax(0,..) prevents blowout. */
      #boardKPIs { gap: 14px; margin-bottom: 18px; }
      /* THREE tracks for three cards. This declared four until 2026-08-20, when
         the On-time SLA gauge was removed and its track was left behind — an
         empty 0.95fr of 4.1fr, i.e. 23% of the row, which is why the expanded
         stats stopped short of the right edge while the collapsed summary bar
         (no grid) ran the full width. Owner: "it spans when uncollapse but not
         when it's collapse." */
      @media (min-width: 768px) { #boardKPIs { grid-template-columns: minmax(0,1.3fr) minmax(0,1fr) minmax(0,.95fr); } }
      /* Mark-as-unread hover dot on inbox rows */
      .markUnreadBtn { display:inline-flex; align-items:center; justify-content:center; width:24px; height:24px; border-radius:8px; color:var(--text-faint); opacity:0; cursor:pointer; transition:opacity .15s ease, color .15s ease, background .15s ease; }
      .msgRow:hover .markUnreadBtn { opacity:1; }
      .markUnreadBtn:hover { color:var(--accent); background:color-mix(in srgb, var(--accent) 10%, transparent); }
      /* The unread badge IS the mark-read button, so it must be visible without
         hovering — the hollow "mark unread" ring stays a hover affordance. Its
         size comes from the inline style, so the 24px box is released here. */
      .markUnreadBtn.is-unread { opacity:1; width:auto; height:auto; }
      .markUnreadBtn.is-unread:hover { background:var(--accent-hover); color:var(--text-ink); }
      /* Messages inbox filters. A pill that matches the type tabs beside it,
         opening a searchable list — a native <select> could not hold a search
         and did not look like anything else in the product. */
      .msgf-wrap { position:relative; display:inline-block; }
      .msgf-btn { display:inline-flex; align-items:center; gap:6px; max-width:230px;
                  padding:8px 12px; border-radius:11px; background:var(--bg-card-2);
                  border:1px solid var(--tint-07); cursor:pointer;
                  font-family:'Poppins','Inter',sans-serif; font-size:12px; color:var(--text-muted);
                  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
                  transition:color .15s ease, border-color .15s ease, background .15s ease; }
      .msgf-btn:hover { color:var(--text-body); border-color:var(--tint-16); }
      .msgf-btn.on { background:var(--bg-well); color:var(--text); border-color:color-mix(in srgb, var(--accent) 34%, transparent); }
      .msgf-menu { position:absolute; left:0; top:100%; margin-top:6px; z-index:40;
                   min-width:260px; max-width:320px; display:flex; flex-direction:column;
                   background:var(--bg-card); border:1px solid var(--tint-14);
                   border-radius:12px; box-shadow:0 18px 50px var(--shade-60); overflow:hidden; }
      /* The Filters button's own menu: it holds two sections, so it is wider
         than a plain picker. min()/max() so it still fits a very narrow phone.
         Which side it opens from is decided in positionMsgFilterMenu(), because
         the bar wraps and one fixed side cannot suit both layouts. */
      .msgf-menu-wide { min-width:min(330px, calc(100vw - 24px)); max-width:min(340px, calc(100vw - 24px)); }
      .msgf-search { flex:none; width:100%; padding:9px 12px; background:var(--bg-card-2); color:var(--text);
                     border:none; border-bottom:1px solid var(--tint-08);
                     font-family:'Poppins','Inter',sans-serif; font-size:12px; outline:none; }
      .msgf-search::placeholder { color:var(--text-faint); }
      .msgf-list { display:block; max-height:260px; overflow-y:auto; }
      .msgf-opt { display:flex; align-items:center; gap:8px; padding:8px 12px; cursor:pointer;
                  font-family:'Poppins','Inter',sans-serif; font-size:12px; color:var(--text-secondary); }
      .msgf-opt:hover { background:var(--tint-05); }
      .msgf-opt.on { background:color-mix(in srgb, var(--accent) 10%, transparent); color:var(--text); }
      .msgf-tick { width:12px; flex:none; color:var(--accent); font-size:11px; }
      .msgf-lbl { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
      .msgf-empty { padding:10px 12px; font-family:'Poppins','Inter',sans-serif;
                    font-size:12px; color:var(--text-faint); }
      /* Section label inside the Filters menu (Show / From). Same 9.5px
         letterspaced caps the drawer's labels use, so the two panels read as
         one product. */
      .msgf-sec { padding:10px 12px 6px; font-family:'Poppins','Inter',sans-serif;
                  font-size:9.5px; letter-spacing:1.4px; text-transform:uppercase;
                  color:var(--text-muted); }
      .msgf-clear { display:inline-flex; align-items:center; gap:6px; padding:8px 12px;
                    border-radius:11px; background:color-mix(in srgb, var(--accent) 10%, transparent);
                    border:1px solid color-mix(in srgb, var(--accent) 30%, transparent); cursor:pointer;
                    font-family:'Poppins','Inter',sans-serif; font-size:12px; color:var(--accent); }
      .msgf-clear:hover { background:color-mix(in srgb, var(--accent) 18%, transparent); }
      /* Location code chip on the user profile — dense enough that a whole
         estate is scannable instead of scrollable. */
      .up-chip { display:inline-flex; align-items:center; justify-content:center;
                 font-family:'JetBrains Mono','SFMono-Regular',monospace; font-size:11px;
                 color:#5ad89c; background:color-mix(in srgb, var(--green) 8%, transparent);
                 border:1px solid color-mix(in srgb, var(--green) 22%, transparent); border-radius:7px;
                 padding:4px 8px; cursor:pointer; white-space:nowrap;
                 transition:background .14s ease, border-color .14s ease, color .14s ease; }
      .up-chip:hover { background:color-mix(in srgb, var(--green) 18%, transparent); border-color:color-mix(in srgb, var(--green) 50%, transparent); color:var(--green-soft); }
      /* Calendar board view */
      /* ---- Pop-out windows -------------------------------------------------
         A pop-out is this same page with ?popout=<view>. It shares the session,
         the env lock and every renderer; only the chrome differs. Nothing here
         is a second layout - it is the same layout with the navigation removed,
         which is why a popped-out view behaves exactly like the docked one. */
      body.is-popout #sidebar,
      body.is-popout #sidebarBackdrop,
      body.is-popout #popoutBtn { display:none !important; }
      /* The brand segment (workspace switcher) is sidebar chrome: a pop-out
         window has no rail, so it carries only the controls (WO-02007). */
      body.is-popout #topBrand { display:none !important; }
      body.is-popout #mainCol { margin-left:0 !important; }
      body.is-popout #popoutBar { display:inline-flex !important; }
      /* The hamburger only opens the sidebar this window doesn't have. */
      body.is-popout .topbar button[onclick="openSidebar()"] { display:none !important; }
      .cal-grid { display:grid; grid-template-columns:repeat(7,minmax(0,1fr)); gap:6px; }
      .cal-dow { font:600 9.5px 'Poppins',sans-serif; letter-spacing:1.2px; color:var(--text-muted); padding:2px 4px; }
      .cal-cell { min-height:112px; background:var(--bg-card,var(--bg-card)); border:1px solid var(--tint-06); border-radius:12px; padding:6px; display:flex; flex-direction:column; gap:4px; }
      .cal-cell.cal-blank { background:transparent; border-color:transparent; }
      .cal-cell.cal-today { border-color:color-mix(in srgb, var(--accent) 45%, transparent); box-shadow:0 0 0 1px color-mix(in srgb, var(--accent) 18%, transparent) inset; }
      .cal-cell.cal-dragover { outline:2px dashed var(--accent); outline-offset:-3px; background:color-mix(in srgb, var(--accent) 5%, transparent); }
      /* The tray is a drop target too — drag a chip back onto it to unschedule.
         Amber rather than the mint used for day cells: this drop REMOVES a date,
         so it should not look like the same gesture as setting one. */
      .cal-tray.cal-dragover { outline:2px dashed var(--amber); outline-offset:-3px; background:color-mix(in srgb, var(--amber) 6%, transparent); }
      .cal-daynum { font:600 11px 'Poppins',sans-serif; color:var(--text-soft); }
      .cal-today .cal-daynum { color:var(--accent); }
      .cal-chips { display:flex; flex-direction:column; gap:3px; overflow:hidden; }
      .cal-chip { font:500 10.5px 'Poppins',sans-serif; color:var(--text-body); background:var(--tint-04); border:1px solid var(--tint-07); border-radius:7px; padding:3px 6px; cursor:grab; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
      .cal-chip:hover { background:var(--tint-09); }
      .cal-chip-time { color:var(--accent-info-light); font-weight:600; }
      .cal-chip-code { color:var(--accent); font-weight:600; }
      .cal-tray { width:250px; flex:none; background:var(--bg-raised,var(--bg-raised)); border:1px solid var(--tint-07); border-radius:14px; padding:12px; max-height:100%; overflow-y:auto; }
      /* Month cells share leftover container height (calendar is viewport-sized) */
      /* The month grid used to scroll inside itself (min 112px rows + overflow-y),
         so a 5- or 6-week month always hid its last row or two behind a second
         scrollbar - owner, Aug 20: "make the height bigger, I don't like that I
         still have to scroll". It now declares its real row count and divides
         the available height between them, so every week of the month is on
         screen at once and the grid never scrolls. Cells share the space; the
         day's chip list keeps its own overflow and the header shows a count when
         a day holds more than fits. */
      .cal-grid-fill { flex:1; min-height:0; overflow:hidden; grid-template-rows:repeat(var(--cal-rows,6),minmax(0,1fr)); align-content:stretch; }
      /* Cells must match their grid track, not exceed it. .cal-cell carries a
         112px min-height for the OTHER views; inside the month grid that floor
         is taller than a 1fr track on most screens, so a busy day grew past its
         row and drew over the week below (owner, Aug 20: "the calendar ui is
         overlapping"). Inside .cal-grid-fill the cell takes its height from the
         track and clips; the chip list is the part allowed to shrink. */
      .cal-grid-fill .cal-cell { min-height:0; overflow:hidden; }
      .cal-grid-fill .cal-cell-head { flex:none; }
      /* flex-basis 0, not auto: the box must take exactly the room left in the
         cell, never its own content height. calClampMonthCells measures
         clientHeight to decide how many chips fit, and with `auto` that
         measurement would be the overflow itself and always "fit". */
      .cal-grid-fill .cal-chips { flex:1 1 0; min-height:0; gap:2px; }
      /* Denser chips in month view only - three of them now fit a ~96px row, so
         the common day renders whole instead of being cut mid-chip. Week, Day
         and the tray keep the roomier chip. */
      .cal-grid-fill .cal-chip { font-size:10px; padding:2px 5px; border-radius:6px; }
      /* flex:none is the whole point: a chip is a fixed-height row, never a
         share of what is left. Without it seven chips in one cell each shrank
         to a 2px sliver and the day became unreadable (owner, Sep 21). The
         cap in board-calendar.js decides how many are drawn; this decides that
         the ones drawn are legible. */
      .cal-grid-fill .cal-chips > * { flex:none; }
      /* The overflow row. It reads as a chip, not a badge, because it sits in
         the stack and does the same thing a chip does - it opens the day. */
      .cal-more-row { font:600 10px 'Poppins',sans-serif; color:var(--text-muted); background:var(--tint-04);
        border:1px dashed var(--tint-10); border-radius:6px; padding:2px 5px; cursor:pointer;
        white-space:nowrap; overflow:hidden; text-overflow:ellipsis; transition:color .15s ease, background .15s ease, border-color .15s ease; }
      .cal-more-row:hover { color:var(--text); background:var(--tint-09); border-color:color-mix(in srgb, var(--accent) 45%, transparent); }
      /* A bare day is a create target: click it to open a new work order already
         scheduled for that date. Chips inside keep their own click (open ticket). */
      .cal-cell:not(.cal-blank) { cursor:pointer; transition:border-color .15s ease, background .15s ease; }
      .cal-cell:not(.cal-blank):hover { border-color:color-mix(in srgb, var(--accent) 35%, transparent); background:color-mix(in srgb, var(--accent) 3.5%, transparent); }
      .cal-cell .cal-chip { cursor:pointer; }
      .cal-cell-head { display:flex; align-items:center; justify-content:space-between; gap:4px; }
      /* Day rows fill leftover height and still grow when an hour is busy
         (the .cal-dayview rule further down). */
      .cal-nav-btn.cal-mode-on { background:color-mix(in srgb, var(--accent) 14%, transparent); border-color:color-mix(in srgb, var(--accent) 50%, transparent); color:var(--accent); }
      /* Week view: hour-by-day time grid (0001) */
      /* Week body = flex rows (a stretched grid item's height depends on its
         track, which made content-sized rows circular and let chips draw over
         the next hour). Rows size to their tallest cell. */
      .cal-hint { display:inline-flex; align-items:center; gap:5px; margin-left:10px; font:400 11px 'Poppins','Inter',sans-serif; color:var(--text-faint); cursor:default; }
      .cal-hint svg { opacity:.8; }
      @media (max-width: 1100px) { .cal-hint { display:none; } }
      /* Collapsed tray: a 34px rail with a vertical label. Still a drop target,
         so dragging a chip out of the calendar to unschedule keeps working. */
      .cal-tray { transition: width .26s cubic-bezier(.32,.72,0,1), padding .26s cubic-bezier(.32,.72,0,1); overflow-x:hidden; position:relative; }
      .cal-tray-collapsed { width:34px; padding:8px 0; overflow:hidden; }
      .cal-tray-rail { display:none; width:100%; height:100%; flex-direction:column; align-items:center; gap:10px; padding:4px 0; background:none; border:none; color:var(--text-muted); cursor:pointer; }
      .cal-tray-collapsed .cal-tray-rail { display:flex; }
      .cal-tray-rail:hover { color:var(--text); }
      .cal-tray-rail-label { writing-mode:vertical-rl; font:600 9.5px 'Poppins','Inter',sans-serif; letter-spacing:1.6px; }
      .cal-tray-rail-count { font:600 10px 'Poppins','Inter',sans-serif; color:var(--text-ink); background:var(--accent); border-radius:5px; padding:1px 5px; }
      .cal-tray-content { display:block; width:226px; }
      .cal-tray-collapsed .cal-tray-content { display:none; }
      .cal-tray-collapse { display:inline-flex; align-items:center; justify-content:center; width:20px; height:20px; border-radius:6px; border:none; background:none; color:var(--text-muted); cursor:pointer; flex:none; }
      .cal-tray-collapse:hover { background:var(--tint-07); color:var(--text); }
      @media (prefers-reduced-motion: reduce) { .cal-tray { transition:none; } }
      /* Border matches the Unscheduled tray exactly (Andrea, Aug 8). */
      .cal-wk-wrap { background:var(--bg-card,var(--bg-card)); border:1px solid var(--tint-07); border-radius:14px; display:flex; flex-direction:column; }
      .cal-wk-row { display:flex; align-items:stretch; }
      /* Day headings are columns too — without a flex basis they bunched up on
         the left instead of sitting over their own day. */
      .cal-wk-row > .cal-wk-cell, .cal-wk-row > .cal-wk-head { flex:1 1 0; min-width:0; }
      .cal-wk-row > .cal-hour-label, .cal-wk-row > .cal-wk-gutter-head { width:56px; flex:none; }
      .cal-wk-headrow { position:sticky; top:0; z-index:3; background:var(--bg-card,var(--bg-card)); flex:none; }
      /* Hour rows share the leftover height so the grid reaches the bottom of
         its box, and still grow past it when an hour is busy. */
      .cal-wk-wrap > .cal-wk-row:not(.cal-wk-headrow) { flex:1 1 auto; }
      .cal-wk-gutter-head { border-bottom:1px solid var(--tint-07); }
      .cal-wk-head { padding:8px 4px; text-align:center; font:600 10px 'Poppins',sans-serif; letter-spacing:1px; color:var(--text-muted); border-bottom:1px solid var(--tint-07); }
      .cal-wk-head span { color:var(--text); font-size:11px; }
      .cal-wk-head.cal-wk-head-today { color:var(--accent); box-shadow:0 -2px 0 0 var(--accent) inset; }
      .cal-wk-head-today span { color:var(--accent); }
      .cal-wk-gutter { padding:6px 8px 0 0; border-bottom:1px solid var(--tint-05); }
      .cal-wk-gutter-now { color:var(--accent); }
      .cal-wk-cell { min-height:44px; border-bottom:1px solid var(--tint-05); border-left:1px solid var(--tint-04); padding:4px; display:flex; flex-direction:column; gap:4px; cursor:pointer; transition:background .15s ease; }
      .cal-wk-cell:hover { background:color-mix(in srgb, var(--accent) 2.5%, transparent); }
      .cal-wk-cell.cal-wk-today { background:color-mix(in srgb, var(--accent) 4.5%, transparent); }
      .cal-wk-cell.cal-dragover { outline:2px dashed var(--accent); outline-offset:-2px; background:color-mix(in srgb, var(--accent) 8%, transparent); }
      .cal-wk-cell .cal-chip { white-space:normal; font-size:10.5px; line-height:1.4; padding:4px 6px; flex:none; max-height:5.2em; overflow:hidden; }
      /* Day view: hour rows (0001) */
      .cal-dayview { background:var(--bg-card,var(--bg-card)); border:1px solid var(--tint-07); border-radius:14px; overflow:hidden; }
      .cal-dayview .cal-hour-row { flex:1 1 auto; }
      .cal-hour-row { display:flex; gap:10px; align-items:flex-start; padding:6px 10px; min-height:48px; border-bottom:1px solid var(--tint-05); }
      .cal-hour-row:last-child { border-bottom:none; }
      .cal-hour-row.cal-dragover { outline:2px dashed var(--accent); outline-offset:-3px; background:color-mix(in srgb, var(--accent) 5%, transparent); }
      .cal-hour-row.cal-hour-now { background:color-mix(in srgb, var(--accent) 5%, transparent); box-shadow:2px 0 0 0 var(--accent) inset; }
      .cal-hour-label { width:56px; flex:none; padding-top:3px; font:600 10px 'Poppins',sans-serif; letter-spacing:.6px; color:var(--text-faint); text-align:right; }
      .cal-hour-now .cal-hour-label { color:var(--accent); }
      .cal-hour-chips { flex:1; min-width:0; display:flex; flex-wrap:wrap; gap:4px; }
      .cal-hour-chips .cal-chip { max-width:340px; }
      /* 15-minute snap-on-drop: live badge while a chip is dragged over an
         hour row / week cell (0005) */
      .cal-hour-row, .cal-wk-cell { position:relative; }
      .cal-snap-label { position:absolute; top:3px; right:5px; z-index:5; pointer-events:none; font:600 10px 'Poppins',sans-serif; color:var(--text-ink); background:var(--accent); padding:2px 7px; border-radius:6px; box-shadow:0 4px 12px var(--shade-40); }
      /* Day view quarter slots: chips sit in their :00/:15/:30/:45 zone and
         each zone is its own drop target (0006) */
      .cal-hour-quarters { flex:1; min-width:0; align-self:stretch; display:flex; flex-direction:column; }
      .cal-q { flex:0 0 auto; min-height:22px; position:relative; display:flex; flex-wrap:wrap; gap:4px; align-items:flex-start; align-content:flex-start; padding:1px 2px; cursor:pointer; transition:background .15s ease; }
      .cal-q:hover { background:color-mix(in srgb, var(--accent) 2.5%, transparent); }
      .cal-q + .cal-q { border-top:1px dashed var(--tint-04); }
      .cal-q.cal-dragover { outline:2px dashed var(--accent); outline-offset:-2px; background:color-mix(in srgb, var(--accent) 8%, transparent); }
      .cal-q .cal-chip { max-width:340px; }
      .cal-tray-head { font:600 10px 'Poppins',sans-serif; letter-spacing:1.4px; color:var(--text-muted); margin-bottom:8px; }
      .cal-tray .cal-chip { white-space:normal; margin-bottom:6px; }
      .cal-nav-btn { padding:5px 10px; border-radius:8px; background:var(--tint-04); border:1px solid var(--tint-10); color:var(--text-secondary); font:500 12px 'Poppins',sans-serif; cursor:pointer; }
      .cal-nav-btn:hover { background:var(--tint-09); }
      @keyframes boardRise { 0% { opacity:0; transform:translateY(10px); } 100% { opacity:1; transform:translateY(0); } }
      #boardKPIs.board-anim-run > * { animation: boardRise .5s ease both; }
      #boardKPIs.board-anim-run > *:nth-child(1) { animation-delay: .02s; }
      #boardKPIs.board-anim-run > *:nth-child(2) { animation-delay: .08s; }
      #boardKPIs.board-anim-run > *:nth-child(3) { animation-delay: .14s; }
      #boardKPIs.board-anim-run > *:nth-child(4) { animation-delay: .2s; }
      #kanban.board-anim-run > .kanban-col { animation: boardRise .4s ease both; }
      #kanban.board-anim-run > .kanban-col:nth-child(1) { animation-delay: .06s; }
      #kanban.board-anim-run > .kanban-col:nth-child(2) { animation-delay: .10s; }
      #kanban.board-anim-run > .kanban-col:nth-child(3) { animation-delay: .14s; }
      #kanban.board-anim-run > .kanban-col:nth-child(4) { animation-delay: .18s; }
      #kanban.board-anim-run > .kanban-col:nth-child(5) { animation-delay: .22s; }
      #kanban.board-anim-run > .kanban-col:nth-child(6) { animation-delay: .26s; }
      @media (prefers-reduced-motion: reduce) {
        #boardKPIs.board-anim-run > *, #kanban.board-anim-run > .kanban-col { animation: none; }
      }

      /* v3 shared keyframes (design/motion spec §4.1): ccRise = panel entrance
         (same curve as boardRise), ccblink = the LIVE/status pulse dot. Used by
         the Operations Dashboard's panels + live badges. */
      @keyframes ccRise { 0% { opacity:0; transform:translateY(10px); } 100% { opacity:1; transform:translateY(0); } }
      /* Sign-up flow spec: connected toast pops in; invite-verify spinner. */
      @keyframes ccPop { 0% { opacity:0; transform:scale(.92); } 100% { opacity:1; transform:scale(1); } }
      @keyframes ccSpin { to { transform:rotate(360deg); } }
      @keyframes ccblink { 0%,100% { opacity:1; } 50% { opacity:.35; } }
      /* Stellar loader (infinity mark) — the brand loading state. The dash
         sweeps around the ∞ path; presets: full-page boot, inline panel,
         in-button. Colors: trail #12b47d, lead #4ce0ac. */
      @keyframes infTrace { to { stroke-dashoffset: 100; } }
      @keyframes infComet { from { offset-distance: 100%; } to { offset-distance: 0%; } }
      @keyframes ldrDots { 0%,20% { opacity:.2; } 50% { opacity:1; } 80%,100% { opacity:.2; } }
      @keyframes ldrBar { 0% { transform: translateX(-100%); } 100% { transform: translateX(340%); } }
      @media (prefers-reduced-motion: reduce) { .inf-trace { animation: none !important; stroke-dasharray: 40 60; } .ldr-bar, .ldr-dot { animation: none !important; } .inf-comet { display: none; } }
      /* Map "command center" motion (stellar-v3 spec §4.5): hotspot ping rings
         and pin drop-in. (The radar sweep overlay was removed — owner call.) */
      @keyframes ccPing { 0% { transform: scale(.6); opacity:.7; } 100% { transform: scale(2.7); opacity:0; } }
      @keyframes ccDrop { 0% { transform: translateY(-58px) scale(.35); opacity:0; } 62% { transform: translateY(4px) scale(1.05); opacity:1; } 80% { transform: translateY(-2px) scale(.98); } 100% { transform: translateY(0) scale(1); opacity:1; } }
      /* Animated map canvas: the Google/Leaflet map fills #mapContainer; these
         overlays sit on top (decorative ones are pointer-events:none so the map
         stays fully interactive). */
      #mapWrap { position: relative; border-radius: 16px; overflow: hidden; border: 1px solid var(--tint-10); }
      #mapWrap > #mapContainer { border: 0; border-radius: 0; }
      .map-vignette { position:absolute; inset:0; pointer-events:none; z-index:6; box-shadow: inset 0 0 140px 30px var(--shade-60); }
      .map-live { position:absolute; top:14px; left:14px; display:flex; align-items:center; gap:8px; padding:7px 12px; border-radius:9px; background:var(--map-hud); border:1px solid var(--tint-10); backdrop-filter:blur(6px); z-index:20; font-family:'Poppins','Inter',sans-serif; font-size:10px; letter-spacing:1.5px; color:var(--text); }
      .map-live-dot { width:8px; height:8px; border-radius:50%; background:var(--green); animation:ccblink 1.4s ease-in-out infinite; }
      .map-live-sep { width:1px; height:12px; background:var(--tint-14); }
      .map-live #mapSyncTime { letter-spacing:0; color:var(--text-muted); }
      .map-legend-ov { position:absolute; left:14px; bottom:14px; padding:11px 13px; border-radius:12px; background:var(--map-hud); border:1px solid var(--tint-10); backdrop-filter:blur(6px); z-index:20; }
      .map-health { position:absolute; right:14px; top:14px; bottom:14px; width:290px; max-width:42vw; border-radius:14px; background:var(--map-hud); border:1px solid var(--tint-10); backdrop-filter:blur(9px); z-index:20; display:flex; flex-direction:column; overflow:hidden; transition:transform .3s cubic-bezier(.32,.72,0,1), opacity .25s ease; }
      .map-health.collapsed { transform:translateX(calc(100% + 22px)); opacity:0; pointer-events:none; }
      .map-health-reopen { position:absolute; right:14px; top:14px; z-index:20; display:flex; align-items:center; gap:7px; padding:9px 13px; border-radius:11px; background:var(--map-hud); border:1px solid var(--tint-14); backdrop-filter:blur(9px); cursor:pointer; font:500 12px 'Poppins','Inter',sans-serif; color:var(--text-secondary); }
      .map-health-reopen:hover { border-color:color-mix(in srgb, var(--accent) 45%, transparent); color:var(--text); }
      .map-health-collapse { display:flex; align-items:center; justify-content:center; width:26px; height:26px; border-radius:8px; color:var(--text-muted); cursor:pointer; flex:none; }
      .map-health-collapse:hover { background:var(--tint-07); color:var(--text); }
      @media (max-width: 900px) { .map-health { display:none; } }
      @media (prefers-reduced-motion: reduce) { .map-live-dot { animation: none; } }
      @media (prefers-reduced-motion: reduce) {
        [style*="animation:ccRise"], [style*="animation: ccRise"] { animation: none !important; }
      }
      /* Operations Dashboard — same rule as the Board: no view frame, the
         screen sits directly on the shared app backdrop (.aurora). */
      section[data-view="ops"].active {
        font-family: 'Poppins', 'Inter', sans-serif; color: var(--text);
      }

      /* ============================================================
         Work-order detail: centered MODAL presentation (stellar-v3
         reference), an alternative to the right-side .drawer above. Which one
         opens is a per-user choice (STATE.woView, persisted) flipped by the
         Panel/Modal toggle in either presentation's header - see
         openWorkOrder()/detail-forms.js. Reuses the drawer's existing section
         builders (vendor panel, thread, photos, related tasks, invoices) and
         the SAME element ids for the thread/composer (#woThread,
         #commentInput, ...) so posting, mentions, and realtime refresh are
         unchanged code, just laid out into the reference's 3-zone frame.
      ============================================================ */
      /* Class-based (not #id) so it ties in specificity with Tailwind's
         .hidden and the SAME "toggle .hidden on this element" pattern the
         other modals (.modal-backdrop) already use works identically here -
         app.css loads after this block, so .hidden wins the tie and actually
         hides it (an #id backdrop would out-specificity .hidden and never
         disappear). */
      .wo-modal-backdrop {
        position: fixed; inset: 0; z-index: 60; background: var(--backdrop);
        display:flex; align-items:center; justify-content:center; padding: 26px;
        animation: ccFadeIn .2s ease both;
      }
      @keyframes ccFadeIn { from{opacity:0} to{opacity:1} }
      @keyframes ccModalIn { 0%{opacity:0;transform:translateY(18px) scale(.965)} 100%{opacity:1;transform:translateY(0) scale(1)} }
      /* Sliding-highlight skeleton (quote drafting state). Pair with a
         background-size:200% 100% gradient; reduced-motion guard below. */
      @keyframes ccShimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
      @media (prefers-reduced-motion: reduce) { [style*="ccShimmer"] { animation: none !important; } }
      /* Composer focus ring (video req V2): the message box gets the cyan/teal
         accent stroke while the user is typing in it. */
    /* Drop target for the work-order composer (owner, Sep 3). */
    #drawer.dnd-over #drawerContent, #woModalBackdrop.dnd-over #woModalContent { outline: 2px dashed var(--accent); outline-offset: -6px; border-radius: 12px; }
    #drawer.dnd-over::after, #woModalBackdrop.dnd-over::after { content: 'Drop photos or PDFs to attach'; position: fixed; left: 50%; bottom: 28px; transform: translateX(-50%); font: 600 12px 'Poppins', 'Inter', sans-serif; color: var(--text-ink); background: var(--accent); padding: 8px 14px; border-radius: 999px; z-index: 60; pointer-events: none; }
      .wo-composer { transition: border-color .18s ease, box-shadow .18s ease; }
      .wo-composer:focus-within { border-color: color-mix(in srgb, var(--accent) 55%, transparent) !important; box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 22%, transparent), 0 0 18px -6px color-mix(in srgb, var(--accent) 35%, transparent); }
      /* A disabled field must LOOK disabled: the role editor's locked name +
         security tier still rendered like live inputs (chevron and all), so a
         system role read as broken rather than fixed. */
      .input.is-locked, .input:disabled { opacity: .55; cursor: not-allowed; background: var(--tint-02); }
      select.input.is-locked { appearance: none; -webkit-appearance: none; background-image: none; }
      .wo-modal-card {
        position: relative; width: 100%; max-width: 1040px; height: 86vh; max-height: 880px;
        background: var(--head-b); border: 1px solid var(--tint-12); border-radius: 20px;
        box-shadow: var(--shadow-modal); display:flex; flex-direction:column; overflow:hidden;
        animation: ccModalIn .34s cubic-bezier(.22,.61,.36,1) both; font-family: 'Poppins', 'Inter', sans-serif; color: var(--text);
      }
      @media (prefers-reduced-motion: reduce) { .wo-modal-backdrop, .wo-modal-card { animation: none; } }
      .wo-modal-head { flex:none; padding: 20px 24px 16px; border-bottom: 1px solid var(--tint-08); background: var(--bg-head); }
      .wo-modal-body { flex:1; min-height:0; display:flex; }
      .wo-modal-main { flex: 1.72; min-width:0; display:flex; flex-direction:column; border-right: 1px solid var(--tint-08); }
      .wo-modal-rail { width: 352px; flex:none; overflow-y:auto; padding: 16px 16px 24px; background: var(--bg-canvas); }
      .wo-modal-rail::-webkit-scrollbar { width:6px; }
      .wo-modal-section { border:1px solid var(--tint-08); border-radius:13px; background:var(--bg-field); padding:14px; margin-top:12px; }
      .wo-modal-section:first-child { margin-top:0; }
      .wo-modal-section.vendor { border-color: color-mix(in srgb, var(--accent-info) 28%, transparent); background: color-mix(in srgb, var(--accent-info) 5%, transparent); }
      .wo-modal-label { font-family:'Poppins',sans-serif; font-size:9px; letter-spacing:1.3px; color:var(--text-muted); margin-bottom:11px; }
      .wo-modal-icon-btn { width:38px; height:38px; border-radius:10px; background:var(--tint-04); border:1px solid var(--tint-12); display:flex; align-items:center; justify-content:center; cursor:pointer; color:var(--text-secondary); transition: background .12s, border-color .12s; }
      .wo-modal-icon-btn:hover { background:var(--tint-08); }
      .wo-modal-icon-btn.danger:hover { background:color-mix(in srgb, var(--urgent) 14%, transparent); border-color:color-mix(in srgb, var(--urgent) 40%, transparent); }
      /* View toggle (Panel / Modal) — app-only control; themed to v3 inset track + mint active. */
      /* Messages inbox rows (DM + thread updates, stellar-v3 spec): unread rows
         get a stronger border + raised gradient; hover nudges right + mint edge. */
      .msgRow { display:flex; align-items:center; gap:13px; padding:13px 15px; border-radius:14px; cursor:pointer; border:1px solid var(--tint-045); background:var(--tint-015); transition: transform .16s ease, border-color .16s ease; }
      .msgRow.unread { border-color: var(--tint-08); background: var(--bg-panel); }
      .msgRow:hover { transform: translateX(3px); border-color: color-mix(in srgb, var(--accent) 30%, transparent) !important; }
      @media (prefers-reduced-motion: reduce) { .msgRow:hover { transform: none; } }
      .wo-view-toggle { display:inline-flex; border-radius:10px; overflow:hidden; border:1px solid var(--tint-08); background:var(--tint-03); }
      .wo-view-toggle button { padding:7px 12px; font-size:12px; font-weight:500; font-family:'Poppins',sans-serif; cursor:pointer; background:transparent; color:var(--text-muted); border:none; transition: background .15s, color .15s; }
      .wo-view-toggle button.active { background:var(--accent); color:var(--text-ink); }
      /* In-header presentation switch (drawer + modal) — reachable wherever a
         work order opens, not just from the board toolbar. */
      .wo-switch-btn { display:inline-flex; align-items:center; gap:6px; flex:none; padding:7px 11px; border-radius:9px; border:1px solid var(--tint-12); background:var(--tint-04); color:var(--text-soft); font-family:'Poppins','Inter',sans-serif; font-size:11.5px; font-weight:500; cursor:pointer; transition:border-color .15s, color .15s, background .15s; }
      .wo-switch-btn.is-muted { color:var(--amber-pale); border-color:color-mix(in srgb, var(--amber) 40%, transparent); background:color-mix(in srgb, var(--amber) 10%, transparent); }
      .wo-switch-btn:hover { border-color:color-mix(in srgb, var(--accent) 45%, transparent); color:var(--text); background:color-mix(in srgb, var(--accent) 8%, transparent); }
      /* ===== Users & Access (stellar-v3) ===== */
      @keyframes uaBlink { 0%,100% { opacity:1; } 50% { opacity:.4; } }
      .ua-usercard { width:100%; background:var(--bg-panel); border:1px solid var(--tint-06); border-radius:16px; padding:16px 12px; display:flex; flex-direction:column; align-items:center; text-align:center; gap:1px; cursor:pointer; transition:transform .2s ease, border-color .2s ease, box-shadow .2s ease; }
      .ua-usercard:hover { transform:translateY(-3px); border-color:color-mix(in srgb, var(--accent) 40%, transparent); box-shadow:0 18px 36px -22px var(--shade-85); }
      .ua-stat { border-radius:16px; background:var(--bg-panel); border:1px solid var(--tint-055); box-shadow:var(--shadow-card); padding:16px 18px; }
      .ua-stat.first { border-top:2px solid var(--accent); }
      .ua-stat .num { font-family:'Poppins','Inter',sans-serif; font-weight:600; font-size:30px; line-height:1; color:var(--text); }
      .ua-stat .lbl { font-family:'Poppins','Inter',sans-serif; font-size:9px; letter-spacing:1.2px; text-transform:uppercase; color:var(--text-muted); margin-top:8px; }
      .ua-rolerow { display:flex; align-items:center; gap:13px; padding:13px 16px; border-top:1px solid var(--tint-05); cursor:pointer; transition:background .18s ease; }
      .ua-rolerow:hover { background:var(--tint-03); }
      /* Role editor (stellar-v3 reference): a focused screen whose sections are
         gradient cards on the canvas — NOT the flat black the editor rendered
         before. When a role is open the roles list-panel surface is stripped so
         the editor cards sit on the dark background and read with depth. */
      #rolesPanel.editing { background:transparent !important; border-color:transparent !important; box-shadow:none !important; border-radius:0 !important; overflow:visible !important; }
      #rolesPanel.editing > #rolesPanelHead { display:none; }
      .re-card { background:var(--bg-panel); border:1px solid var(--tint-055); border-radius:16px; }
      .re-rowhov { transition:background .16s ease; }
      .re-rowhov:hover { background:var(--tint-03); }
      /* Custom-lane-rules toggle: hidden checkbox + track/knob, mint when on. */
      .re-toggle { position:relative; display:inline-block; width:34px; height:19px; border-radius:999px; background:var(--tint-12); border:1px solid var(--tint-10); transition:background .2s,border-color .2s; flex:none; }
      .re-toggle::after { content:''; position:absolute; top:2px; left:2px; width:13px; height:13px; border-radius:50%; background:var(--switch-knob); box-shadow:var(--switch-knob-ring); transition:transform .22s cubic-bezier(.34,1.4,.5,1),background .2s; }
      .re-toggle-input:checked + .re-toggle { background:color-mix(in srgb, var(--accent) 50%, transparent); border-color:color-mix(in srgb, var(--accent) 60%, transparent); }
      .re-toggle-input:checked + .re-toggle::after { transform:translateX(15px); background:var(--text-ink); }
      .ua-trow { cursor:pointer; transition:background .15s ease; }
      .ua-trow:hover { background:var(--tint-03); }

      /* Edit a location — always visible (owner: "don't hide it"). */
      .site-edit-btn {
        display:inline-flex; align-items:center; gap:5px;
        padding:4px 10px; margin-right:8px; border-radius:8px;
        vertical-align:middle; cursor:pointer; white-space:nowrap;
        font-family:'Poppins','Inter',sans-serif; font-size:11.5px; font-weight:500;
        color:var(--accent);
        background:color-mix(in srgb, var(--accent) 9%, transparent);
        border:1px solid color-mix(in srgb, var(--accent) 32%, transparent);
        transition:background .15s ease, border-color .15s ease;
      }
      .site-edit-btn:hover,
      .site-edit-btn:focus-visible {
        background:color-mix(in srgb, var(--accent) 18%, transparent);
        border-color:color-mix(in srgb, var(--accent) 55%, transparent);
      }
      @media (prefers-reduced-motion: reduce) { .site-edit-btn { transition:none; } }
      .ua-lift { transition:transform .2s ease, border-color .2s ease, box-shadow .2s ease; }
      .ua-lift:hover { transform:translateY(-3px); border-color:color-mix(in srgb, var(--accent) 40%, transparent); box-shadow:0 18px 36px -22px var(--shade-85); }
      /* Location detail (spec §2): upgrade the legacy dark cards to stellar-v3 gradient panels + subtle borders/dividers. Scoped to the detail (the list view uses explicit inline gradients). */
      #sitesContent .bg-white { background: var(--bg-panel) !important; box-shadow: var(--shadow-card); }
      #sitesContent .border-ink-100, #sitesContent .border-ink-50 { border-color: var(--tint-06) !important; }
      #sitesContent .divide-ink-50 > * + * { border-color: var(--tint-05) !important; }
      #sitesContent .hover\:bg-ink-50:hover { background: var(--tint-03) !important; }
      #sitesContent .bg-amber-50 { background: color-mix(in srgb, var(--amber) 8%, transparent) !important; }
      #sitesContent .border-amber-200 { border-color: color-mix(in srgb, var(--amber) 30%, transparent) !important; }
      #sitesContent .text-amber-800, #sitesContent .text-amber-700 { color: var(--amber-soft) !important; }
      #sitesContent .text-amber-900 { color: var(--amber-pale) !important; }
      #sitesContent .bg-amber-50 .bg-white { background: var(--tint-05) !important; box-shadow: none; }
      /* #ai / #activity / #invoices / #completed — lift the remaining legacy panels to the stellar-v3 gradient. */
      section[data-view="invoices"] .bg-white,
      section[data-view="completed"] .bg-white,
      section[data-view="activity"] .bg-white { background: var(--bg-panel) !important; box-shadow: var(--shadow-card); }
      section[data-view="invoices"] .border-ink-100, section[data-view="completed"] .border-ink-100, section[data-view="activity"] .border-ink-100 { border-color: var(--tint-06) !important; }
      section[data-view="completed"] .divide-ink-50 > * + *, section[data-view="activity"] .divide-ink-50 > * + * { border-color: var(--tint-05) !important; }
      #roleEditor input[type="checkbox"] { accent-color:var(--accent); }
      /* The manage controls rendered ON a profile page (not in the drawer they
         were written for). The drawer stacked them under an identity header;
         the profile card supplies its own title, so the first section must not
         re-draw that separator over empty space — and a form is capped to a
         readable measure instead of spanning the whole page. */
      .um-oncard > *:first-child { margin-top:0 !important; padding-top:0 !important; border-top:0 !important; }
      .um-oncard > * { max-width:620px; }
      .um-oncard .seg-wrap { max-width:420px; }
      .um-oncard select.input { max-width:420px; }
      /* ===== Role editor: access as a feature table =================================
         Rows are features, columns are None / View / Manage — the shape the
         owner asked for ("Work order  none | view | manage / can click work
         orders ..."). The work-order permissions are sub-rows of Board rather
         than a separate panel, because that is what they are: indent shows the
         nesting, and a row dims when its parent is off. A sub-row is a yes/no,
         so its three level cells show an em dash instead of pretending to be a
         tri-state. */
      .rt-row { display:grid; grid-template-columns: minmax(0,1fr) 104px 104px 104px; align-items:center;
                gap:8px; border-bottom:1px solid var(--tint-045); }
      .rt-row:last-child { border-bottom:none; }
      .rt-row:hover { background:var(--tint-02); }
      .rt-feat { padding:9px 12px; min-width:0; }
      .rt-feat-check { display:flex; align-items:flex-start; gap:10px; cursor:pointer; }
      .rt-feat-text { display:block; min-width:0; }
      .rt-feat-name { display:block; font-size:12.5px; color:var(--text-body); line-height:1.35; }
      .rt-feat-hint { display:block; font-size:10.5px; color:var(--text-dim); line-height:1.45; margin-top:2px; }
      .rt-check { accent-color:var(--accent); width:15px; height:15px; margin-top:2px; flex:none; }
      .rt-cell { display:flex; align-items:center; justify-content:center; }
      .rt-cell-dash { color:var(--text-ghost); font-size:11px; }
      /* Captions the two checkbox columns inside the work-order section, so the
         boxes read against View / Manage the same way the segments above do. */
      .rt-colhead { font-size:9px; letter-spacing:1.1px; text-transform:uppercase;
                    color:var(--text-faint); font-weight:600; }
      /* Whole-cell hit area for a feature checkbox — a 15px box is a small
         target when there are two per row. */
      .rt-boxwrap { display:flex; align-items:center; justify-content:center;
                    width:100%; padding:9px 0; cursor:pointer; }
      .rt-boxwrap .rt-check { margin-top:0; }
      /* A sub-permission is a yes/no, so its row spans the whole width — it gets
         no None/View/Manage cells to draw three dead columns that look like a
         tri-state you aren't allowed to click. */
      .rt-sub-full { grid-template-columns: 1fr; cursor:pointer; }
      /* A row whose parent is off. Presentation only — nothing is unchecked. */
      .rt-sub.rt-muted, .rt-subhead.rt-muted { opacity:.4; pointer-events:none; }
      .rt-tabsec .rt-sechead { border-bottom:1px solid var(--tint-09); margin-top:10px; }
      .rt-tabsec:first-child .rt-sechead { margin-top:0; }
      .rt-sechead:hover { background:none; }
      .rt-sechead-name { font-family:'Poppins','Inter',sans-serif; font-weight:600; font-size:12.5px; color:var(--text); }
      .rt-subhead { background:var(--tint-02); }
      .rt-subhead:hover { background:var(--tint-02); }
      .rt-subhead-name { font-weight:600; color:var(--text-body); font-size:11.5px;
                         letter-spacing:.4px; text-transform:uppercase; }
      #userManageDrawer input[type="checkbox"] { accent-color:var(--accent); }
      .ua-switch { position:relative; display:inline-block; width:42px; height:24px; flex:none; cursor:pointer; }
      .ua-switch input { position:absolute; inset:0; opacity:0; margin:0; cursor:pointer; }
      .ua-switch .ua-switch-track { position:absolute; inset:0; border-radius:999px; background:var(--tint-12); transition:background .22s ease; pointer-events:none; }
      .ua-switch .ua-switch-track::after { content:''; position:absolute; top:3px; left:3px; width:18px; height:18px; border-radius:50%; background:var(--switch-knob); box-shadow:var(--switch-knob-ring); transition:transform .22s cubic-bezier(.34,1.4,.5,1); }
      .ua-switch input:checked + .ua-switch-track { background:var(--amber); }
      .ua-switch input:checked + .ua-switch-track::after { transform:translateX(18px); }
      .ua-switch.is-disabled { opacity:.5; cursor:not-allowed; }
      @media (prefers-reduced-motion: reduce) { .ua-usercard { transition:none; } }
      /* ===== PHONE LAYER (<= 767px). Every rule in here is mobile-only; desktop (md+) is untouched by design. =====
         Known, accepted exception: the viewport meta's viewport-fit=cover cannot be media-gated. A Plus/Max
         iPhone held in landscape (926-932 CSS px) gets the desktop layout with the rail/topbar drawn under
         the Dynamic Island and home indicator, because no desktop rule adds env(safe-area-inset-left/right).
         Real desktop browsers report zero insets and are unaffected. Left as-is to keep desktop CSS untouched. */
      /* On a phone the console should feel like the iOS app (owner, Sep 5):
         a bottom tab bar, one kanban lane per screen, list -> thread push
         navigation. 767px is the complement of Tailwind's md (768px) - the one
         breakpoint the shell already keys on (#sidebar, #mainCol, closeSidebar)
         - so nothing here can reach a desktop. Add rules here; never edit the
         desktop ones to make a phone work. */
      @media (max-width: 767px) {
        :root { --phone-tabbar-h: 60px; --topbar-h: 56px; }
        /* Persistent top bar on a phone (WO-02007): the brand segment shrinks
           to hamburger + tile (the workspace name would squeeze the search to
           nothing at 360-375px), the tile drops to 30px, and the search keeps
           a lower floor so the row never overflows. */
        #topBrand { width: auto; padding: 0 4px 0 6px; gap: 4px; }
        #topBrand #wsSwitcherBtn { gap: 0; flex: none; }
        #topBrand #wsSwitcherBtn > .min-w-0 { display: none; }
        #topBrand #wsSwitcherBtn > div:first-child,
        #topBrand #wsSwitcherBtn > div:first-child img { width: 30px; height: 30px; border-radius: 9px; }
        .ws-attn-dot { left: 23px; }
        #topControls { padding-left: 8px; padding-right: 12px; }
        .top-search { min-width: 100px; }
        /* The workspace menu anchors to the brand segment, which is only ~76px
           wide here: give it a viewport-sized box instead. */
        #wsMenu { left: 8px; right: auto; width: min(320px, calc(100vw - 16px)); }
        /* The data-busy pill is a nowrap <span> in the topbar's flex row, so
           on a phone its own text sets its minimum width: "Refreshing Texas
           Restaurant Holdings, LLC…" squeezed the search box to zero and then
           pushed the page 163px wider than the screen. Worse in the error
           state, where "Try again" and the dismiss X ended up past the right
           edge - an un-dismissable red banner with both of its controls out
           of reach. Lift it out of the row entirely: a full-width strip under
           the topbar that wraps instead of pushing. z-45 sits above the board
           and below the drawer (z-50). */
        /* !important throughout: _paintDataBusy writes the pill's look as an
           inline cssText (one place, both themes), and only these five
           properties are the phone's business. */
        #dataBusy:not(.hidden) {
          position: fixed !important; left: 12px; right: 12px;
          top: calc(env(safe-area-inset-top) + var(--topbar-h));
          z-index: 45; justify-content: center; flex-wrap: wrap;
          max-width: none !important; white-space: normal !important;
          overflow: visible !important;
        }
        #dataBusy > span {
          white-space: normal !important; overflow: visible !important;
          text-overflow: clip !important;
        }
        /* Bottom tab bar. z-40 keeps it above the rail (z-35, WO-02007) and
           the z-30 backdrop, so it paints over the rail's bottom edge and
           stays tappable while the "More" sheet is open; the work-order
           drawer (z-50) and every modal (z-60) still cover it. */
        #mobileTabs {
          position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
          display: flex; align-items: stretch;
          height: calc(var(--phone-tabbar-h) + env(safe-area-inset-bottom));
          padding-bottom: env(safe-area-inset-bottom);
          background: var(--glass);
          background: color-mix(in srgb, var(--bg-raised) 88%, transparent);
          -webkit-backdrop-filter: blur(18px); backdrop-filter: blur(18px);
          border-top: 1px solid var(--stroke);
        }
        .mtab {
          flex: 1 1 0; min-width: 44px; min-height: 44px; position: relative;
          display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
          padding: 6px 2px 4px; background: transparent; border: none; border-radius: 12px;
          color: var(--text-muted); cursor: pointer; -webkit-tap-highlight-color: transparent;
          font-family: 'Poppins', 'Inter', sans-serif; font-size: 10px; font-weight: 500; letter-spacing: .02em; line-height: 1;
          transition: color .18s ease;
        }
        .mtab svg { width: 24px; height: 24px; flex: none; transition: transform .18s ease; }
        .mtab:active svg { transform: scale(.92); }
        .mtab.active { color: var(--accent); }
        .mtab:focus { outline: none; }
        .mtab:focus-visible { box-shadow: inset 0 0 0 3px var(--accent-dim); }
        /* Unread count riding the icon's top-right. */
        .mtab-badge {
          position: absolute; top: 4px; left: calc(50% + 6px);
          min-width: 16px; height: 16px; padding: 0 4px; border-radius: 999px;
          font-family: 'JetBrains Mono', ui-monospace, monospace; font-size: 9px; font-weight: 700; line-height: 16px; text-align: center;
          color: var(--text-ink); background: var(--accent-info); box-shadow: 0 0 0 1.5px color-mix(in srgb, var(--accent-info) 22%, transparent);
        }
        /* Nothing hides under the bar: page content (main), and the rail's
           Help / Settings links when the "More" sheet is open (sidebar). */
        #mainCol > main { padding-bottom: calc(72px + env(safe-area-inset-bottom)); }
        #sidebar { padding-bottom: calc(var(--phone-tabbar-h) + env(safe-area-inset-bottom)); }
        /* The bar has no job under a full-screen surface. The drawer and the
           modals already cover it by z-order; hiding it as well takes it out
           of the focus order and leaves composers / .cm-actionbar on the
           bottom edge they already have. */
        body.is-popout #mobileTabs,
        body.msg-thread-open #mobileTabs,
        body:has(#drawer.open) #mobileTabs,
        body:has(#woModalBackdrop:not(.hidden)) #mobileTabs,
        body:has(.modal-backdrop:not(.hidden)) #mobileTabs { display: none; }

        /* Topbar: the ⌘K hint means nothing to a thumb; the search takes the room.
           Pop-out opens a second browser window, which a phone does not have. */
        .top-kbd { display: none; }
        #popoutBtn { display: none; }

        /* Page headers (title + description on the left, actions on the right)
           wrap on a phone: the actions drop below, so the description gets
           the full width instead of a four-line sliver beside them. */
        main section[data-view] > .flex.justify-between:first-child { flex-wrap: wrap; row-gap: 12px; }

        /* Kanban: one lane per screen, snap-scrolled; each lane body keeps its
           own vertical scroll. Scoped to the board's own #kanban so the
           embedded boards on Location / User pages are untouched. The board
           resizer is a mouse affordance; sizeKanban subtracts the bar instead. */
        section[data-view="tasks"].active #kanban { scroll-snap-type: x mandatory; scroll-padding: 0; gap: 12px; }
        section[data-view="tasks"].active #kanban > .kanban-col { flex: 0 0 100%; min-width: 0; scroll-snap-align: start; scroll-snap-stop: always; }
        section[data-view="compliance"].active #complianceKanban.flex { scroll-snap-type: x mandatory; scroll-padding: 0; gap: 12px; }
        section[data-view="compliance"].active #complianceKanban.flex > .kanban-col { flex: 0 0 100%; min-width: 0; scroll-snap-align: start; scroll-snap-stop: always; }
        #boardResizer { display: none; }
        /* Lane strip (iOS lanesPicker): one pill per lane, dot in the lane's
           colour, live count; tap to jump, follows the board's scroll. */
        #laneStrip { position: relative; display: flex; gap: 6px; overflow-x: auto; scrollbar-width: none; margin: 0 -1rem 12px; padding: 2px 1rem; -webkit-overflow-scrolling: touch; }
        #laneStrip::-webkit-scrollbar { display: none; }
        #laneStrip:empty { display: none; }
        .lane-pill {
          flex: none; display: inline-flex; align-items: center; gap: 8px; min-height: 44px; padding: 0 12px;
          border-radius: 999px; border: 1px solid var(--stroke); background: transparent; color: var(--text-soft);
          font-family: 'Poppins', 'Inter', sans-serif; font-size: 10.5px; font-weight: 700; letter-spacing: .5px; cursor: pointer;
          white-space: nowrap; -webkit-tap-highlight-color: transparent;
          transition: background-color .18s ease, border-color .18s ease, color .18s ease;
        }
        .lane-pill .badge-dot { width: 7px; height: 7px; background: var(--lane, var(--accent-info)); transform: scale(.85); transition: transform .18s ease; }
        .lane-pill .lane-n { font-family: 'JetBrains Mono', ui-monospace, monospace; font-size: 10.5px; font-weight: 500; color: var(--text-faint); }
        .lane-pill.active { color: var(--text); border-color: color-mix(in srgb, var(--lane, var(--accent)) 55%, transparent); background: color-mix(in srgb, var(--lane, var(--accent)) 18%, transparent); }
        .lane-pill.active .badge-dot { transform: scale(1); }
        .lane-pill.active .lane-n { color: var(--text-soft); }
        .lane-pill:focus { outline: none; }
        .lane-pill:focus-visible { box-shadow: 0 0 0 3px var(--accent-dim); }

        /* Messages: the docked DM window becomes the full-screen thread (list
           -> thread push). Minimize is meaningless full-screen; the back
           chevron in the header (renderDMThread) is the way out. The
           hover-only "mark unread" dot has no hover to wait for on touch. */
        #dmModal { inset: 0; width: auto; max-width: none; }
        #dmModal > div { height: 100dvh; border-radius: 0; border: none; }
        #dmDockBody { height: auto !important; flex: 1 1 auto; min-height: 0; }
        #dmThreadHeader { padding-top: calc(10px + env(safe-area-inset-top)); }
        #dmThreadHeader .dm-back { min-width: 44px; min-height: 44px; display: inline-flex; align-items: center; justify-content: center; }
        #dmThreadHeader button[onclick="_dmToggleMinimize()"],
        #dmThreadHeader button[aria-label="Close chat"] { display: none; }
        .markUnreadBtn { opacity: 1; }

        /* Work-order detail. The panel is already full-width (w-full max-w-md);
           keep it from ever scrolling sideways. The Modal presentation stops
           being a centred card and fills the screen, rail stacked under the
           conversation and the whole thing scrolling as one. */
        #drawer { max-width: none; overflow-x: hidden; }
        #drawerContent { overflow-x: hidden; }
        .wo-modal-backdrop { padding: 0; }
        .wo-modal-card { max-width: none; height: 100dvh; max-height: none; border-radius: 0; border: none; }
        .wo-modal-body { flex-direction: column; overflow-y: auto; overscroll-behavior: contain; }
        .wo-modal-main { flex: none; border-right: none; border-bottom: 1px solid var(--tint-08); }
        .wo-modal-rail { width: 100%; overflow: visible; }
        /* Compliance detail: same full-screen sheet. The 760px block already
           stacks the zones; this covers 761-767 and drops the two-column grid. */
        .modal-backdrop .cm-modal { max-width: none !important; height: 100dvh; max-height: none; border-radius: 0; border-left: none; border-right: none; }
        .cm-body { flex-direction: column; flex: none; }
        .cm-details { flex: none; min-width: 0; overflow: visible; }
        .cm-grid { grid-template-columns: 1fr; }

        /* Viewport-height boxes learn about the bar. */
        #mapContainer { height: calc(100dvh - 220px - var(--phone-tabbar-h) - env(safe-area-inset-bottom)); min-height: 360px; }

        @media (prefers-reduced-motion: reduce) {
          .mtab, .mtab svg, .lane-pill, .lane-pill .badge-dot, .drawer { transition: none; }
          section[data-view="tasks"].active #kanban, #laneStrip { scroll-behavior: auto; }
        }
      }

/* KPI page rows (kpi-metrics.js): cards sit side by side while each has room
   and wrap when it does not, instead of shrinking until their legends spill out
   (the donut's legend overflowed the page at 768px). On a phone every card is
   full width. The inline min-width:0 is what this overrides. */
.kpi-row { flex-wrap: wrap; }
.kpi-row > * { min-width: min(100%, 260px) !important; }
.kpi-row.kpi-row-cards > * { min-width: min(100%, 170px) !important; }
@media (max-width: 767px) {
  .kpi-row > *, .kpi-row.kpi-row-cards > * { min-width: 100% !important; }
}
