/* ============================================================================
   QUASAR UI — single shared stylesheet (the ONE source of truth)
   ----------------------------------------------------------------------------
   Reference look: quote.html — frosted-glass dark UI, curved cards.
   Loaded LAST on every page so it wins the cascade. A modest !important is used
   ONLY on border-radius and the frosted-glass properties (background / blur /
   border) where divergent inline or page rules would otherwise win.

   Pages reconciled: index.html, projects.html, proposal.html, wiring.html,
   quote.html. They use different class names; every box/card/panel/rail/button/
   input/badge/modal selector found per page is targeted below.

   Radius scale
     cards / panels / sidebars / rails / inspectors ... 12px
     inputs / selects / textareas / buttons ........... 8px
     modals / dialogs ................................. 14px
     pills / badges / chips ........................... 999px

   Frosted-glass recipe (top bars, side rails/sidebars/inspectors, floating
   cards/panels/toolbars):
     background: var(--bg-panel);
     -webkit-backdrop-filter: blur(12px);
     backdrop-filter: blur(12px);
     border: 1px solid var(--line);   (or a single edge border kept per layout)

   EXCLUDED from frosting/blur (canvases + imagery — never blur these):
     wiring  #canvas-svg, main.canvas-area, .pe-stage, .pe-canvas
     index   #globe, #globe canvas, .globe-container, .globe-tooltip
     shape / product image elements (img, .shape-instance, svg shapes)

   Standardized column widths
     left rail / sidebar ............ 256px
     secondary detail / inspector ... 300px
   ========================================================================== */

/* ──────────────────────────────────────────────────────────────────────────
   1. DESIGN TOKENS — reconciled to quote.html (dark default + light theme).
      Page-specific tokens (--canvas-bg, --grid-line, --grid-major, --port*,
      --wire*, --status-*, --scrim) are intentionally NOT redefined here so each
      page keeps them intact.
   ────────────────────────────────────────────────────────────────────────── */
/* Web fonts the design uses (named in the tokens below). The app never loaded them,
   so every page fell back to system fonts (SF / Times) and did not match the mockups.
   Loaded once here for all pages; display=swap avoids invisible text while they fetch. */
@import url('https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500;700&display=swap');

/* Title/display font: the mockups use "Faxfont Standard" (self-hosted in web/fonts/,
   since it is not a Google Font). --font-display prefers it. For production, convert
   the .otf to .woff2 to cut ~60% off the file size. */
@font-face{
  font-family:'Faxfont Standard';
  src:url('/fonts/Faxfont Standard Regular.otf') format('opentype');
  font-weight:400 800; font-display:swap;
}

:root{
  /* Native form controls + SCROLLBARS follow the theme (Adam 2026-07-17: the Mail
     list scrollbar rendered as a fat white bar in dark mode - the browser was
     drawing light-scheme scrollbars on a dark page). The light theme flips it below. */
  color-scheme: dark;
  /* Dark surface tones sampled from the dark-mode mockups - a darker base with a
     small blue tint (navy), cards a touch lighter navy (mockup card ~#151d30). */
  --bg:#05070b;                     /* deep-black ground (Adam 2026-07-03, was #0a0e17) */
  --bg-panel:rgba(13,16,22,.82);    /* translucent panel, de-tinted to near-black */
  --bg-panel-solid:#0d1016;         /* cards / solid panels (was #131b2c) */
  --bg-elev:#151922;                /* raised controls (was #1b2540) */
  --line:rgba(255,255,255,.11);     /* lifted slightly for definition on black (was .08) */
  --line-strong:rgba(255,255,255,.16);
  --hover-bg:rgba(255,255,255,.025);
  --hover-bg-strong:rgba(255,255,255,.05);
  --ink:#e8e4d8;
  --ink-dim:#a0a0a8;
  --ink-faint:#666670;
  /* Accent — Victron blue (token keeps its historical "brass" name; every
     page reads var(--brass), renaming would touch thousands of usages). */
  --brass:#3a7fc2;
  --brass-bright:#5d9bd8;
  --brass-on-bg:#ffffff;
  --ocean:#3a7bd5;
  --rust:#d87252;
  --good:#4ade80;
  /* Status palette matching the baselined .feed .fi colour set, so pages can
     drop per-page pastel hexes during the E3 hex-to-token sweep (E2b ask). */
  --status-red:#e0625e;
  --status-amber:#e0a23e;
  --status-violet:#b08be0;
  --status-blue:#5d9bd8;
  --status-green:#5bb98b;
  /* CRM stage/status palette (sales.html's STATUS[] object) - one fixed value
     per status, deliberately NOT theme-flipped: the globe dots + legend read
     these hexes from a JS object independent of the active theme, so a
     light-theme override here would make a pill diverge from the dot/legend it
     must match. */
  --status-lead:#7a7a84;
  --status-contacted:#4a9eff;
  --status-discussion:#e4bc6c;
  --status-nda:#b084d9;
  --status-proposal:#ff9f40;
  --status-negotiating:#ff6fa8;
  --status-customer:#4ade80;
  --status-paused:#525260;
  --status-lost:#ef4444;
  --modal-overlay:rgba(5,6,8,.78);

  --font-ui:'Manrope',system-ui,sans-serif;
  --font-display:'Instrument Serif',serif;            /* "Safiery HQ" brand logotype (serif, per the mockup) */
  --font-title:'Faxfont Standard','Manrope',system-ui,sans-serif;  /* page titles/headings (the mockup's title font) */
  --font-mono:'JetBrains Mono',ui-monospace,monospace;

  /* shared radius scale */
  --r-card:12px;
  --r-control:8px;
  --r-modal:14px;
  --r-pill:999px;
}

/* Base page surface. Without this, html/body fall back to the browser default
   (white) and leak through any area the fixed shell + cards don't cover - most
   visibly below the content and in dark mode (e.g. the Manufacturing page, which
   sets only body padding, not a background). A page may still override in its own
   <style>; this shared default just means no page can miss the theme background.
   Reads var(--bg)/var(--ink) so it tracks the active theme automatically. */
html, body{ background:var(--bg); color:var(--ink); font-family:var(--font-ui); }

/* Light theme — matches quote.html / index.html / projects.html light block.
   Cover both selector forms used across pages: html[data-theme] and :root[]. */
html[data-theme="light"],
:root[data-theme="light"]{
  color-scheme: light;
  --bg:#f3f1ea;
  --bg-panel:rgba(255,255,255,.86);
  --bg-panel-solid:#fbfaf6;
  --bg-elev:#fff;
  --line:rgba(0,0,0,.1);
  --line-strong:rgba(0,0,0,.18);
  --hover-bg:rgba(0,0,0,.025);
  --hover-bg-strong:rgba(0,0,0,.05);
  --ink:#1d2330;
  --ink-dim:#5a6170;
  --ink-faint:#9098a6;
  /* Light-theme accent = Goldenrod (premium gold). The token is named --brass
     for historical reasons but holds the accent hue; retinting it here shifts
     the brand, active nav, primary buttons/boxes and links together. Dark mode
     keeps its blue --brass (defined in :root above). Chosen 2026-07-06. */
  --brass:#b5820e;
  --brass-bright:#936609;
  --brass-on-bg:#fff;
  --modal-overlay:rgba(20,22,28,.5);
}

/* ──────────────────────────────────────────────────────────────────────────
   2. TOP BARS — frosted glass, consistent across pages.
      quote/proposal: .topbar (52px flex) · index/projects/wiring: header.topbar
   ────────────────────────────────────────────────────────────────────────── */
.topbar,
header.topbar{
  background:var(--bg-panel) !important;
  -webkit-backdrop-filter:blur(12px) !important;
  backdrop-filter:blur(12px) !important;
  border-bottom:1px solid var(--line) !important;
  /* Raise the bar (and the injected search dropdown, which is trapped in the
     bar's backdrop-filter stacking context) above page overlays like the main
     page's globe location-search bar (z-index 20) — but below app modals (500+). */
  position:relative; z-index:100;
}

/* Top-nav links — quote.html style, active link in brass. Normalised across
   .links (quote/proposal) and .topbar .links (index/projects/wiring). */
/* Consistent banner layout: brand + nav hug the left on EVERY page, all other
   topbar content (stats, diagram name, controls) pushed to the right. Without
   this, space-between distributes leftover space as gaps, so the nav drifts
   toward centre on pages with a sparse right side (projects/wiring). */
.topbar, header.topbar{ justify-content:flex-start!important; }
.links,
.topbar .links{ display:flex; gap:4px; margin-left:8px; margin-right:auto!important; }
.links a,
.topbar .links a{
  font-size:12px; letter-spacing:.04em; color:var(--ink-dim);
  padding:5px 10px; border-radius:6px; text-decoration:none;
  /* Flex-shrink squeezes long labels below their natural width, and without
     this a label wraps at its own break point (a space, or "/" in
     RMA/Warranty) instead of the row just tightening - one link goes to two
     lines while its neighbours stay put. Keep every label on one line; if the
     full row doesn't fit, items shrink toward this floor rather than wrap. */
  white-space:nowrap;
}
.links a:hover,
.topbar .links a:hover{ color:var(--ink); background:var(--hover-bg); }
.links a.active,
.topbar .links a.active{ color:var(--brass); background:var(--hover-bg-strong); }

/* Nav dropdown (the "Advanced ▾" menu) — pure CSS hover, shared by every page.
   inline-flex + nowrap keeps the trigger on ONE line, vertically centred with
   the other links, with the caret inline to the right of the label (it used to
   wrap onto a second line, which made the tab look taller / misaligned). */
.links .navdrop{ position:relative; display:inline-flex; align-items:center; }
.links .navdrop > a{ cursor:default; display:inline-flex; align-items:center; gap:5px; white-space:nowrap; }
.links .navdrop .navdd{
  display:none; position:absolute; top:100%; left:0; z-index:600; min-width:150px;
  background:var(--bg-panel, #15151a); border:1px solid var(--line, #2a2a31); border-radius:8px;
  padding:4px; box-shadow:0 10px 28px rgba(0,0,0,.4);
}
.links .navdrop:hover .navdd, .links .navdrop:focus-within .navdd{ display:block; }
.links .navdrop .navdd a{ display:block; white-space:nowrap; }

/* Unified brand wordmark across every page: "Quasar HQ / <Page>" (home link).
   Wins over each page's local .brand/.mark/.tag styling (loaded last). */
.topbar .brand, header.topbar .brand, .brand{
  font-family:var(--font-display)!important; font-size:19px!important; font-style:normal!important;
  color:var(--ink); text-decoration:none; letter-spacing:0!important;
  display:inline-flex!important; align-items:baseline; gap:6px; line-height:1; white-space:nowrap;
}
.brand .slash{ color:var(--brass)!important; margin:0 3px; font-size:19px!important; }
.brand .what{ font-family:var(--font-ui)!important; font-size:14px!important; color:var(--ink-dim)!important; letter-spacing:.01em!important; text-transform:none!important; }

/* ──────────────────────────────────────────────────────────────────────────
   3. SIDE RAILS / SIDEBARS / INSPECTORS / DETAIL — frosted glass, 12px radius,
      standardized widths.
        left rail / sidebar  → 256px
        secondary inspector / detail → 300px
   ────────────────────────────────────────────────────────────────────────── */
.rail,
.sidebar,
aside.sidebar,
aside.detail,
aside.inspector{
  background:var(--bg-panel) !important;
  -webkit-backdrop-filter:blur(12px) !important;
  backdrop-filter:blur(12px) !important;
  border-radius:0 !important;            /* full-height columns stay square-edged */
}

/* Left rail / sidebar — single standard width (proposal .rail is fixed-width;
   the grid pages set their column track below). */
.rail{ width:256px; }

/* Grid-based shells (index / projects / wiring) — standardize the column
   tracks so rails and inspectors are the same size across pages. The middle
   track (1fr) and the header row are preserved. #app is the same selector on
   all three pages but has a different column count, so each is matched via a
   page-unique descendant grid-area to avoid cross-page clobbering.
     index    : sidebar 340 / main / detail 380  → 256 / 1fr / 300
     projects : sidebar 320 / main               → 256 / 1fr
     wiring   : canvas / library rail            → 1fr / 300 (relayout)       */
#app:has(main.globe-container){
  grid-template-columns:256px 1fr 300px !important;
}
#app:has(main.project-main){
  grid-template-columns:256px 1fr !important;
}
#app:has(main.canvas-area){
  grid-template-columns:1fr 300px !important; /* wiring relayout: big canvas + 300px library rail (no internal sidebar/inspector) */
}
/* Fallback for engines without :has() — match the secondary column directly so
   at least the rail/inspector get their standard widths. */
aside.detail{ width:300px; }      /* index right column   */
aside.inspector{ width:300px; }   /* wiring right column   */

/* ──────────────────────────────────────────────────────────────────────────
   4. CARDS / PANELS / BOXES — curved (12px). Frost the floating ones.
   ────────────────────────────────────────────────────────────────────────── */
/* Solid (in-flow) cards: 12px radius. quote .card · proposal .plan-card /
   .action-card / .gen-card / .placeholder-card / .choice / .slide-card ·
   projects .file-card · index detail uses .field groups (handled below). */
.card,
.plan-card,
.action-card,
.gen-card,
.placeholder-card,
.choice,
.slide-card,
.file-card,
.project-item{
  border-radius:var(--r-card) !important;
}

/* Floating panels / toolbars get the frosted-glass treatment + 12px radius.
   wiring .canvas-toolbar / .canvas-zoomwidget / .ai-panel · index .legend /
   .map-search-bar / .map-filters select. These float over a canvas/globe but
   are chrome, not the canvas itself, so frosting is correct. */
.canvas-toolbar .tool,
.canvas-zoomwidget,
.ai-panel,
.globe-container .legend,
.map-search-bar{
  background:var(--bg-panel) !important;
  -webkit-backdrop-filter:blur(12px) !important;
  backdrop-filter:blur(12px) !important;
  border:1px solid var(--line) !important;
  border-radius:var(--r-card) !important;
}
.ai-pill{ border-radius:var(--r-pill) !important; }

/* ──────────────────────────────────────────────────────────────────────────
   5. BUTTONS — consistent 8px radius. quote.html .btn / .btn.primary is the
      reference; map every page's button class onto it.
        quote/proposal .btn / .btn.primary
        index .btn-primary / .btn-secondary / .modal .actions button
        projects .new-btn / .upload-btn / .icon-btn / .tab / .chip
        wiring .insp-btn / .icon-btn / .canvas-toolbar .tool / .side-head .mini
   ────────────────────────────────────────────────────────────────────────── */
.btn,
.btn-primary,
.btn-secondary,
.new-btn,
.upload-btn,
.icon-btn,
.insp-btn,
.geocode-btn,
.add-project-btn,
.canvas-toolbar .tool,
.canvas-zoomwidget button,
.side-head button.mini,
.modal .actions button,
.modal-head .actions button,
.danger-zone button{
  border-radius:var(--r-control) !important;
}

/* Reference button look (quote.html). Applied to the generic .btn used by
   quote + proposal so the two pages match exactly; primary = brass fill. */
.btn{
  background:var(--bg-elev);
  border:1px solid var(--line);
  color:var(--ink-dim);
  padding:8px 13px;
  font-size:12px;
  font-weight:600;
}
.btn:hover{ color:var(--ink); border-color:var(--line-strong); }
.btn.primary,
.btn-primary{
  background:var(--brass);
  color:var(--brass-on-bg);
  border-color:transparent;
}
.btn.primary:hover,
.btn-primary:hover{ background:var(--brass-bright); }
/* Destructive action button - reads red at rest so it never sits next to a
   primary looking harmless; deepens to rust on hover. */
.btn.danger{
  color:var(--status-red);
  border-color:color-mix(in srgb,var(--status-red) 42%,transparent);
  background:color-mix(in srgb,var(--status-red) 9%,transparent);
}
.btn.danger:hover{ color:var(--rust); border-color:var(--rust); background:color-mix(in srgb,var(--status-red) 15%,transparent); }

/* HQ-524/HQ-522 round (Adam, 2026-08-02: "i find on darkmode if i have a filled
   in blue box my eyes naturally go to it" - so a filled/solid --brass button is
   reserved sitewide for the ONE next-action per section; every other on-theme
   action reads instead of shouts). Same construction as .btn.danger two rules up
   (colour + tinted background + a colour-mix border), just keyed to --brass -
   existing tokens only, no new colour. */
.btn.accent{
  color:var(--brass)!important;
  border-color:color-mix(in srgb,var(--brass) 42%,transparent)!important;
  background:color-mix(in srgb,var(--brass) 9%,transparent)!important;
}
.btn.accent:hover{ color:var(--brass-bright)!important; border-color:var(--brass-bright)!important; background:color-mix(in srgb,var(--brass) 15%,transparent)!important; }

/* Danger zone - a bordered red-tinted region that quarantines a destructive
   action away from the primary flow (bottom of an edit form / edit modal).
   Built from the shared status-red token so both themes get it for free. */
.danger-zone{
  margin-top:16px;
  border:1px solid color-mix(in srgb,var(--status-red) 32%,transparent);
  background:color-mix(in srgb,var(--status-red) 6%,transparent);
  border-radius:var(--r-card);
  padding:14px 16px;
}
.danger-zone .dz-head{
  font-size:11px; font-weight:700; letter-spacing:.08em; text-transform:uppercase;
  color:var(--status-red); margin-bottom:10px;
}
.danger-zone .dz-row{ display:flex; align-items:center; gap:16px; justify-content:space-between; flex-wrap:wrap; }
.danger-zone .dz-txt{ display:flex; flex-direction:column; gap:2px; min-width:0; }
.danger-zone .dz-txt strong{ font-size:13px; color:var(--ink); font-weight:600; }
.danger-zone .dz-txt span{ font-size:12px; color:var(--ink-dim); line-height:1.45; }
.danger-zone .btn.danger{ flex:none; }

/* Canonical icon button — ONE size + hover everywhere. Pages had defined this
   four different ways (32px vs 30px, ink-hover vs brass-hover, radius 7 vs 8),
   which read as a clash when moving between pages. Radius is already unified to
   8px above; here we pin the frame + a brass hover. !important on size/hover so
   it also wins on erp/shipping/admin, whose inline <style> loads AFTER this
   file (on every other page the link loads last and would win anyway). */
.icon-btn{
  width:32px !important; height:32px !important;
  display:grid !important; place-items:center !important;
  background:transparent; border:1px solid var(--line); color:var(--ink-dim);
  cursor:pointer; transition:color .15s, border-color .15s, background .15s;
}
.icon-btn:hover{ color:var(--brass) !important; border-color:var(--brass) !important; background:transparent; }
.icon-btn svg{ width:14px; height:14px; }
#theme-toggle svg{ width:16px !important; height:16px !important; }

/* ──────────────────────────────────────────────────────────────────────────
   6. TEXT INPUTS / SELECTS / TEXTAREAS — consistent 8px radius across pages.
        quote .fld input/select, table.lines input.cell, .gate input
        index/projects .field input/select/textarea, .search input,
              .sidebar-search input, .tags-input, .coord-display
        proposal .text-input / .text-area, .gate input
        wiring .insp-row input/select, #config-gate/#auth-gate input
        (search bars are pinned to the control-radius spec in 11b below)
   ────────────────────────────────────────────────────────────────────────── */
.fld input,
.fld select,
.fld textarea,
.field input,
.field select,
.field textarea,
.insp-row input,
.insp-row select,
.text-input,
.text-area,
.search input,
.sidebar-search input,
.gate input,
#config-gate input,
#auth-gate input,
.tags-input,
.coord-display,
table.lines input.cell{
  border-radius:var(--r-control) !important;
}
/* Focus colour kept consistent (brass) — non-radius, so no !important. */
.fld input:focus,.fld select:focus,
.field input:focus,.field select:focus,.field textarea:focus,
.insp-row input:focus,.insp-row select:focus,
.text-input:focus,.text-area:focus{ border-color:var(--brass); }

/* ──────────────────────────────────────────────────────────────────────────
   7. BADGES / PILLS / CHIPS — fully rounded (999px).
        quote .badge · index .filter-chip / .tag-pill / .gmail-pill /
              .unread-badge / .company-pill / status pips
        projects .chip / .tab .badge / .pstatus
        proposal .rail-item .pill / .pill-* / .selected-contact-pill
        wiring .ai-pill (also handled above)
   ────────────────────────────────────────────────────────────────────────── */
.badge,
.filter-chip,
.tag-pill,
.gmail-pill,
.company-pill,
.unread-badge,
.chip,
.tab .badge,
.pstatus,
.rail-item .pill,
.selected-contact-pill,
.ws-link,
.po-link{
  border-radius:var(--r-pill) !important;
}

/* ──────────────────────────────────────────────────────────────────────────
   8. MODALS / OVERLAYS / DIALOGS — 14px radius; frosted glass on the panel.
        quote .modal .panel
        index .modal-overlay / .modal
        projects .modal-backdrop / .modal
        wiring #port-editor .dialog  (BUT NOT its .pe-stage / .pe-canvas)
      Proposal has no DOM modal (its picker is inline); .ps-overlay is a SLIDE
      caption overlay, deliberately NOT targeted here.
   ────────────────────────────────────────────────────────────────────────── */
.modal,
.modal .panel,
#port-editor .dialog{
  border-radius:var(--r-modal) !important;
}
/* Frost the floating modal panels (the dialog box itself). The .modal-overlay
   / .modal-backdrop scrims keep their own dim background. */
.modal .panel,
#port-editor .dialog{
  background:var(--bg-elev) !important;
  -webkit-backdrop-filter:blur(12px) !important;
  backdrop-filter:blur(12px) !important;
  border:1px solid var(--line) !important;
}

/* ──────────────────────────────────────────────────────────────────────────
   9. HARD EXCLUSIONS — canvases, globe, and shape imagery must NEVER be frosted
      or blurred, and their square/native geometry must be preserved. Listed
      last so these win over anything above.
   ────────────────────────────────────────────────────────────────────────── */
#canvas-svg,
main.canvas-area,
.canvas-area,
.canvas,
#port-editor .pe-stage,
#port-editor .pe-canvas,
.pe-stage,
.pe-canvas,
#globe,
#globe canvas,
.globe-container,
main.globe-container,
.globe-tooltip,
.scene-tooltip,
.shape-instance,
.shape-instance .body,
.map-product-chip img,
.product-btn img,
.tt-products img{
  -webkit-backdrop-filter:none !important;
  backdrop-filter:none !important;
}
/* Keep canvas surfaces square-edged (no card rounding bleed-through). */
#canvas-svg,
main.canvas-area,
.canvas-area,
#port-editor .pe-stage,
#port-editor .pe-canvas,
#globe,
.globe-container,
main.globe-container{
  border-radius:0 !important;
}

/* ──────────────────────────────────────────────────────────────────────────
   Site-wide search — injected into the top bar by quasar-search.js.
   ────────────────────────────────────────────────────────────────────────── */
.topbar.has-qsearch .links{ margin-right:8px!important; }   /* nav stops eating all space; the search icon sits right after it */
/* Collapsed to a 32px icon button by default; expands to a full field on focus
   or when the dropdown is open (qs-open). Keeps the bar uncluttered so stats /
   controls have room — the field only claims width while you're actually using
   it. margin-right:auto pushes the rest of the bar to the right edge. */
.qsearch{ position:relative; display:flex; align-items:center; gap:0; margin-right:auto;
  width:32px; height:32px; padding:0; overflow:hidden; cursor:pointer;
  background:transparent; border:1px solid var(--line); border-radius:var(--r-control)!important;
  transition:width .22s cubic-bezier(.4,0,.2,1), background .2s, padding .2s, gap .2s, border-color .15s; }
.qsearch:hover{ border-color:var(--line-strong); }
.qsearch.qs-open,
.qsearch:focus-within{ width:clamp(220px,24vw,360px); gap:7px; padding:0 10px;
  background:var(--bg-elev); border-color:var(--brass); cursor:text; }
.qsearch .qs-ico{ width:15px; height:15px; color:var(--ink-dim); flex:none; margin:auto; transition:color .15s, width .2s, height .2s, margin .2s; }
.qsearch:hover .qs-ico{ color:var(--brass); }
.qsearch.qs-open .qs-ico,
.qsearch:focus-within .qs-ico{ width:14px; height:14px; color:var(--ink-faint); margin:0; }
.qsearch .qs-input{ flex:1; min-width:0; width:0; opacity:0; background:none; border:none; outline:none; color:var(--ink); font-size:13px; transition:opacity .15s; }
.qsearch.qs-open .qs-input,
.qsearch:focus-within .qs-input{ width:auto; opacity:1; }
.qsearch .qs-input::placeholder{ color:var(--ink-faint); }

/* Bar stability when the field expands ──────────────────────────────────────
   Keep everything on ONE line and stop the field from squeezing its neighbours
   (which wrapped the nav text and nudged the buttons up). The brand, nav and
   right-hand cluster are pinned to their natural size; only the search field
   itself yields when space is genuinely tight, so nothing reshuffles. */
.topbar.has-qsearch{ flex-wrap:nowrap; }
.topbar.has-qsearch .brand,
.topbar.has-qsearch .links,
.topbar.has-qsearch .topbar-right{ flex:0 0 auto; }
.topbar.has-qsearch .links{ white-space:nowrap; }
/* On a crowded bar (e.g. the Main page: full nav + centre stats + a wide right
   cluster) there isn't room for an open field too. Rather than reshuffle, hide
   the least-critical middle stats while the search is open — they come back the
   moment it closes. No-ops on pages without a .topbar-center. */
.topbar:has(.qsearch.qs-open) .topbar-center,
.topbar:has(.qsearch:focus-within) .topbar-center{ display:none; }

.qsearch-dd{ position:absolute; top:calc(100% + 8px); left:0; width:min(560px,82vw); max-height:64vh; overflow:auto;
  background:var(--bg-panel); -webkit-backdrop-filter:blur(14px); backdrop-filter:blur(14px);
  border:1px solid var(--line); border-radius:12px; box-shadow:0 20px 60px rgba(0,0,0,.45); z-index:1200; padding:8px; }
.qs-chips{ display:flex; flex-wrap:wrap; gap:6px; padding:2px 4px 8px; border-bottom:1px solid var(--line); margin-bottom:6px; }
.qs-chip{ background:var(--bg-elev); border:1px solid var(--line); color:var(--ink-dim); border-radius:999px!important; padding:4px 10px; font-size:11px; cursor:pointer; }
.qs-chip:hover{ color:var(--ink); }
.qs-chip.active{ color:var(--brass); border-color:var(--brass); }
.qs-chip .qs-n{ color:var(--ink-faint); font-variant-numeric:tabular-nums; margin-left:3px; }
.qs-empty{ padding:18px 12px; color:var(--ink-faint); font-size:12px; text-align:center; }
.qs-row{ display:flex; align-items:center; gap:10px; padding:8px 10px; border-radius:8px; text-decoration:none; cursor:pointer; }
.qs-row.sel,.qs-row:hover{ background:var(--hover-bg-strong); }
.qs-badge{ flex:none; font-size:9px; letter-spacing:.06em; text-transform:uppercase; padding:3px 7px; border-radius:6px; border:1px solid var(--line); color:var(--ink-dim); }
.qs-badge.qs-contact{ color:var(--good); } .qs-badge.qs-project{ color:var(--brass); } .qs-badge.qs-quote{ color:var(--ocean); } .qs-badge.qs-proposal{ color:var(--rust); } .qs-badge.qs-diagram{ color:var(--brass-bright); } .qs-badge.qs-page{ color:var(--ink); }
.qs-meta{ min-width:0; display:flex; flex-direction:column; }
.qs-title{ color:var(--ink); font-size:13px; font-weight:600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.qs-sub{ color:var(--ink-faint); font-size:11px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
@media (max-width:820px){ .qsearch.qs-open, .qsearch:focus-within{ width:200px; } }

/* Public sign-up is disabled (accounts are admin-created). Hide the
   "Need an account? Sign up →" toggle so users only ever see Sign In. */
#auth-switch, #auth-toggle { display: none !important; }

/* ──────────────────────────────────────────────────────────────────────────
   10. LIQUID GLASS DOCK — floating bottom island (quasar-nav.js).
       Replaces the top bar entirely. Genuinely translucent: a faint tint over
       backdrop blur + saturation, a bright glass rim, and a specular sheen —
       the page scrolls visibly through it.
   ────────────────────────────────────────────────────────────────────────── */
:root{
  --qglass-bg:linear-gradient(135deg, rgba(255,255,255,.10), rgba(255,255,255,.035) 55%, rgba(255,255,255,.07));
  --qglass-rim:rgba(255,255,255,.16);
  --qglass-hi:rgba(255,255,255,.25);
  --qglass-lo:rgba(0,0,0,.22);
  --qglass-active:rgba(255,255,255,.14);
  --qglass-drop:0 18px 50px rgba(0,0,0,.45), 0 2px 10px rgba(0,0,0,.3);
  --qglass-sheen:linear-gradient(115deg, rgba(255,255,255,.22), rgba(255,255,255,0) 30%, rgba(255,255,255,0) 72%, rgba(255,255,255,.10));
}
html[data-theme="light"], :root[data-theme="light"]{
  --qglass-bg:linear-gradient(135deg, rgba(255,255,255,.50), rgba(255,255,255,.22) 55%, rgba(255,255,255,.38));
  --qglass-rim:rgba(255,255,255,.75);
  --qglass-hi:rgba(255,255,255,.95);
  --qglass-lo:rgba(30,36,46,.10);
  --qglass-active:rgba(29,35,48,.10);
  --qglass-drop:0 18px 44px rgba(31,38,46,.25), 0 2px 8px rgba(31,38,46,.12);
  --qglass-sheen:linear-gradient(115deg, rgba(255,255,255,.65), rgba(255,255,255,0) 32%, rgba(255,255,255,0) 70%, rgba(255,255,255,.35));
}

/* ──────────────────────────────────────────────────────────────────────────
   10b. APP SHELL — left sidebar + top bar (quasar-nav.js).
        Replaces the old floating dock with the house look from the mockups:
        a blue serif "Safiery HQ" wordmark top-left, a left module sidebar (the
        active module expands to indented sub-sections), and a top bar with
        global search, a notification bell, a messages icon, a save indicator
        and the user chip. Applied via html.qnav-shell before first paint so
        content reflows to the right of the sidebar with no flash.
   ────────────────────────────────────────────────────────────────────────── */
:root{
  --qside-w:240px;          /* sidebar width (expanded) */
  --qside-w-rail:64px;      /* sidebar width (collapsed to icon rail) */
  --qtop-h:60px;            /* top bar height */
  --qside-gap:12px;         /* margin around the floating sidebar */
  --qside-off:calc(var(--qside-w) + var(--qside-gap)*2);       /* content offset (expanded) */
  --qside-off-rail:calc(var(--qside-w-rail) + var(--qside-gap)*2); /* content offset (rail) */
  /* Shell surfaces — slightly distinct from the page bg, per the mockups. */
  --qshell-side-bg:#050708;         /* sidebar (deep black, was #0b1019) */
  --qshell-top-bg:rgba(6,8,12,.9);  /* top bar (deep black, was rgba(10,14,23,.85)) */
  --qshell-active-bg:rgba(58,127,194,.16);   /* active module pill */
  --qshell-active-bar:var(--brass-bright);   /* active accent bar/text */
  --qshell-sub-cur-bg:rgba(58,127,194,.14);  /* current sub-section */
}
html[data-theme="light"], :root[data-theme="light"]{
  --qshell-side-bg:#fbfaf6;
  --qshell-top-bg:rgba(255,255,255,.88);
  --qshell-active-bg:rgba(42,102,171,.12);
  --qshell-active-bar:var(--brass);
  --qshell-sub-cur-bg:rgba(42,102,171,.10);
}

/* The original page top bar is replaced wholesale by the shell. Its elements
   stay in the DOM (page scripts hold ids/listeners); quasar-nav.js relocates
   the live controls (#who, theme toggle, sign out, save indicator, search) into
   the new top bar. qnav-on alias keeps the legacy phone-layer / grid-row rules
   working. */
html.qnav-shell .topbar,
html.qnav-shell header.topbar{ display:none !important; }

/* Reflow page content to the right of the sidebar and below the top bar.
   Two page archetypes:
     (a) the top bar is a body SIBLING of the content (warehouse / support /
         operations / field-service / inventory / shipping / admin) — content
         is in normal flow, so we shift it with body padding;
     (b) the top bar lives INSIDE #app, which is sized to the viewport
         (grid 100vw×100vh: index/projects/wiring; flex height:100vh:
         quote/proposal; flex:1: erp/manufacturing) — body padding can't move a
         viewport-sized box, so we PIN #app to the content area with fixed insets
         (overrides its original width/height/flex uniformly) and cancel the
         body padding. */
html.qnav-shell body{
  padding-left:var(--qside-off);
  padding-top:var(--qtop-h);
  transition:padding-left .2s ease;
}
html.qnav-shell.qnav-rail body{ padding-left:var(--qside-off-rail); }

/* (b) #app contains the (now hidden) top bar → pin it to the content area. */
html.qnav-shell #app:has(.topbar){
  position:fixed !important;
  inset:var(--qtop-h) 0 0 var(--qside-off) !important;
  width:auto !important; height:auto !important; max-width:none !important;
  margin:0 !important;
  transition:left .2s ease;
}
html.qnav-shell.qnav-rail #app:has(.topbar){ left:var(--qside-off-rail) !important; }
/* Projects' layout root IS the pinned #app (rail+detail grid, no inner wrapper),
   so the 1920px large-screen cap is applied to the pinned box itself: with both
   insets set, the auto margins centre the capped box in the content area. */
html.qnav-shell #app:has(main.project-main){ max-width:1920px !important; margin-left:auto !important; margin-right:auto !important; }
/* Grid shells reserve a 54px header row inside #app — collapse it to 0. */
html.qnav-shell #app:has(> header.topbar){ grid-template-rows:0 1fr !important; }
/* #app is pinned, so the body padding would just add dead space — drop it. */
html.qnav-shell:has(#app .topbar) body{ padding-left:0 !important; padding-top:0 !important; }

/* HQ-514: rail-synced overlays that sit ON TOP of #app (index.html's #dashboard,
   quote.html's .orders-fs full-screen browser) reposition their own left/inset
   from the SAME --qside-off-rail token, but their own page <style> blocks never
   gave them the shell's transition - so on a fast expand their edge snapped to
   the final width instantly while #app/.qside were still 200ms into easing,
   exposing whatever #app renders underneath for that gap ("a screen behind it").
   Added here (not in the page's own block) so it costs no ui-drift bytes. */
#dashboard, .orders-fs{ transition:left .2s ease; }
@media (max-width:900px){
  html.qnav-shell #app:has(main.globe-container){ grid-template-rows:0 40vh 1fr !important; }
  html.qnav-shell aside.detail{ top:0 !important; }
}

/* ── Impersonation banner (admin "log in as") ──
   An unmissable amber strip pinned across the very top on EVERY page whenever
   auth-user reports the session is being impersonated. It is owned by the shared
   shell (quasar-nav.js builds it hidden and toggles html.qimp-on), so it shows on
   every module, not just index. It REUSES the existing --status-amber accent - no
   new colour. While it is on, the shell (top bar, sidebar, content) shifts down by
   the banner height so nothing is hidden behind it. The tinted surface (amber
   mixed into the panel colour) keeps --ink text readable in BOTH themes; the solid
   amber bottom edge + amber emphasis carry the accent. */
.qimp-banner{
  position:fixed; top:0; left:0; right:0; height:40px; z-index:445;
  display:flex; align-items:center; gap:10px; padding:0 16px;
  background:color-mix(in srgb, var(--status-amber) 30%, var(--bg-panel-solid));
  color:var(--ink);
  border-bottom:2px solid var(--status-amber);
  font-family:var(--font-ui); font-size:13px; line-height:1.2;
  box-shadow:0 6px 20px rgba(0,0,0,.28);
}
.qimp-banner[hidden]{ display:none; }
.qimp-ic{ flex:0 0 auto; width:18px; height:18px; color:var(--status-amber); display:grid; place-items:center; }
.qimp-ic svg{ width:18px; height:18px; }
.qimp-msg{ min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.qimp-msg strong{ font-weight:700; }
.qimp-msg .qimp-em{ color:var(--status-amber); font-weight:800; }
.qimp-spacer{ flex:1 1 auto; }
.qimp-exit{
  flex:0 0 auto; cursor:pointer; font-family:inherit; font-size:12.5px; font-weight:700;
  padding:5px 14px; border-radius:var(--r-pill);
  background:transparent; color:var(--ink); border:1px solid var(--status-amber);
}
.qimp-exit:hover{ background:color-mix(in srgb, var(--status-amber) 28%, transparent); }
.qimp-exit:disabled{ opacity:.6; cursor:default; }
/* Shift the shell down by the banner height while impersonating. Each rule adds
   .qimp-on so it out-specifies the base shell rule it overrides; only `top` (and
   body padding-top) change, so the left/rail offsets stay exactly as they were. */
html.qimp-on .qtopbar{ top:40px; }
html.qimp-on .qside{ top:calc(var(--qside-gap) + 40px); }
html.qnav-shell.qimp-on body{ padding-top:calc(var(--qtop-h) + 40px); }
html.qnav-shell.qimp-on #app:has(.topbar){ top:calc(var(--qtop-h) + 40px) !important; }

/* ── The sidebar ── */
.qside{
  position:fixed; top:var(--qside-gap); left:var(--qside-gap); bottom:var(--qside-gap);
  width:var(--qside-w); z-index:430;
  display:flex; flex-direction:column;
  background:var(--bg-panel-solid);
  border-radius:16px; border:none;
  box-shadow:0 14px 40px rgba(0,0,0,.45);
  overflow:hidden;
  transition:width .2s ease, transform .2s ease;
}
.qside-brand{
  display:flex; align-items:center; height:var(--qtop-h); flex:0 0 auto;
  padding:0 18px; text-decoration:none;
  white-space:nowrap; overflow:hidden;
}
.qbrand-word{
  font-family:var(--font-display); font-style:italic; font-size:30px; line-height:1.14;
  color:var(--brass-bright); letter-spacing:.4px;
}
html[data-theme="light"] .qbrand-word{ color:var(--brass); }

.qside-links{
  display:flex !important; flex-direction:column; gap:1px;
  padding:10px 10px 16px; margin:0 !important;
  overflow-y:auto; overflow-x:hidden; flex:1 1 auto; min-height:0;
}
.qside-links::-webkit-scrollbar{ width:8px; }
.qside-links::-webkit-scrollbar-thumb{ background:var(--line-strong); border-radius:8px; }

/* Module row */
.qside-item{
  display:flex; align-items:center; gap:11px;
  padding:9px 11px; border-radius:9px;
  color:var(--ink-dim); text-decoration:none;
  font-size:13.5px; font-weight:600; letter-spacing:.005em;
  white-space:nowrap; overflow:hidden;
  transition:color .12s, background .12s;
}
.qside-item:hover{ color:var(--ink); background:var(--hover-bg-strong); }
.qside-item.active{ color:var(--ink); background:var(--qshell-active-bg); }
.qside-item.active .qside-ic{ color:var(--qshell-active-bar); }
.qside-item .qside-ic{ flex:0 0 auto; width:20px; height:20px; display:grid; place-items:center; color:inherit; }
.qside-item .qside-ic svg{ width:20px; height:20px; }
.qside-item .qside-label{ overflow:hidden; text-overflow:ellipsis; }
.qside-item.qside-extra{ font-weight:500; color:var(--ink-faint); }
.qside-item.qside-extra:hover{ color:var(--ink-dim); }

/* The active module's sub-section strip — indented, current one highlighted. */
.qside-subs{
  display:flex; flex-direction:column; gap:1px;
  margin:2px 0 6px 0; padding-left:31px;
  position:relative;
}
.qside-subs::before{
  content:""; position:absolute; left:20px; top:4px; bottom:4px;
  width:1px; background:var(--line-strong);
}
.qside-sub{
  display:block; padding:6px 10px; border-radius:7px;
  color:var(--ink-dim); text-decoration:none;
  font-size:12.5px; font-weight:500; white-space:nowrap;
  overflow:hidden; text-overflow:ellipsis;
  transition:color .12s, background .12s;
}
.qside-sub:hover{ color:var(--ink); background:var(--hover-bg); }
.qside-sub.current{ color:var(--brass-bright); background:var(--qshell-sub-cur-bg); font-weight:700; }
html[data-theme="light"] .qside-sub.current{ color:var(--brass); }

.qside-foot{ flex:0 0 auto; padding:8px 10px 12px; }
.qside-collapse{
  display:flex; align-items:center; gap:11px; width:100%;
  padding:8px 11px; border:none; border-radius:9px; cursor:pointer;
  background:transparent; color:var(--ink-faint);
  font-family:inherit; font-size:12.5px; font-weight:600; text-align:left;
}
.qside-collapse svg{ width:18px; height:18px; flex:0 0 auto; transition:transform .2s; }
.qside-collapse:hover{ color:var(--ink-dim); background:var(--hover-bg-strong); }

/* Collapsed icon-rail (desktop) — labels + sub-strip hide, icons centre. */
html.qnav-rail .qside{ width:var(--qside-w-rail); }
html.qnav-rail .qside-brand{ padding:0; justify-content:center; }
html.qnav-rail .qbrand-word{ font-size:0; }
/* HQ-485 (Adam: "when i collospe the side bar it still says S not Q"): the rail
   shows the FIRST letter of the wordmark quasar-nav.js sets ("Quasar HQ"), so it
   is Q. It was an S from when the collapsed rail was built against the Safiery
   name; the expanded sidebar has said Quasar HQ throughout, so the two disagreed. */
html.qnav-rail .qbrand-word::before{ content:"Q"; font-size:25px; }
html.qnav-rail .qside-label,
html.qnav-rail .qside-subs{ display:none; }
html.qnav-rail .qside-item{ justify-content:center; padding:9px 0; }
html.qnav-rail .qside-collapse{ justify-content:center; padding:8px 0; }
html.qnav-rail .qside-collapse svg{ transform:rotate(180deg); }

/* ── Rail flyout (HQ-383): with the sidebar collapsed, hovering a module icon
   fans out that module's sections so navigating never requires un-collapsing.
   Built/positioned by quasar-nav.js; only ever shown while html.qnav-rail. ── */
.qside-fly{
  position:fixed; z-index:1250; min-width:196px; max-width:260px;
  background:var(--bg-panel-solid); border:1px solid var(--line);
  border-radius:12px; padding:6px; box-shadow:0 18px 50px rgba(0,0,0,.38);
}
.qside-fly[hidden]{ display:none; }
.qside-fly .qfly-head{ display:block; font-size:13px; font-weight:700; color:var(--ink); text-decoration:none; padding:8px 11px; border-radius:8px; }
.qside-fly .qfly-head:hover{ background:var(--hover-bg-strong); }
.qside-fly .qfly-sub{ display:block; font-size:12.5px; font-weight:600; color:var(--ink-dim); text-decoration:none; padding:7px 11px 7px 18px; border-radius:8px; }
.qside-fly .qfly-sub:hover{ background:var(--hover-bg-strong); color:var(--ink); }
.qside-fly .qfly-hint{ font-size:12px; color:var(--ink-faint); padding:6px 11px 7px 18px; }
/* HQ-390: while a flyout is open, its source icon stays lit so the pull-out is
   visibly anchored to the module it came from. */
html.qnav-rail .qside-item.qfly-src{ background:var(--hover-bg-strong); color:var(--ink); }
html.qnav-rail .qside-item.qfly-src .qside-ic{ color:var(--brass); }

/* ── The top bar ── */
.qtopbar{
  position:fixed; top:0; right:0; left:var(--qside-off); height:var(--qtop-h); z-index:425;
  display:flex; align-items:center; gap:12px; padding:0 20px;
  background:var(--qshell-top-bg);
  -webkit-backdrop-filter:blur(14px); backdrop-filter:blur(14px);
  border-bottom:1px solid var(--line);
  /* HQ-383: soften the strip's bottom edge to the house card radius (Adam:
     "the box at the top is square"). Top corners stay square - it meets the
     viewport edge. */
  border-radius:0 0 16px 16px;
  transition:left .2s ease;
}
html.qnav-rail .qtopbar{ left:var(--qside-off-rail); }
.qtopbar-left{ display:flex; align-items:center; gap:10px; }
.qtopbar-spacer{ flex:1 1 auto; }
.qtopbar-search{ flex:0 1 460px; min-width:0; display:flex; }
.qtopbar-search .qsearch{ width:100% !important; padding:0 14px; gap:10px; cursor:text; }
.qtopbar-search .qsearch .qs-ico{ margin:0; width:16px; height:16px; color:var(--ink-faint); }
.qtopbar-search .qsearch .qs-input{ width:auto; opacity:1; }
/* Display-size (zoom) control */
.qsize-menu{ position:absolute; top:calc(100% + 8px); right:0; z-index:1300; min-width:186px; background:var(--bg-panel-solid); border:1px solid var(--line); border-radius:12px; padding:5px; box-shadow:0 18px 50px rgba(0,0,0,.5); }
.qsize-menu[hidden]{ display:none; }
.qsize-menu .qsize-h{ font-size:10px; text-transform:uppercase; letter-spacing:.06em; color:var(--ink-faint); font-weight:700; padding:6px 11px 4px; }
.qsize-menu button{ display:flex; align-items:center; justify-content:space-between; gap:12px; width:100%; text-align:left; background:none; border:none; border-radius:8px; color:var(--ink-dim); font-family:inherit; font-size:13px; font-weight:600; padding:8px 11px; cursor:pointer; }
.qsize-menu button:hover{ background:var(--hover-bg-strong); color:var(--ink); }
.qsize-menu button.on{ color:var(--brass-bright); background:rgba(93,155,216,.12); }
.qsize-menu button .pct{ font-family:var(--font-mono,monospace); font-size:11px; color:var(--ink-faint); }
/* HQ-520 round 8 (Adam: "that text size drop down lets move that into the user
   drop down"): the same step list rendered INSIDE the avatar menu instead of as
   its own topbar popover. The variant strips the popover chrome; the buttons
   keep the .qsize-menu rules above. Collapsed behind a toggle row by default. */
.qsize-menu.qsize-inline{ position:static; min-width:0; border:none; border-top:1px solid var(--line); border-radius:0; box-shadow:none; padding:4px 0 0; margin-top:5px; }

/* Confirmation window (HQ-504): a small centered modal confirming a completed
   save - e.g. Inventory "+ New item" naming the product just added. Built from
   existing tokens only; pages build it with these classes, never inline styles. */
.qconfirm-ov{ position:fixed; inset:0; z-index:2600; display:flex; align-items:center; justify-content:center; background:var(--modal-overlay); padding:24px; }
.qconfirm{ background:var(--bg-panel-solid); border:1px solid var(--line); border-radius:var(--r-modal); box-shadow:0 24px 60px rgba(0,0,0,.45); width:min(420px,100%); padding:20px; }
.qconfirm h3{ margin:0 0 8px; font-size:16px; color:var(--ink); }
.qconfirm p{ margin:0 0 14px; font-size:13px; color:var(--ink-dim); line-height:1.55; }
.qconfirm p b{ color:var(--ink); }
.qconfirm .acts{ display:flex; justify-content:flex-end; gap:8px; }
/* Required-field star (HQ-505): marks a field the save refuses without. */
.fld .req{ color:var(--status-red); margin-left:2px; font-weight:700; }

/* Save / sync status pill (quasar-nav.js injects it on every page). The dot is
   always visible + state-coloured; the label is a hover/focus tooltip so it
   never crowds the bar. Green = synced, amber = saving, red = not synced. */
.qtopbar-save{ position:relative; display:inline-flex; align-items:center; cursor:default; }
.qtopbar-save .dot{ width:7px; height:7px; border-radius:50%; background:#4ade80; flex:none; transition:background .2s; }
.qtopbar-save[data-state="saving"] .dot{ background:var(--brass); animation:qsavepulse .8s infinite; }
.qtopbar-save[data-state="error"]  .dot{ background:#ef4444; }
.qtopbar-save #save-text{
  position:absolute; top:calc(100% + 8px); right:0; z-index:1300;
  background:var(--bg-panel-solid); border:1px solid var(--line); border-radius:8px;
  padding:5px 10px; font-size:11px; letter-spacing:.02em; text-transform:none; font-weight:600;
  color:var(--ink-dim); white-space:nowrap; box-shadow:0 12px 34px rgba(0,0,0,.4);
  opacity:0; pointer-events:none; transform:translateY(-3px);
  transition:opacity .15s ease, transform .15s ease;
}
.qtopbar-save:hover #save-text, .qtopbar-save:focus-within #save-text{ opacity:1; transform:none; }
.qtopbar-save[data-state="error"] #save-text{ color:#ef4444; border-color:rgba(239,68,68,.45); }
.qtopbar-save[data-state="saving"] #save-text{ color:var(--brass); }
@keyframes qsavepulse{ 50%{ opacity:.35; } }

/* Consistent large-screen sizing: EVERY page's content frame caps + centres at
   1920px (like Manufacturing's .mf-wrap) so ultrawide monitors don't stretch the
   rows edge-to-edge. On a laptop (<1920) max-width does nothing, so it fills.
   Pages with their own layout roots carry the same 1920px cap in their page
   style (index .dash-wrap, erp .wrap, quote/proposal .body, projects #app).
   Only true full-bleed surfaces (the CRM globe, the wiring canvas) stay uncapped. */
html.qnav-shell .rp-wrap, html.qnav-shell .co-wrap, html.qnav-shell .pp-wrap,
html.qnav-shell .sp-wrap, html.qnav-shell .wh-wrap, html.qnav-shell .fs-wrap,
html.qnav-shell .inv-wrap, html.qnav-shell .admin-wrap{ max-width:1920px; margin-left:auto; margin-right:auto; }
.qtopbar-tools{ display:flex; align-items:center; gap:8px; flex:0 0 auto; }

/* Top-bar icon buttons (bell, messages, theme, sign out). */
.qtopbar-btn{
  position:relative; width:38px; height:38px; flex:0 0 auto;
  display:grid; place-items:center; padding:0;
  border-radius:10px; border:1px solid transparent; background:transparent;
  color:var(--ink-dim); cursor:pointer; text-decoration:none;
  transition:color .12s, background .12s, border-color .12s;
}
.qtopbar-btn svg{ width:19px; height:19px; }
.qtopbar-btn:hover{ color:var(--ink); background:var(--hover-bg-strong); }
.qtopbar-btn.qtopbar-signout:hover{ color:var(--rust); }

/* Notification / message count badge. */
.qtopbar-btn .qbadge[hidden]{ display:none; }
.qtopbar-btn .qbadge{
  position:absolute; top:3px; right:2px;
  min-width:16px; height:16px; padding:0 4px;
  display:grid; place-items:center;
  background:var(--rust); color:#fff;
  font-size:9.5px; font-weight:800; line-height:1;
  border-radius:999px; pointer-events:none;
  box-shadow:0 1px 4px rgba(0,0,0,.4);
}

/* Save indicator, relocated into the top bar — compact to its status dot. */
.qtopbar .save-indicator{
  display:flex; align-items:center; justify-content:center;
  width:20px; height:38px; flex:0 0 auto;
}
.qtopbar .save-indicator #save-text,
.qtopbar .save-indicator .save-detail{ display:none; }

/* User chip — avatar + name + role. */
.qtopbar-user{
  display:flex; align-items:center; gap:9px; flex:0 0 auto;
  padding:5px 10px 5px 6px; margin-left:4px;
  border-radius:999px; border:1px solid var(--line);
  cursor:pointer;
}
/* The chip is a click/keyboard target now (HQ-411 account menu) - give it the
   same feedback the other topbar controls already have. Tokens only. */
.qtopbar-user:hover,.qtopbar-user:focus-visible{ background:var(--hover-bg-strong); }
.qtopbar-avatar{
  width:30px; height:30px; flex:0 0 auto;
  display:grid; place-items:center;
  border-radius:999px; background:var(--brass); color:var(--brass-on-bg);
  font-size:12px; font-weight:800; letter-spacing:.02em;
}
.qtopbar-whowrap{ display:flex; flex-direction:column; line-height:1.25; min-width:0; }
.qtopbar-who{ font-size:12.5px; font-weight:700; color:var(--ink); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:160px; }
.qtopbar-role{ font-size:10.5px; color:var(--ink-faint); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:160px; }

/* Theme toggle icon-swap (when the page didn't ship its own). */
.qtopbar-btn .ic-moon{ display:none; }
:root[data-theme="light"] .qtopbar-btn .ic-sun{ display:none; }

/* ── Team chat dropdown (opens from the messages icon) ── */
.qtopbar-btn.on{ color:var(--ink); background:var(--hover-bg-strong); }

/* "Desktop view" toggle (quasar-nav.js buildViewModeControl). Only meaningful on
   a touch device, so it is gated on POINTER TYPE, never viewport width - forcing
   desktop view swaps the viewport to ~1280px, so a width-gated control would
   vanish and strand the user in desktop mode with no way back. It is also always
   shown while a desktop view is forced, guaranteeing the escape hatch. When
   engaged it takes the brass accent so the active mode reads at a glance. */
.qtopbar-viewmode{ display:none; }
@media (pointer:coarse){ .qtopbar-viewmode{ display:grid; } }
html.qnav-force-desktop .qtopbar-viewmode{ display:grid; }
.qtopbar-viewmode.on{ color:var(--brass-bright); border-color:var(--brass); background:transparent; }
.qtopbar-viewmode.on:hover{ color:var(--brass-bright); background:var(--hover-bg-strong); }
.qchat{ position:fixed; z-index:1300; width:min(440px, calc(100vw - 24px)); max-height:86vh; display:flex; flex-direction:column;
  background:var(--bg-panel-solid); border:1px solid var(--line); border-radius:14px;
  box-shadow:0 22px 64px rgba(0,0,0,.55); overflow:hidden; }
/* large-dropdown sizing (Adam 2026-07-03): chat + notifications open as a tall
   panel, not a cramped popover - the list fills a generous min-height. */
.qchat > .qchat-list, .qchat > .qnotif-list{ min-height:min(56vh, 460px); }
.qchat[hidden], .qchat-list[hidden], .qchat-thread[hidden], .qchat-tagrow[hidden]{ display:none; }
.qchat-head{ display:flex; align-items:center; gap:8px; padding:11px 14px; border-bottom:1px solid var(--line); }
.qchat-title{ font-weight:800; font-size:14px; color:var(--ink); flex:1; }
.qchat-new{ background:var(--bg-elev); border:1px solid var(--line); border-radius:999px; color:var(--brass-bright); font-family:inherit; font-size:11px; font-weight:700; padding:3px 9px; cursor:pointer; }
.qchat-new:hover{ border-color:var(--brass); }
.qchat-x{ background:none; border:none; color:var(--ink-dim); font-size:21px; line-height:1; cursor:pointer; padding:0 2px; }
.qchat-list{ overflow-y:auto; }
.qchat-row{ display:flex; align-items:center; gap:10px; width:100%; text-align:left; background:none; border:none; border-bottom:1px solid var(--line); padding:11px 14px; cursor:pointer; font-family:inherit; }
.qchat-row:hover{ background:var(--hover-bg-strong); }
.qchat-avatar{ width:34px; height:34px; flex:none; border-radius:999px; display:grid; place-items:center; color:#fff; font-size:12px; font-weight:800; }
.qchat-rname{ flex:1; min-width:0; font-size:13.5px; font-weight:700; color:var(--ink); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.qchat-unread{ flex:none; display:inline-flex; align-items:center; gap:3px; height:19px; padding:0 8px; border-radius:999px; background:var(--brass); color:#fff; font-size:10.5px; font-weight:800; }
.qchat-uico{ width:11px; height:11px; }
.qchat-thread{ display:flex; flex-direction:column; min-height:0; flex:1; }
.qchat-thead{ display:flex; align-items:center; gap:9px; padding:9px 14px; border-bottom:1px solid var(--line); }
.qchat-back{ background:none; border:none; color:var(--ink-dim); font-size:18px; cursor:pointer; padding:0 4px; display:grid; place-items:center; }
/* HQ-520 round 9 (Adam: "there isnt a back button or back arrow on the drop down
   mail function"): the button was always rendered - its SVG just had no size rule
   (.qchat-hbtn svg has one, this class never did), so the arrow drew at zero size. */
.qchat-back svg{ width:16px; height:16px; }
.qchat-back:hover{ color:var(--ink); }
.qchat-tname{ font-size:13px; font-weight:700; color:var(--ink); }
.qchat-msgs{ flex:1; overflow-y:auto; padding:12px 14px; display:flex; flex-direction:column; gap:8px; max-height:min(60vh, 520px); }
.qchat-bub{ max-width:82%; padding:7px 11px; border-radius:13px; font-size:12.5px; line-height:1.35; }
.qchat-bub.them{ align-self:flex-start; background:var(--bg-elev); color:var(--ink); border-bottom-left-radius:4px; }
.qchat-bub.me{ align-self:flex-end; background:var(--brass); color:var(--brass-on-bg); border-bottom-right-radius:4px; }
.qchat-bub b{ font-weight:800; }
.qchat-bub .qchat-time{ display:block; font-size:9.5px; opacity:.7; margin-top:3px; }
.qchat-tagrow{ display:flex; flex-wrap:wrap; gap:5px; padding:8px 12px 0; }
.qchat-tagrow button{ background:var(--bg-elev); border:1px solid var(--line); border-radius:999px; color:var(--ink-dim); font-size:11px; padding:3px 9px; cursor:pointer; font-family:inherit; }
.qchat-tagrow button:hover{ color:var(--ink); border-color:var(--brass); }
.qchat-compose{ display:flex; align-items:center; gap:7px; padding:10px 12px; border-top:1px solid var(--line); }
.qchat-tag{ width:30px; height:30px; flex:none; border-radius:8px; border:1px solid var(--line); background:var(--bg-elev); color:var(--ink-dim); font-size:15px; font-weight:800; cursor:pointer; }
.qchat-tag:hover{ color:var(--brass); border-color:var(--brass); }
.qchat-input{ flex:1; min-width:0; background:var(--bg); border:1px solid var(--line); border-radius:8px; padding:8px 10px; color:var(--ink); font-family:inherit; font-size:12.5px; outline:none; }
.qchat-input:focus{ border-color:var(--brass); }
.qchat-send{ width:34px; height:34px; flex:none; border-radius:8px; border:none; background:var(--brass); color:var(--brass-on-bg); cursor:pointer; display:grid; place-items:center; }
/* Notify-a-teammate composer in the bell panel (HQ-329): the roster picker takes the
   short side of the row so the message keeps most of the width. */
.qnotif-compose .qnotif-to{ flex:0 1 42%; }
.qchat-send svg{ width:16px; height:16px; }
/* Notification dropdown (bell) — reuses .qchat panel chrome */
.qnotif-list{ overflow-y:auto; }
.qnotif-row{ display:flex; align-items:flex-start; gap:11px; width:100%; text-align:left; background:none; border:none; border-bottom:1px solid var(--line); padding:11px 14px; cursor:pointer; font-family:inherit; }
.qnotif-row:hover{ background:var(--hover-bg-strong); }
.qnotif-row.unread{ background:color-mix(in srgb, var(--brass) 7%, transparent); }
.qnotif-row.unread:hover{ background:color-mix(in srgb, var(--brass) 12%, transparent); }
.qnotif-ic{ width:30px; height:30px; flex:none; border-radius:9px; display:grid; place-items:center; }
.qnotif-ic svg{ width:16px; height:16px; }
.qnotif-ic.kind-tag{ background:rgba(93,155,216,.16); color:#5d9bd8; }
.qnotif-ic.kind-quality{ background:rgba(150,123,220,.16); color:#967bdc; }
.qnotif-ic.kind-alert{ background:rgba(224,162,62,.16); color:#e0a23e; }
.qnotif-ic.kind-order{ background:rgba(91,185,139,.16); color:#5bb98b; }
.qnotif-mid{ flex:1; min-width:0; display:flex; flex-direction:column; gap:2px; }
.qnotif-who{ font-size:12.5px; font-weight:700; color:var(--ink); display:flex; align-items:center; gap:6px; }
.qnotif-tagchip{ font-size:9.5px; font-weight:800; color:#5d9bd8; background:rgba(93,155,216,.16); border-radius:999px; padding:1px 6px; }
.qnotif-text{ font-size:11.5px; color:var(--ink-dim); line-height:1.35; }
.qnotif-text b{ color:var(--ink); font-weight:700; }
.qnotif-meta{ display:flex; flex-direction:column; align-items:flex-end; gap:5px; flex:none; }
.qnotif-time{ font-size:10px; color:var(--ink-faint); white-space:nowrap; }
.qnotif-dot{ width:8px; height:8px; border-radius:999px; background:var(--brass); }
.qnotif-jump{ display:inline-flex; align-items:center; gap:3px; margin-left:4px; color:var(--brass-bright); font-weight:700; }
.qnotif-jump svg{ width:12px; height:12px; }
.qnotif-goable{ cursor:pointer; }

/* ── Comms surface extras (team chat + notifications, quasar-comms.js) ── */
/* Header controls: expand / restore + pop-out to a window. */
.qchat-hbtn{ background:none; border:none; color:var(--ink-dim); cursor:pointer; padding:3px; display:grid; place-items:center; border-radius:6px; }
.qchat-hbtn:hover{ color:var(--ink); background:var(--hover-bg-strong); }
.qchat-hbtn svg{ width:16px; height:16px; }

/* In-page expanded panel: larger, centered (JS positions it), draggable by its
   header, and above the docked layer. */
.qchat.qchat--expanded{ width:min(760px, calc(100vw - 40px)); height:min(760px, calc(100vh - 40px)); max-height:none; z-index:1400; box-shadow:0 24px 70px rgba(0,0,0,.5); }
.qnotif.qchat--expanded{ width:min(520px, calc(100vw - 40px)); }
.qchat--expanded > .qchat-head{ cursor:move; }
.qchat--expanded > .qchat-list, .qchat--expanded > .qnotif-list{ flex:1; min-height:0; }
.qchat--expanded .qchat-msgs{ max-height:none; }

/* ── Mail dock panel internals (quasar-mail.js) ─────────────────────────────
   The envelope icon's docked panel rides the qchat shell above; these are its
   mail-specific rows, preview cards and footer. Added 2026-07-10: the panel
   shipped with every qmail-* class unstyled (intent and age ran together as
   bare text, the footer button rendered browser-default white, and the
   confidence dot was invisible because the --qmail-conf-* tokens were never
   defined). All token-built - both themes ride the same rules. */
:root{
  --qmail-conf-high:var(--good);
  --qmail-conf-med:var(--status-amber);
  --qmail-conf-low:var(--status-red);
}
.qmail-row-body{ flex:1; min-width:0; }
.qmail-row-top{ display:flex; align-items:center; gap:8px; margin-bottom:3px; }
.qmail-intent-chip{ font-size:10px; font-weight:700; text-transform:capitalize; color:var(--ink-dim); background:var(--bg-elev); border:1px solid var(--line); border-radius:999px; padding:1px 8px; white-space:nowrap; }
/* HQ-520 round 9 (Adam: "the suggests which say internal questions, other and etc
   pop in a colour"): four intent families, existing palette tokens only (the same
   color-mix tint idiom as .qtoggle.active); 'other'/general keeps the neutral base.
   quasar-mail.js maps the ten agent intents onto data-qi. */
.qmail-intent-chip[data-qi="order"]{ color:var(--ocean); border-color:color-mix(in srgb, var(--ocean) 45%, transparent); background:color-mix(in srgb, var(--ocean) 12%, transparent); }
.qmail-intent-chip[data-qi="sales"]{ color:var(--good); border-color:color-mix(in srgb, var(--good) 45%, transparent); background:color-mix(in srgb, var(--good) 12%, transparent); }
.qmail-intent-chip[data-qi="support"]{ color:var(--rust); border-color:color-mix(in srgb, var(--rust) 45%, transparent); background:color-mix(in srgb, var(--rust) 12%, transparent); }
.qmail-intent-chip[data-qi="internal"]{ color:var(--brass-bright); border-color:color-mix(in srgb, var(--brass-bright) 45%, transparent); background:color-mix(in srgb, var(--brass-bright) 12%, transparent); }
.qmail-age{ margin-left:auto; font-size:11px; color:var(--ink-faint); white-space:nowrap; flex:none; }
.qmail-from{ font-size:12.5px; color:var(--ink); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.qmail-subj{ font-size:12px; color:var(--ink-dim); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.qmail-conf-dot{ width:8px; height:8px; border-radius:999px; flex:none; }
.qmail-inbox-line{ flex:1; min-width:0; font-size:11px; color:var(--ink-faint); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.qmail-list{ min-height:0; }
.qmail-empty{ padding:26px 18px; text-align:center; color:var(--ink-faint); font-size:12.5px; line-height:1.6; }
.qmail-empty--warn{ color:var(--ink-dim); }
.qmail-outbox-link{ color:var(--brass-bright); text-decoration:none; font-weight:600; }
.qmail-outbox-link:hover{ text-decoration:underline; }
/* HQ-520 round 9 (Adam: "the Open mail function isnt aligned"): the footer was
   designed for flex (.qmail-open-btn carries margin-right:auto) but never declared
   it, and the check button's width:100% forced each onto its own row. One row now:
   link left, button filling the right. */
.qmail-footer{ border-top:1px solid var(--line); padding:10px 14px; flex:none; display:flex; align-items:center; gap:12px; }
.qmail-check-btn{ flex:1; background:var(--bg-elev); border:1px solid var(--line); color:var(--ink); border-radius:var(--r-control); padding:7px 10px; font:inherit; font-size:12.5px; font-weight:600; cursor:pointer; }
.qmail-check-btn:hover{ border-color:var(--line-strong); }
.qmail-preview-scroll{ flex:1; min-height:0; overflow-y:auto; padding:12px 14px; display:flex; flex-direction:column; gap:10px; }
.qmail-source-block, .qmail-draft-block{ background:var(--bg-elev); border:1px solid var(--line); border-radius:var(--r-card); padding:10px 12px; flex:none; }
.qmail-source-meta{ display:flex; align-items:center; gap:8px; margin-bottom:6px; }
.qmail-source-from, .qmail-draft-subj{ font-size:12.5px; color:var(--ink); margin-bottom:2px; }
.qmail-source-subj{ font-size:12px; color:var(--ink-dim); margin-bottom:6px; }
.qmail-source-body, .qmail-draft-body{ font-size:12px; color:var(--ink-dim); white-space:pre-wrap; line-height:1.55; max-height:170px; overflow-y:auto; }
.qmail-notes{ margin-top:8px; font-size:11.5px; color:var(--ink-faint); line-height:1.5; }
.qmail-action-msg{ padding:6px 0 0; font-size:12px; color:var(--ink-dim); line-height:1.5; }
.qmail-preview-actions{ display:flex; align-items:center; gap:8px; padding:10px 14px; border-top:1px solid var(--line); flex:none; }
.qmail-open-btn{ margin-right:auto; font-size:12px; color:var(--brass-bright); text-decoration:none; font-weight:600; }
.qmail-open-btn:hover{ text-decoration:underline; }
.qmail-send-btn{ background:var(--brass); border:1px solid transparent; color:var(--brass-on-bg); border-radius:var(--r-control); padding:7px 14px; font:inherit; font-size:12.5px; font-weight:700; cursor:pointer; }
.qmail-send-btn:hover{ background:var(--brass-bright); }
.qmail-discard-btn{ background:none; border:1px solid var(--line); color:var(--status-red); border-radius:var(--r-control); padding:7px 12px; font:inherit; font-size:12.5px; font-weight:600; cursor:pointer; }
.qmail-discard-btn:hover{ border-color:var(--status-red); }

/* "Continue with Google" (outbox.html #gmail-connect): brand-logo sizing.
   Sized to ride the button's 13px label - a step down from the original 17px
   per Adam 2026-07-10. Size only, theme-neutral (the logo is the sanctioned
   full-colour Google SVG in both themes); the rest of .gbtn still lives in
   outbox.html's scoped block, fold-pending. */
.gbtn svg{ width:15px; height:15px; flex:none; }

/* Popped-out window: fill the whole viewport, no floating chrome. */
.qchat.qchat--full{ position:static; width:100%; height:100%; max-height:none; border:none; border-radius:0; box-shadow:none; }
.qchat--full > .qchat-list, .qchat--full > .qnotif-list{ flex:1; min-height:0; }
.qchat--full .qchat-msgs{ max-height:none; }

/* Chat list: name + a two-line preview of the latest message. */
.qchat-subhead{ font-size:10.5px; font-weight:800; letter-spacing:.04em; text-transform:uppercase; color:var(--ink-faint); padding:10px 14px 4px; }
.qchat-rmid{ flex:1; min-width:0; display:flex; flex-direction:column; gap:2px; }
.qchat-preview{ font-size:11px; color:var(--ink-faint); line-height:1.3; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
.qchat-chav{ background:var(--bg-elev); color:var(--ink-dim); border:1px solid var(--line); }
.qchat-chav svg{ width:17px; height:17px; }
.qchat-mwho{ display:block; font-size:10px; font-weight:800; color:var(--ink-faint); margin-bottom:2px; }

/* Deep-link jump chip inside a message bubble. */
.qchat-goto{ display:inline-flex; align-items:center; gap:4px; margin-top:5px; max-width:100%; font-family:inherit; font-size:10.5px; font-weight:700; color:inherit; background:rgba(0,0,0,.14); border:none; border-radius:7px; padding:3px 8px; cursor:pointer; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; text-decoration:none; }
/* HQ-337: inline image-attachment thumbnail inside a chat bubble (tap opens full size). */
.qchat-img{ display:block; max-width:100%; max-height:180px; border-radius:8px; margin-top:6px; cursor:pointer; }
/* HQ-345: integration brand icon tiles (admin Integrations). Base rule is unscoped so
   the tile renders in cards AND centered modals; per-brand backgrounds below (brand
   colours live here, never in a page). */
.ico{width:40px;height:40px;border-radius:10px;display:flex;align-items:center;justify-content:center;color:#fff;flex-shrink:0;font-weight:800;font-size:13px;letter-spacing:.02em}
.ico svg{width:20px;height:20px}
.ico-monday{background:#6161ff}
.ico-xero{background:#13b5ea}
.ico-shippit{background:#7e6bff}
.ico-easyship{background:#00b5b0}
.ico-google_chat{background:#34a853}
.ico-gmail{background:#ea4335}
.ico-anthropic{background:#b08445}
.ico-ringcentral{background:#ff8800}
.ico-default{background:#5d9bd8}
/* HQ-455: the DM person picker (the only .qchat-chanrail user left - the channel pill
   rail itself was replaced by the conversation-list rows below; the dead pill classes
   .qchat-chan / .qchat-chanrail--row went with it). Gutter matches the panel's 12-14px
   siblings instead of sitting flush to the edge. */
.qchat-chanrail{ display:flex; gap:4px; flex-wrap:wrap; padding:6px 12px; border-bottom:1px solid var(--line); margin-bottom:6px; }
.qchat-dmpick{ max-height:160px; overflow-y:auto; }
.qchat-dmpick button{ font-family:inherit; font-size:11px; color:var(--ink); background:transparent; border:1px solid var(--line); border-radius:7px; padding:4px 8px; cursor:pointer; }
.qchat-dmpick button:hover{ background:var(--hover-bg); }
.qchat-goto:hover{ background:rgba(0,0,0,.24); }
.qchat-goto svg{ width:12px; height:12px; flex:none; }

/* HQ-455 Option C: real-mode team chat - a conversation list + a thread, two panes.
   Message CARDS speak the .tmsg idiom the Conversations and support case threads
   already use (bordered panel, one-line meta row, 3px tinted left border), recreated
   here as tokens-only classes because .tmsg itself lives in inbox.html's page style
   block. Bubbles (.qchat-bub) stay for the demo thread and the AI panel - nothing
   existing is restyled. */
.qchat-card{ background:var(--bg-panel-solid); border:1px solid var(--line); border-left:3px solid var(--ink-faint); border-radius:var(--r-card); padding:8px 11px; font-size:12.5px; line-height:1.4; color:var(--ink); }
.qchat-card--mine{ border-left-color:var(--brass); background:color-mix(in srgb, var(--brass) 8%, var(--bg-panel-solid)); }
.qchat-cmeta{ display:flex; align-items:baseline; gap:8px; margin-bottom:3px; }
.qchat-cwho{ font-size:11.5px; font-weight:800; color:var(--ink); }
.qchat-ctime{ margin-left:auto; font-size:10px; color:var(--ink-faint); white-space:nowrap; }
.qchat-card b{ font-weight:800; }
/* The jump/attachment chip assumed a coloured bubble behind it (rgba black wash,
   color:inherit); on a card or a panel note it gets real chrome instead of a smudge. */
.qchat-card .qchat-goto, .qchat-note .qchat-goto{ background:var(--bg-elev); border:1px solid var(--line); color:var(--ink-dim); margin-top:4px; }
.qchat-card .qchat-goto:hover, .qchat-note .qchat-goto:hover{ background:var(--hover-bg); border-color:var(--brass); color:var(--ink); }
/* Prose - notes, empty states, errors, privacy lines - reads as a sentence, never as
   the uppercase .qchat-subhead section label (that class is for CHANNELS / DIRECT
   MESSAGES headings, which is what it was built for). */
.qchat-note{ font-size:12px; color:var(--ink-dim); line-height:1.45; padding:10px 14px; }
.qchat-note--error{ color:var(--status-red); }
.qchat-rnotes, .qchat-rfoot{ flex:none; }
.qchat-rnotes:empty, .qchat-rfoot:empty{ display:none; }
.qchat-rfoot{ border-top:1px solid var(--line); }
.qchat-rfoot .qchat-note{ padding:8px 14px; }
/* The active conversation's row in the list pane. */
.qchat-row.on{ background:var(--hover-bg-strong); }
/* "+ Direct message" reads as a real next action, not a blank list row (HQ-550,
   Adam: "I just feel it's a little blank") - same brass-tint idiom as
   .qnotif-row.unread above and the same colour formula as .btn.accent, just
   scoped to this one row so the plain conversation rows above it are
   untouched. Existing tokens only. */
.qchat-row.qchat-newdm{ background:color-mix(in srgb,var(--brass) 7%,transparent); }
.qchat-row.qchat-newdm:hover{ background:color-mix(in srgb,var(--brass) 12%,transparent); }
.qchat-row.qchat-newdm .qchat-avatar{ background:color-mix(in srgb,var(--brass) 20%,transparent); color:var(--brass-bright); }
.qchat-row.qchat-newdm .qchat-rname{ color:var(--brass-bright); }
/* Two panes side by side (expanded panel / popout; JS adds --split only there and only
   above 700px - the dock keeps one pane + the back arrow). Columns follow the
   .conv-page .conv-body recipe: a bounded list rail and a minmax(0,1fr) thread with
   the min-width:0 blowout guard. Layout only - no new tokens. */
.qchat--split{ display:grid; grid-template-columns:minmax(180px,240px) minmax(0,1fr); grid-template-rows:auto minmax(0,1fr); }
.qchat--split > .qchat-head{ grid-column:1 / -1; }
.qchat--split > .qchat-list{ grid-row:2; grid-column:1; min-height:0; min-width:0; overflow-y:auto; border-right:1px solid var(--line); }
.qchat--split > .qchat-thread{ grid-row:2; grid-column:2; min-height:0; min-width:0; }
.qchat--split .qchat-back{ display:none; }

/* HQ-346: the AI assistant dock panel (connect form + session-local thread). */
.qai-connect{ display:flex; flex-direction:column; gap:10px; padding:14px; }
.qai-connect .qchat-goto{ align-self:flex-start; }
.qai-field{ display:flex; flex-direction:column; gap:5px; }
.qai-field label{ font-size:11.5px; font-weight:700; color:var(--ink-dim); }
.qai-note{ font-size:11.5px; color:var(--ink-faint); line-height:1.45; }
.qai-connhead{ display:flex; align-items:flex-start; gap:8px; padding:10px 14px; border-bottom:1px solid var(--line); font-size:12px; color:var(--ink-dim); line-height:1.45; }
.qai-connhead > span{ flex:1; min-width:160px; }
.qai-connhead b{ color:var(--ink); }
.qai-confirm{ display:flex; align-items:center; gap:8px; flex-wrap:wrap; padding:8px 14px; background:color-mix(in srgb, var(--status-red) 8%, transparent); border-bottom:1px solid var(--line); }
.qai-confirm span{ flex:1; min-width:160px; font-size:12px; color:var(--ink-dim); }
.qai-error{ font-size:12px; color:var(--status-red); padding:6px 14px; }
.qai-thinking{ opacity:.6; font-style:italic; }
.qai-sources{ align-self:flex-start; max-width:82%; margin-top:-4px; font-size:10.5px; color:var(--ink-faint); line-height:1.4; padding:2px 4px 6px; }
.qai-connect .qai-connectbtn:disabled, .qai-confirm .qai-disc-yes:disabled, .qchat-send.qai-send:disabled{ opacity:.5; cursor:not-allowed; }

/* Menu items inside an anchored popover (quote.html .ofs-pop incl. the HQ-358
   order More menu) read as a left-aligned list, not centered pills. */
.ofs-pop .btn{ justify-content:flex-start !important; }

/* inventory.html (body.inv-page) - R25 centered-modal chrome for its .ov/.sheet pair
   (patterns.md s.15) + the edit-item sheet's form classes whose recipes never came
   across when the markup was copied from incoming.html (round-29 B3). The page sits
   exactly at its ui-drift baseline, so these live here, not in the page block. */
.inv-page .sheet{ box-sizing:border-box; align-self:center; margin:auto; border:1px solid var(--line); }
.inv-page .grid2{ display:grid; grid-template-columns:1fr 1fr; gap:10px 14px; }
.inv-page .section{ font-size:11px; letter-spacing:.06em; text-transform:uppercase; color:var(--brass); font-weight:700; margin:18px 0 8px; padding-bottom:6px; border-bottom:1px solid var(--line); }
.inv-page .chk{ display:flex; align-items:center; gap:8px; font-size:12.5px; color:var(--ink); margin:14px 0 0; cursor:pointer; }
.inv-page .hint{ font-size:11px; color:var(--ink-faint); }
.inv-page .muted{ color:var(--ink-dim); }
@media (max-width:700px){ .inv-page .grid2{ grid-template-columns:1fr; } }

/* HQ-509: the "Apply codes" bulk sku-assign screen (inventory.html's #sheet, a wider
   review than a normal item edit). Kept here, not the page's own <style> block - the
   page sits at its ui-drift baseline (see the note above), so new CSS lives in this
   shared file, token-based, scoped by the ska- prefix so it can only ever match this
   one screen's markup. */
.inv-page .ska-tabs{ margin-top:10px; }
.inv-page .ska-search{ flex:1; min-width:180px; }
.inv-page .ska-count{ font-size:11.5px; margin-bottom:6px; }
.inv-page .ska-tblwrap{ max-height:42vh; overflow:auto; border:1px solid var(--line); border-radius:8px; }
.inv-page .ska-sel-count{ align-self:center; margin-right:auto; font-size:12px; }
.inv-page .ska-code-input{ width:auto; min-width:170px; font-family:var(--font-mono,monospace); font-size:12px; }
.inv-page .ska-issue{ color:var(--status-amber); }
.inv-page .ska-row{ display:flex; align-items:center; gap:8px; padding:6px 4px; border-bottom:1px solid var(--line); font-size:12.5px; }
.inv-page .ska-row-name{ flex:1; }
.inv-page .ska-cat-select{ width:auto; min-width:140px; }

/* conversations.html (body.conv-page) - round-3 chrome kept out of the page's own
   <style> block (CLAUDE.md "one look, one file"; the page sits at its ui-drift
   baseline, so these live here). Tokens only, two pieces:
   1) the queue rail is a bounded flex column so the inbox VIEWS stay pinned at the
      top and only the conversation list scrolls (the .case-list rail idiom);
   2) the Intercom import overlay is a CENTERED dialog, not a right-hand sheet - the
      inventory.html .ov/.sheet centering idiom (patterns.md s.15). */
.conv-page .case-list{ display:flex; flex-direction:column; overflow:hidden; }
.conv-page .case-list #views{ flex:none; }
.conv-page .case-list #conv-list{ flex:1 1 auto; min-height:0; overflow-y:auto; }
.conv-page #import-ov.on{ display:flex; align-items:center; justify-content:center; padding:24px; }
.conv-page #import-ov .sheet{ position:relative; inset:auto; width:560px; max-width:94vw; max-height:88vh; border:1px solid var(--line); border-radius:14px; box-shadow:0 20px 60px rgba(0,0,0,.4); }

/* round 5 (Adam 2026-07-20): the context sheet becomes a PERSISTENT right column -
   the support.html case-detail idiom. .conv-body mirrors .case-cols' right two
   tracks (minmax(0,2fr) thread | minmax(300px,1fr) column, gap 14, align start);
   .conv-main carries .case-cols>div:nth-child(2)'s min-width:0 blowout guard;
   the column cards read solid like support's .case-rail .dpanel; the column is
   a bounded sticky scroller (the page's own queue-rail idiom) so the thread
   scrolls, the column scrolls, the page does not. .ctx-off is the header
   Context chip's hidden state (localStorage 'quasar.conv.ctx'). Stacks under
   the thread at support's 1100px breakpoint. Tokens only. */
.conv-page .conv-body{ display:grid; grid-template-columns:minmax(0,2fr) minmax(300px,1fr); gap:14px; align-items:start; }
.conv-page .conv-body>.conv-main{ min-width:0; }
.conv-page .conv-ctx{ min-width:0; position:sticky; top:12px; max-height:calc(100vh - 170px); overflow-y:auto; }
.conv-page .conv-ctx .dpanel{ background:var(--bg-panel-solid); }
.conv-page .conv-body.ctx-off{ grid-template-columns:minmax(0,1fr); }
.conv-page .conv-body.ctx-off .conv-ctx{ display:none; }
@media(max-width:1100px){
  .conv-page .conv-body{ grid-template-columns:1fr; }
  .conv-page .conv-ctx{ position:static; max-height:none; }
}

/* round 5 fix (Adam 2026-07-20 "i cant scroll down - its like the page is
   fixed"): the nav shell pins #app position:fixed to the content area
   (html.qnav-shell #app:has(.topbar), above) with default overflow - the body
   then has nothing to scroll, so anything past the fold (the composer under
   the 56vh thread once the round-5 case-head + context column grew the stack)
   hung below, unreachable. Make the pinned #app the page's single vertical
   scroller - the exact body.crm-page #app precedent (CRM section below). The
   sticky bounded rails (.case-list / .conv-ctx, top:12px) now stick to #app's
   scrollport, so the queue rail and context column pin while the page scrolls
   and the thread keeps its own scroller. Layout-only, no tokens. */
body.conv-page #app{ overflow-y:auto; }

/* Scrollbars (Adam 2026-07-17): color-scheme on the theme roots makes native
   scrollbars follow the theme; this thumb polish keeps them slim and subdued in
   both themes (webkit browsers). Tokens only - adapts to light automatically. */
::-webkit-scrollbar{ width:10px; height:10px; }
::-webkit-scrollbar-track{ background:transparent; }
::-webkit-scrollbar-corner{ background:transparent; }
::-webkit-scrollbar-thumb{ background:color-mix(in srgb, var(--ink-faint) 40%, transparent); border-radius:8px; border:2px solid transparent; background-clip:content-box; }
::-webkit-scrollbar-thumb:hover{ background:color-mix(in srgb, var(--ink-faint) 65%, transparent); border:2px solid transparent; background-clip:content-box; }

/* Compose tools (mention + pin-a-location) and the captured-anchor chip. */
.qchat-tool{ width:30px; height:30px; flex:none; border-radius:8px; border:1px solid var(--line); background:var(--bg-elev); color:var(--ink-dim); font-size:15px; font-weight:800; cursor:pointer; display:grid; place-items:center; }
.qchat-tool:hover{ color:var(--brass); border-color:var(--brass); }
.qchat-tool svg{ width:16px; height:16px; }
.qchat-anchorbar{ padding:7px 12px 0; }
.qchat-anchorchip{ display:inline-flex; align-items:center; gap:6px; max-width:100%; background:color-mix(in srgb, var(--brass) 14%, transparent); border:1px solid color-mix(in srgb, var(--brass) 40%, transparent); color:var(--ink); border-radius:999px; padding:4px 7px 4px 9px; font-size:11px; font-weight:700; }
.qchat-anchorchip svg{ width:13px; height:13px; flex:none; color:var(--brass-bright); }
.qchat-anchorchip > span{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; max-width:230px; }
.qchat-anchorx{ background:none; border:none; color:var(--ink-dim); cursor:pointer; font-size:15px; line-height:1; padding:0 2px; }
.qchat-anchorx:hover{ color:var(--ink); }

/* Pointer-pick ("pin a location") overlay + the scroll-to flash on arrival. */
body.qpin-active, body.qpin-active *{ cursor:crosshair !important; }
.qpin-outline{ position:fixed; z-index:2000; pointer-events:none; border:2px solid var(--brass); background:color-mix(in srgb, var(--brass) 14%, transparent); border-radius:6px; box-shadow:0 0 0 2px color-mix(in srgb, var(--brass) 30%, transparent); transition:all .04s linear; }
.qpin-hint{ position:fixed; z-index:2001; left:50%; bottom:26px; transform:translateX(-50%); background:var(--ink); color:var(--bg); font-size:12px; font-weight:700; padding:8px 15px; border-radius:999px; pointer-events:none; box-shadow:0 6px 24px rgba(0,0,0,.4); }
.qfocus-flash{ animation:qfocusFlash 2.6s ease-out; border-radius:10px; position:relative; z-index:1; }
@keyframes qfocusFlash{
  0%   { box-shadow:0 0 0 3px var(--brass), 0 0 0 9px color-mix(in srgb, var(--brass) 35%, transparent); }
  15%  { box-shadow:0 0 0 3px var(--brass), 0 0 0 9px color-mix(in srgb, var(--brass) 35%, transparent); }
  100% { box-shadow:0 0 0 0 transparent, 0 0 0 0 transparent; }
}

:root[data-theme="light"] .qtopbar-btn .ic-moon{ display:inline-grid; }

/* Back button - shared affordance for any detail/pop-out view (Adam 2026-07-03:
   detail views read as having "no back button"; this is the recognizable one).
   HQ-522 (2026-08-01) first made it a SOLID accent fill; Adam then noticed (round
   4, 2026-08-02) that on a dark screen his eye is pulled to every filled blue box
   equally, so a filled box should be reserved for the one next-action per screen.
   Back is never that action, so it now uses the .btn.accent construction instead
   (see that rule above) - coloured text/border over a faint tint, sitewide via
   this one shared class. */
.backbtn{ display:inline-flex; align-items:center; gap:6px; margin-bottom:10px;
  background:color-mix(in srgb,var(--brass) 9%,transparent); border:1px solid color-mix(in srgb,var(--brass) 42%,transparent);
  border-radius:8px; padding:8px 13px 8px 11px; font-size:12.5px; font-weight:700; color:var(--brass);
  cursor:pointer; font-family:inherit; line-height:1.1; white-space:nowrap; }
.backbtn:hover{ color:var(--brass-bright); border-color:var(--brass-bright); background:color-mix(in srgb,var(--brass) 15%,transparent); }
/* HQ-331: the browser's default blue focus ring read as a "blue square" around the CRM
   record's back button - swap it for the house brass focus treatment. */
.backbtn:focus{ outline:none; }
.backbtn:focus-visible{ outline:2px solid var(--brass); outline-offset:2px; }
.backbtn svg{ width:15px; height:15px; flex:none; }

/* HQ-526 round 2: pages with NO wrapper-class override below (e.g. quote.html,
   support.html) still washed out in light mode - the base .btn.accent rule above
   had no !important, so the glass-lite qctl-bg rules (which do carry !important)
   won the background/border fight and only the text colour survived. The base
   rule now carries !important on all three properties too, matching the wrapper
   block below exactly. The wrapper block itself stays - it is still needed to
   beat a page's OWN local !important fill (see next paragraph), which load order
   alone would not resolve even with the base rule fixed. */

/* HQ-526: until round 3, a dozen pages each hard-coded a blanket
   "<their-own-wrap-class> .btn{background:brass!important}" inside their OWN
   <style> block, so .btn.accent needed this shared !important override to win
   there. Round 3 stripped those page fills on 13 wrappers; round 4 (Adam's
   explicit direction, 2026-08-02) stripped the last one, support.html. This
   block is now belt-and-braces on ALL 14 - kept because it is harmless and it
   hard-stops any future page-local !important fill from swallowing the accent
   tier again. Selectors are each page's genuine, module-specific wrapper class
   (never a bare generic name like .wrap/.actions, which could bleed onto an
   unrelated element on some other page). */
.admin-wrap .btn.accent, .sp-wrap .btn.accent, .co-wrap .btn.accent, .cmp-wrap .btn.accent,
.mf-wrap .btn.accent, .fs-wrap .btn.accent, .rp-wrap .btn.accent, .wh-wrap .btn.accent,
.inv-wrap .btn.accent, .pp-wrap .btn.accent, .rd-wrap .btn.accent, .sc-wrap .btn.accent,
.pj-wrap .btn.accent, .ws-wrap .btn.accent{
  background:color-mix(in srgb,var(--brass) 9%,transparent)!important;
  border:1px solid color-mix(in srgb,var(--brass) 42%,transparent)!important;
  color:var(--brass)!important;
}
.admin-wrap .btn.accent:hover, .sp-wrap .btn.accent:hover, .co-wrap .btn.accent:hover, .cmp-wrap .btn.accent:hover,
.mf-wrap .btn.accent:hover, .fs-wrap .btn.accent:hover, .rp-wrap .btn.accent:hover, .wh-wrap .btn.accent:hover,
.inv-wrap .btn.accent:hover, .pp-wrap .btn.accent:hover, .rd-wrap .btn.accent:hover, .sc-wrap .btn.accent:hover,
.pj-wrap .btn.accent:hover, .ws-wrap .btn.accent:hover{
  filter:none; background:color-mix(in srgb,var(--brass) 15%,transparent)!important;
  border-color:var(--brass-bright)!important; color:var(--brass-bright)!important;
}

/* @mention picker - a small popover a composer can anchor to tag a teammate on a
   record (Adam 2026-07-03: "how do I tag someone on something"). */
.qmention{ position:absolute; z-index:1400; min-width:210px; max-height:240px; overflow-y:auto;
  background:var(--bg-panel-solid); border:1px solid var(--line); border-radius:10px;
  box-shadow:0 16px 44px rgba(0,0,0,.5); padding:5px; }
.qmention[hidden]{ display:none; }
.qmention .qm-h{ font-size:10px; letter-spacing:.06em; text-transform:uppercase; color:var(--ink-faint); font-weight:700; padding:5px 8px 6px; }
.qmention button{ display:flex; align-items:center; gap:8px; width:100%; text-align:left; background:none; border:none;
  border-radius:7px; padding:7px 8px; cursor:pointer; font-family:inherit; font-size:12.5px; color:var(--ink); }
.qmention button:hover, .qmention button.on{ background:var(--hover-bg-strong); }
.qmention .qm-av{ width:24px; height:24px; flex:none; border-radius:999px; display:grid; place-items:center; color:#fff; font-size:10px; font-weight:800; background:var(--brass); }
.qmention .qm-role{ margin-left:auto; font-size:10.5px; color:var(--ink-faint); }
/* @mention token rendered inside a thread/note body */
.mention{ color:#5d9bd8; font-weight:700; background:rgba(93,155,216,.12); border-radius:4px; padding:0 3px; }

/* Hamburger — phones only. */
.qtopbar-burger{
  display:none; width:38px; height:38px; flex:0 0 auto;
  place-items:center; padding:0; border:none; border-radius:10px;
  background:transparent; color:var(--ink-dim); cursor:pointer;
}
.qtopbar-burger svg{ width:22px; height:22px; }

/* Sidebar drawer scrim (phones). */
.qside-scrim{ display:none; }

/* ── Phones (≤900px): sidebar becomes an off-canvas drawer ── */
@media (max-width:900px){
  /* Page overlays pinned with the shell offset tokens (index #dashboard, quote
     .orders-fs) span full width on phones - the sidebar is an off-canvas drawer
     here, so the content offset is zero. Wins over the :root defs (l.584-585) by
     later source order; scoped to :root (not html.qnav-shell) so a page whose nav
     script failed to boot is still correct (drawer off-canvas -> offset 0 either
     way). This is what stops the home globe showing through the left of the
     dashboard on a phone; it deliberately does NOT touch the 40vh globe row rule
     (l.645), so the globe keeps laying out beneath the full-width dashboard. */
  :root{ --qside-off:0px; --qside-off-rail:0px; }
  html.qnav-shell body{ padding-left:0 !important; }
  /* #app stays pinned below the top bar but spans full width (no sidebar gap). */
  html.qnav-shell #app:has(.topbar){ left:0 !important; }
  .qside{ transform:translateX(-100%); width:var(--qside-w) !important; box-shadow:none; }
  html.qsidenav-open .qside{ transform:none; box-shadow:24px 0 70px rgba(0,0,0,.5); }
  html.qsidenav-open .qside-scrim{
    display:block; position:fixed; inset:0; z-index:428; background:var(--modal-overlay);
  }
  .qtopbar{ left:0 !important; padding:0 12px; }
  .qtopbar-burger{ display:grid; }
  .qside-foot{ display:none; }              /* no rail-collapse on phones */
  .qtopbar-search{ flex:0 1 200px; min-width:140px; }
  .qtopbar-who, .qtopbar-role{ max-width:110px; }
}
@media (max-width:560px){
  .qtopbar-search{ display:none; }          /* search lives in the page on tiny screens */
  .qtopbar-whowrap{ display:none; }         /* avatar only */
  .qtopbar-user{ padding:4px; border:none; }
  /* HQ-342: at 390px burger+bell+chat/mail+dot+display-size+theme+avatar
     still crowd the bar even with search/name gone - the zoom menu and the
     save-status dot are the two with least value at this width, so they go. */
  .qtopbar-save{ display:none; }
  /* Home dashboard (index.html #dashboard) on phones: the nowrap .page-head has
     .ph-titles{min-width:0} so the title shrinks instead of wrapping - force the
     stats/controls onto their own full-width row, and drop the KPI grid to one
     column (its own responsive steps stop at 1180px 2-up). Scoped to #dashboard
     so quote.html's .page-head is untouched; the #dashboard .kpis/padding ties
     are won by index.html loading this stylesheet after its inline <style>. */
  #dashboard .page-head{ flex-wrap:wrap; }
  #dashboard .page-head .ph-stats-wrap{ flex:1 1 100%; }
  #dashboard .page-head .ph-ctls{ flex-wrap:wrap; }
  #dashboard .kpis{ grid-template-columns:1fr; }
  #dashboard{ padding:12px 14px 14px; }
}

/* ── Print: the shell is appended to <body> as siblings of #app, so a page's
   own `@media print{#app{display:none}}` won't hide it. Hide all injected
   chrome and undo the content offset so printed PDFs are clean. ── */
@media print{
  .qside, .qtopbar, .qside-scrim, .qimp-banner,
  .qdrawer-btn, .qdrawer-scrim{ display:none !important; }
  html.qnav-shell body{ padding-left:0 !important; padding-top:0 !important; }
  html.qnav-shell #app:has(.topbar){
    position:static !important; inset:auto !important;
    width:100% !important; height:auto !important; margin:0 !important;
  }
}

/* Legacy bottom-clearance class (some pages tagged scroll areas via the old
   dock). Now a no-op so nothing reserves phantom space at the page bottom. */
.qnav-padded{ padding-bottom:0; }

/* ──────────────────────────────────────────────────────────────────────────
   11. LIQUID GLASS EVERYWHERE — one material system for the whole app.
       Chrome surfaces (rails, panels, modals, dropdowns, floating toolbars)
       get the full glass: deep backdrop blur + saturation + rim + top light.
       Controls (buttons, inputs, chips) get glass-lite: the same translucent
       tint, rim and inner highlight WITHOUT backdrop blur — they always sit
       on a glass surface already, and per-element blur murders phone GPUs.
   ────────────────────────────────────────────────────────────────────────── */
:root{
  --qpanel-bg:linear-gradient(160deg, rgba(16,19,26,.62), rgba(6,8,12,.6));  /* de-tinted to near-black (Adam 2026-07-03) */
  /* Flat material — no gradients, no inset top-light. The gloss/"shading" read
     wrong on controls; translucency stays, the shine is gone. (--qctl-hi /
     --qbrass-hi keep a no-op shadow value so list-form box-shadows stay valid.) */
  --qctl-bg:rgba(255,255,255,.06);
  --qctl-bg-hover:rgba(255,255,255,.11);
  --qctl-rim:rgba(255,255,255,.13);
  --qctl-hi:0 0 transparent;
  --qbrass-bg:var(--brass);
  --qbrass-hi:0 0 transparent;
}
html[data-theme="light"], :root[data-theme="light"]{
  --qpanel-bg:linear-gradient(160deg, rgba(255,255,255,.62), rgba(250,248,242,.48));
  --qctl-bg:rgba(255,255,255,.62);
  --qctl-bg-hover:rgba(255,255,255,.85);
  --qctl-rim:rgba(30,36,46,.14);
  --qctl-hi:0 0 transparent;
  --qbrass-bg:var(--brass);
  --qbrass-hi:0 0 transparent;
}

/* Full glass on every chrome surface (upgrades the blur(12) frosting above). */
.rail, .sidebar, aside.sidebar, aside.detail, aside.inspector,
.canvas-toolbar .tool, .canvas-zoomwidget, .ai-panel, .globe-container .legend, .map-search-bar,
.modal .panel, #port-editor .dialog,
.links .navdrop .navdd, .qsearch-dd,
.col, .queue-panel{
  background:var(--qpanel-bg) !important;
  -webkit-backdrop-filter:blur(26px) saturate(190%) !important;
  backdrop-filter:blur(26px) saturate(190%) !important;
}
/* The global command-palette results panel must be SOLID and opaque so results read
   cleanly over ANY page content - the chrome glass above made it translucent (page text
   bled through) in both themes. Overrides the !important glass; single-class specificity
   so this !important wins by cascade order. */
.qsearch-dd{ background:var(--bg-panel-solid) !important; -webkit-backdrop-filter:none !important; backdrop-filter:none !important; }
/* Chrome autofill repaints fields its own near-white regardless of `background`, so an
   autofilled input (e.g. the logged-in email dropped into a search box) shows as a jarring
   white box in dark mode. Pin autofilled fields back to the themed surface: the big inset
   shadow repaints the fill, text-fill-color keeps the text themed, the long transition
   defeats the late browser repaint. */
input:-webkit-autofill, input:-webkit-autofill:hover, input:-webkit-autofill:focus,
textarea:-webkit-autofill, select:-webkit-autofill{
  -webkit-text-fill-color:var(--ink) !important;
  -webkit-box-shadow:0 0 0 1000px var(--bg) inset !important;
  box-shadow:0 0 0 1000px var(--bg) inset !important;
  caret-color:var(--ink);
  transition:background-color 9999s ease-in-out 0s;
}
.rail, .sidebar, aside.sidebar{ border-right:1px solid var(--qglass-rim) !important; }
aside.detail, aside.inspector{ border-left:1px solid var(--qglass-rim) !important; }
.modal .panel, #port-editor .dialog, .qsearch-dd, .links .navdrop .navdd,
.canvas-toolbar .tool, .canvas-zoomwidget, .ai-panel, .globe-container .legend, .map-search-bar, .col{
  border:1px solid var(--qglass-rim) !important;
  box-shadow:var(--qglass-drop), inset 0 1px 0 var(--qglass-hi);
}

/* In-flow cards become glass-lite (tint + rim, no per-card blur). */
.card, .plan-card, .action-card, .gen-card, .placeholder-card, .choice,
.slide-card, .file-card, .project-item{
  background:var(--qctl-bg) !important;
  border:1px solid var(--qctl-rim) !important;
  box-shadow:var(--qctl-hi);
}

/* Buttons — every button class in the app gets the glass-lite finish. */
.btn, .btn-secondary, .upload-btn, .icon-btn, .insp-btn,
.geocode-btn, .add-project-btn, .canvas-toolbar .tool, .canvas-zoomwidget button,
.side-head button.mini, .modal .actions button, .modal-head .actions button,
.refresh-btn, .chip, .filter-chip, .qs-chip,
.expand, .tool{
  background:var(--qctl-bg) !important;
  border:1px solid var(--qctl-rim) !important;
  box-shadow:var(--qctl-hi);
  color:var(--ink-dim);
}
.btn:hover, .btn-secondary:hover, .upload-btn:hover,
.icon-btn:hover, .insp-btn:hover,
.chip:hover, .filter-chip:hover, .qs-chip:hover, .expand:hover,
.tool:hover, .canvas-zoomwidget button:hover{
  background:var(--qctl-bg-hover) !important;
  border-color:var(--line-strong) !important;
  color:var(--ink);
}
/* Primary / brass actions: solid brass, no gloss. */
.btn.primary, .btn-primary, .new-btn, .adm-btn, .newbtn, .rail-head .new,
.qp-head .new,
#config-gate button, #auth-gate button{
  background:var(--qbrass-bg) !important;
  border:1px solid var(--qbrass-bg) !important;
  box-shadow:var(--qbrass-hi);
  color:var(--brass-on-bg) !important;
}
.btn.primary:hover, .btn-primary:hover, .new-btn:hover, .adm-btn:hover,
.newbtn:hover, .rail-head .new:hover, .qp-head .new:hover{
  filter:brightness(1.08);
  color:var(--brass-on-bg) !important;
}
.icon-btn{ background:var(--qctl-bg) !important; }
.icon-btn:hover{ background:var(--qctl-bg-hover) !important; }

/* FIX (readability): several module pages locally declared a solid-brass ".btn" with
   WHITE text (e.g. .btn{background:var(--brass);color:#fff}). The glass base above
   overrides the background to light with !important but leaves the white text in place,
   giving white-on-light = unreadable (e.g. shipping "Mark shipped", incoming "Confirm
   sort"/"Receive goods"/"Print labels & receive"). Force readable dark text on PLAIN
   .btn only. The colour variants (.primary/.success/.danger/.ghost/.accent) set their own
   text colour at higher specificity + !important, so they are excluded here and untouched.
   HQ-526 added .accent to this list - it was missing, so THIS rule (0,6,0) was silently
   beating .btn.accent's own colour rule (0,2,0) everywhere, sitewide, not just on the 14
   wrapper pages (caught live on the deploy preview: quote.html's Print/PDF/Email buttons,
   which carry no wrapper class at all, rendered ink-dim instead of the accent colour).
   HQ-526 round 3 (Adam's Option C pick, 2026-08-02): the grey tier's label is full-strength
   --ink, not --ink-dim - in light mode the dim grey label blended into the glass fill
   ("the font doesnt pop much in light mode"). The tint/border stay quiet; only the label
   carries the weight. */
.btn:not(.primary):not(.success):not(.danger):not(.ghost):not(.act):not(.accent){
  color:var(--ink) !important;
}

/* HQ-383: DISABLED buttons in the LIGHT theme faded to blank pills - the usual
   opacity ~.45 fade over the pale glass background erased the label entirely
   ("Quote rate to customer" / "Packing slip" on Ship Today read as empty boxes).
   Dark theme keeps its fade (enough contrast survives); in light, disabled is
   signalled with muted ink at a readable opacity instead of ghosting the control. */
html[data-theme="light"] .btn:disabled,
html[data-theme="light"] .btn[disabled]{
  opacity:.8;
  color:var(--ink-faint) !important;
  background:rgba(0,0,0,.035) !important;
  border-color:var(--line) !important;
}

/* HQ-526 round 4: the last on-brass white-text rule is GONE. Rounds 1-3 kept a
   ".sp-wrap .btn:not(...){color:var(--brass-on-bg)!important}" carve-out here
   because support.html (Adam's flagged fork) still had its page-local solid
   brass .btn blanket. On 2026-08-02 Adam explicitly directed support.html into
   the Option C tier system ("the support cases never got done"), its blanket's
   fill was stripped like the other 14 pages, and with no brass-filled plain
   buttons left anywhere this rule had nothing legitimate to paint - a revival
   would put white text on the glass grey tier (the invisible-label bug this
   round fixed on website/inbox/conversations). Its total absence is pinned by
   test/button-hierarchy-hq526.test.js. */

/* Dashboard page-head "+ New" (index.html #ph-new, Dashboard + My Dashboard):
   the FILLED accent button, same treatment as sales "+ New Deal" - brass fill,
   accent label, brightness hover. Overrides the page's outline .ph-ctl chrome;
   !important because index.html loads this file after its own <style>, but the
   page hover rule (.page-head .ph-ctl.ph-new:hover) ties on specificity and
   other pages load this file first. Both themes ride --brass / --brass-on-bg. */
.page-head .ph-ctl.ph-new{
  background:var(--brass) !important;
  border:1px solid var(--brass) !important;
  color:var(--brass-on-bg) !important;
}
.page-head .ph-ctl.ph-new:hover{
  background:var(--brass) !important;
  border-color:var(--brass) !important;
  color:var(--brass-on-bg) !important;
  filter:brightness(1.08);
}

/* Inputs / selects / textareas / search fields — glass wells. */
.fld input, .fld select, .fld textarea,
.field input, .field select, .field textarea,
.insp-row input, .insp-row select,
.text-input, .text-area,
.search input, .sidebar-search input, .searchwrap input,
.gate input, #config-gate input, #auth-gate input,
.tags-input, .coord-display, table.lines input.cell,
.library-search input, .diagram-name,
.rail-filters input, .rail-filters select, .toolbar select,
#ct-search, #r-search, .adm-wrap input, .wrap input{
  background:var(--qctl-bg) !important;
  border:1px solid var(--qctl-rim) !important;
  box-shadow:var(--qctl-hi);
  color:var(--ink);
}
.fld input:focus, .fld select:focus, .fld textarea:focus,
.field input:focus, .field select:focus, .field textarea:focus,
.insp-row input:focus, .insp-row select:focus,
.text-input:focus, .text-area:focus, .search input:focus,
.sidebar-search input:focus, #ct-search:focus, #r-search:focus{
  border-color:var(--brass) !important;
}

/* Selected chips/tabs keep a solid fill — glass everywhere else made the
   active state invisible (page-level brass/ink fills lost to the override). */
.chip.active, .filter-chip.active{
  background:var(--qbrass-bg) !important;
  border-color:transparent !important;
  color:var(--brass-on-bg) !important;
  box-shadow:var(--qbrass-hi);
}
/* Pills / badges — translucent glass beads.
   NOT .st-badge: the status badge is a bare colored dot + text with no
   padding or radius, so a glass fill + rim renders as a sharp-edged box
   hugging the text (user-reported). It identifies itself by tone color. */
.badge, .tag-pill, .gmail-pill, .company-pill, .unread-badge,
.pstatus, .rail-item .pill, .selected-contact-pill, .ws-link, .po-link,
.qs-badge{
  background:var(--qctl-bg);
  border:1px solid var(--qctl-rim);
}
/* Status badges stay container-less everywhere — pin it so a page-level
   rule can't re-box them. */
.st-badge{ background:transparent !important; border:none !important; padding:0 !important; }

/* ──────────────────────────────────────────────────────────────────────────
   11b. CANONICAL CONTROLS — every search bar and button is the SAME control.
        Pages had drifted (pill search w/ brass ring on quote/erp vs square
        fields on index/projects/wiring vs bare inline-styled inputs on the
        quote contacts/B2B views; mono-uppercase buttons vs UI-font buttons).
        One spec, pinned with !important so it survives the pages whose
        inline <style> loads after this file (erp/shipping/admin).

        Search field: 36px glass rect (var(--r-control) radius, matches the
        button below), magnifier at left 13px, brass focus ring. Square
        shape is a deliberate 2026-07 theme decision - search inputs are
        controls, not chips/status pills, so they take the control radius,
        not the 999px pill (Adam). Button: 32px glass rect, 8px radius,
        12px/600 UI font.
   ────────────────────────────────────────────────────────────────────────── */
/* — search fields — */
.search, .sidebar-search, .searchwrap, .library-search, .qp-search, .rail-search, .ct-search{
  position:relative !important; display:flex !important; align-items:center !important;
}
.search input, .sidebar-search input, .searchwrap input, .library-search input,
.qp-search input, .rail-search input, .ct-search input,
#ct-search, #b2b-prod-search{
  width:100%; height:36px !important; box-sizing:border-box !important;
  border-radius:var(--r-control) !important;
  padding:0 14px 0 36px !important; margin-bottom:0;
  font-family:var(--font-ui) !important; font-size:13px !important;
  color:var(--ink) !important;
  background:var(--qctl-bg) !important;
  border:1px solid var(--qctl-rim) !important;
  box-shadow:var(--qctl-hi);
  transition:border-color .15s, box-shadow .15s;
}
.search svg, .sidebar-search svg, .searchwrap .si, .library-search svg,
.qp-search svg, .rail-search svg, .ct-search svg{
  position:absolute !important; left:13px !important; top:50% !important;
  transform:translateY(-50%) !important;
  width:15px !important; height:15px !important;
  color:var(--ink-faint) !important; pointer-events:none;
}
/* icon-less inputs (quote B2B view) get the magnifier painted in.
   NOT #ct-search — its .ct-search wrapper has a real inline svg icon, so the
   painted-in copy doubled it up (user-reported). */
#b2b-prod-search{
  background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238a8f98' stroke-width='2' stroke-linecap='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cline x1='21' y1='21' x2='16.5' y2='16.5'/%3E%3C/svg%3E") no-repeat 13px 50% / 15px 15px,
             var(--qctl-bg) !important;
}
.search input:focus, .sidebar-search input:focus, .searchwrap input:focus,
.library-search input:focus, .qp-search input:focus, .rail-search input:focus,
.ct-search input:focus, #ct-search:focus, #b2b-prod-search:focus{
  outline:none !important;
  border-color:var(--brass) !important;
  box-shadow:var(--qctl-hi), 0 0 0 3px rgba(58,127,194,.25) !important;
}

/* Bare search input - the canonical 36px glass pill for an <input> that sits
   directly in a card-head with no .search wrapper (inventory catalogue head).
   Same spec as ".search input" above; magnifier painted in like #b2b-prod-search. */
input.search-bare{
  width:220px; height:36px !important; box-sizing:border-box !important;
  appearance:none; -webkit-appearance:none;
  border-radius:var(--r-control) !important;
  padding:0 14px 0 36px !important; margin-bottom:0;
  font-family:var(--font-ui) !important; font-size:13px !important;
  color:var(--ink) !important;
  background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238a8f98' stroke-width='2' stroke-linecap='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cline x1='21' y1='21' x2='16.5' y2='16.5'/%3E%3C/svg%3E") no-repeat 13px 50% / 15px 15px,
             var(--qctl-bg) !important;
  border:1px solid var(--qctl-rim) !important;
  box-shadow:var(--qctl-hi);
  transition:border-color .15s, box-shadow .15s;
}
input.search-bare::placeholder{ color:var(--ink-faint); }
input.search-bare:focus{
  outline:none !important;
  border-color:var(--brass) !important;
  box-shadow:var(--qctl-hi), 0 0 0 3px rgba(58,127,194,.25) !important;
}

/* — buttons: one 32px glass control (auth-gate CTAs excluded on purpose) — */
.btn, .btn-primary, .btn-secondary, .new-btn, .newbtn, .upload-btn, .adm-btn,
.refresh-btn, .rail-head .new, .qp-head .new, .geocode-btn, .add-project-btn,
.side-head button.mini, .modal .actions button, .modal-head .actions button,
.canvas-toolbar .tool, .danger-zone button{
  display:inline-flex !important; align-items:center !important;
  justify-content:center !important; gap:6px;
  height:32px !important; padding:0 13px !important;
  font-family:var(--font-ui) !important; font-size:12px !important;
  font-weight:600 !important; letter-spacing:.02em !important;
  text-transform:none !important; line-height:1 !important;
  border-radius:var(--r-control) !important;
  cursor:pointer; white-space:nowrap;
}
/* icon-only buttons stay square (same 32px height as everything else) */
.expand, .icon-btn, button.icon-only{ width:32px !important; height:32px !important; padding:0 !important; }
button.icon-only svg{ width:16px; height:16px; flex:none; }
/* outline variant (e.g. Orders' From-PO): brass line + brass icon, no fill */
button.icon-only.outline{
  background:transparent !important;
  border:1px solid var(--brass) !important;
  color:var(--brass) !important;
  box-shadow:none;
  margin-right:6px;
}
.canvas-zoomwidget button{ height:26px !important; padding:0 8px !important; } /* zoom keeps its compact HUD scale */

/* ──────────────────────────────────────────────────────────────────────────
   12. PHONE LAYER (≤820px) — every page works one-handed.
       Side rails become off-canvas glass drawers (toggles injected by
       quasar-nav.js); multi-column shells collapse; scroll areas clear the
       island; inputs hit 16px so iOS doesn't zoom-jump.
   ────────────────────────────────────────────────────────────────────────── */
.qdrawer-btn{ display:none; }
.qdrawer-scrim{ display:none; }
@media (max-width:820px){
  /* collapse the shells to a single column — areas must collapse WITH the
     columns or the named tracks keep reserving phantom width */
  html.qnav-on #app:has(main.project-main){
    grid-template-columns:1fr !important;
    grid-template-areas:"header" "main" !important;
  }
  html.qnav-on #app:has(main.canvas-area){
    grid-template-columns:1fr !important;
    grid-template-areas:"header" "canvas" !important;
  }
  html.qnav-on #app:has(main.globe-container){
    grid-template-columns:1fr !important;
    grid-template-rows:0 1fr !important;
    grid-template-areas:"header" "main" !important;
  }
  html.qnav-on .main{ flex-direction:column !important; }            /* shipping */
  html.qnav-on .queue-panel{
    width:100% !important; max-height:42vh;
    border-right:none !important; border-bottom:1px solid var(--qglass-rim) !important;
  }
  html.qnav-on .ct-grid{ grid-template-columns:1fr !important; }     /* quote contacts */
  html.qnav-on .work{ padding:16px 14px 110px !important; }          /* quote/proposal editors */
  html.qnav-on .pview{ padding:14px 12px 110px !important; }
  html.qnav-on .detail-panel{ padding:16px 14px 110px !important; }

  /* side rails → off-canvas glass drawers */
  html.qnav-on aside.sidebar, html.qnav-on .rail{
    position:fixed !important; top:0; bottom:0; left:0;
    width:min(86vw, 330px) !important; max-height:none !important;
    transform:translateX(-105%); transition:transform .25s;
    z-index:460; border-radius:0 18px 18px 0 !important;
    border-right:1px solid var(--qglass-rim) !important;
    box-shadow:none;
  }
  html.qside-open aside.sidebar, html.qside-open .rail{
    transform:none; box-shadow:30px 0 80px rgba(0,0,0,.45);
  }
  html.qnav-on aside.inspector{
    position:fixed !important; top:0; bottom:0; right:0; left:auto;
    width:min(86vw, 330px) !important;
    transform:translateX(105%); transition:transform .25s;
    z-index:460; border-radius:18px 0 0 18px !important;
    border-left:1px solid var(--qglass-rim) !important;
  }
  html.qinsp-open aside.inspector{ transform:none; box-shadow:-30px 0 80px rgba(0,0,0,.45); }
  html.qside-open .qdrawer-scrim, html.qinsp-open .qdrawer-scrim{
    display:block; position:fixed; inset:0; z-index:459;
    background:var(--modal-overlay);
  }

  /* Floating glass toggle for the inspector drawer (injected by
     quasar-nav.js). HQ-342: bottom-right, not top-right - the top-left
     corner is the real `.qtopbar-burger`'s alone now, and bottom-right stays
     clear of the fixed top bar entirely rather than merely not colliding
     with the burger. (The matching left-side `.qside-btn` toggle no longer
     exists - quasar-nav.js stopped injecting it; left panels restack
     in-flow instead. This rule is harmless if that class is ever re-added.) */
  .qdrawer-btn{
    display:grid; place-items:center; position:fixed; bottom:12px;
    width:40px; height:40px; border-radius:999px !important; z-index:455;
    background:var(--qglass-bg) !important;
    -webkit-backdrop-filter:blur(26px) saturate(190%) !important;
    backdrop-filter:blur(26px) saturate(190%) !important;
    border:1px solid var(--qglass-rim) !important;
    box-shadow:var(--qglass-drop), inset 0 1px 0 var(--qglass-hi);
    color:var(--ink-dim); cursor:pointer;
  }
  .qdrawer-btn svg{ width:19px; height:19px; }
  .qinsp-btn{ right:12px; }

  /* scroll areas clear the floating island */
  .contact-list, .rail-list, .qp-list, .library-list, .diagrams-list,
  .ct-list, .board, .adm-wrap, .wrap, .work-body, .projects-list{
    padding-bottom:96px !important;
  }
  .stepnav{ overflow-x:auto; }                                        /* proposal wizard */

  /* bottom-anchored floaters rise above the island */
  .canvas-zoomwidget{ bottom:84px !important; }                       /* wiring zoom */
  .ai-widget{ bottom:84px !important; }                               /* wiring AI */
  .hud-bl{ bottom:92px !important; }                                  /* globe HUD */

  /* keep the wiring toolbar clear of the floating drawer toggles */
  .canvas-toolbar{ padding-left:56px !important; padding-right:56px !important; }
  .board{ padding:12px 12px 96px !important; }
  .board .col{ flex:0 0 84vw !important; }                            /* erp: swipe cards */

  /* wide tables scroll inside their card instead of bursting the page */
  .card:has(table){ overflow-x:auto; }
  table.lines{ min-width:640px; }

  /* modals fit the screen */
  .modal .panel, .modal-content, .modal .sheet, #port-editor .dialog{
    width:min(94vw, 560px) !important; max-height:86vh !important; overflow:auto;
  }

  /* iOS: sub-16px inputs trigger zoom-on-focus (ids listed to outrank the
     canonical 13px search spec above) */
  input, select, textarea, #ct-search, #b2b-prod-search{ font-size:16px !important; }
  .qsearch .qs-input{ font-size:16px !important; }
}

/* ──────────────────────────────────────────────────────────────────────────
   12b. ROUND-21 MOBILE PASS (HQ-342) - foundation utilities every page
        builder relies on. Defined once here so page-level media blocks don't
        each reinvent them.
   ────────────────────────────────────────────────────────────────────────── */
/* Horizontal-scroll wrapper for wide tables/rows at phone width - the card
   scrolls sideways, the page body never does. Defined at every width: on
   desktop the content already fits so overflow-x:auto shows no scrollbar. */
.qm-scroll{ overflow-x:auto; -webkit-overflow-scrolling:touch; max-width:100%; }

@media (max-width:700px){
  /* Shared `.ov`/`.sheet`, `.qf-ov`/`.qf-sheet` and (round-25, HQ-345) the
     generic `.ov .sheet` descendant pairing go true full-screen at phone
     width. The plain `.ov .sheet` selector is the ONE shared rule for every
     page that has converted its own .ov/.sheet pair to the centered-modal
     treatment (the OVERLAY RULE) - a page builder no longer has to repeat
     this in a per-page media block, though some keep one anyway for other
     phone-only tweaks. incoming.html's .ov/.sheet is already centered and
     wants this same full-screen-on-phone behaviour, so the rule reinforces
     it rather than fighting it. !important beats those pages' non-important
     .sheet width rule regardless of source order, so this still wins even
     if a page builder's own media block forgets to touch it. */
  .crm-page .sheet,
  .qf-sheet,
  .ov .sheet,
  .modal .panel, .modal-content, .modal .sheet, #port-editor .dialog{
    width:100vw !important; height:100vh !important;
    max-width:none !important; max-height:none !important;
    border-radius:0 !important;
    /* round-29 B3 find: without flex:none the sheet (a flex item of the .ov
       scrim) SHRINKS to the scrim's padded flex line (~343px on a 375px phone)
       and the 100vw above never wins - every converted page was affected. */
    flex:none !important;
  }
}

/* ============================================================
   Forms engine (quasar-forms.js) - self-contained, token-based.
   A cross-page module: it renders its own overlay, fill dialog,
   record view, records registry and admin builder using ONLY
   these .qf-* classes + the global theme tokens, so it looks
   identical on every page regardless of that page's scoped CSS.
   ============================================================ */
/* overlay + sheet */
.qf-ov{ position:fixed; inset:0; z-index:1500; background:var(--modal-overlay); display:none; align-items:flex-start; justify-content:center; overflow:auto; padding:40px 16px; }
.qf-ov.open{ display:flex; }
.qf-sheet{ width:min(720px,100%); background:var(--bg-panel-solid); border:1px solid var(--line); border-radius:var(--r-modal); box-shadow:0 24px 60px rgba(0,0,0,.45); padding:20px 22px 22px; }
.qf-sheet.wide{ width:min(920px,100%); }
.qf-head{ display:flex; align-items:flex-start; gap:12px; margin-bottom:6px; }
.qf-head .qf-htext{ flex:1; min-width:0; }
.qf-title{ margin:0; font-size:17px; font-weight:800; color:var(--ink); font-family:var(--font-title); letter-spacing:-.01em; }
.qf-sub{ margin:3px 0 0; font-size:12px; color:var(--ink-dim); }
.qf-ref{ font-family:var(--font-mono); font-size:11px; color:var(--ink-faint); }
.qf-x{ background:transparent; border:1px solid var(--line); color:var(--ink-dim); width:30px; height:30px; border-radius:8px; cursor:pointer; font-size:16px; line-height:1; flex:0 0 auto; }
.qf-x:hover{ color:var(--ink); border-color:var(--line-strong); }
.qf-back{ display:inline-flex; align-items:center; gap:6px; background:transparent; border:0; color:var(--ink-dim); font:inherit; font-size:12px; cursor:pointer; padding:4px 0; margin-bottom:6px; }
.qf-back:hover{ color:var(--brass-bright); }
/* buttons (own, so page-level .btn overrides can't change the engine look) */
.qf-btn{ display:inline-flex; align-items:center; justify-content:center; gap:6px; background:var(--bg-elev); border:1px solid var(--line); color:var(--ink-dim); padding:8px 14px; border-radius:var(--r-control); font:inherit; font-size:12.5px; font-weight:700; cursor:pointer; }
.qf-btn:hover{ color:var(--ink); border-color:var(--line-strong); }
.qf-btn.pri{ background:var(--brass); border-color:transparent; color:var(--brass-on-bg); }
.qf-btn.pri:hover{ background:var(--brass-bright); }
.qf-btn.sm{ padding:6px 11px; font-size:12px; }
.qf-btn.danger:hover{ color:var(--rust); border-color:var(--rust); }
/* section cards inside a form */
.qf-sec{ background:var(--bg-elev); border:1px solid var(--line); border-radius:var(--r-card); padding:14px 15px; margin-top:12px; }
.qf-sec-h{ margin:0 0 4px; font-size:13px; font-weight:800; color:var(--ink); }
.qf-sec-note{ margin:0 0 10px; font-size:11.5px; color:var(--ink-dim); }
/* form grid + fields */
.qf-grid{ display:grid; grid-template-columns:1fr 1fr; gap:12px 14px; }
.qf-fld{ display:flex; flex-direction:column; gap:5px; min-width:0; }
.qf-fld.wide{ grid-column:1 / -1; }
.qf-label{ font-size:11.5px; font-weight:700; color:var(--ink-dim); }
.qf-req{ color:var(--rust); margin-left:2px; }
.qf-input, .qf-select, .qf-textarea{ background:var(--bg); border:1px solid var(--line); color:var(--ink); border-radius:var(--r-control); padding:8px 10px; font:inherit; font-size:13px; width:100%; }
.qf-textarea{ min-height:74px; resize:vertical; }
.qf-input:focus, .qf-select:focus, .qf-textarea:focus{ outline:none; border-color:var(--brass); box-shadow:0 0 0 3px color-mix(in srgb, var(--brass) 18%, transparent); }
.qf-help{ font-size:11px; color:var(--ink-faint); }
.qf-input.bad, .qf-select.bad, .qf-textarea.bad{ border-color:var(--rust); }
/* Required checklist / choice groups get the same missing-field affordance as inputs
   (showErrors adds .bad to the .qf-checklist / .qf-radio wrapper). */
.qf-checklist.bad, .qf-radio.bad, .qf-ynlist.bad, .qf-att.bad{ border-left:2px solid var(--rust); padding-left:8px; margin-left:-10px; }
.qf-errmsg{ display:none; font-size:11px; color:var(--rust); }
.qf-errmsg.show{ display:block; }

/* HQ-441: New-email compose "From" select on inbox.html (.mini-form) and
   outbox.html (.cfld) - the same look as the sibling text input in each panel,
   extended from input/textarea to select. No new tokens; existing rules
   (.mini-form input,.mini-form textarea / .cfld input) copied verbatim per element. */
.mini-form select{width:100%;box-sizing:border-box;background:var(--bg-elev);border:1px solid var(--line);border-radius:8px;padding:8px 10px;font-size:12.5px;color:var(--ink);font-family:var(--font-ui)}
.cfld select{flex:1;min-width:0;background:transparent;border:none;font-size:13.5px;color:var(--ink);font-family:inherit;padding:2px 0}
/* checkbox + checklist */
.qf-chk{ display:flex; align-items:flex-start; gap:9px; padding:6px 0; cursor:pointer; font-size:13px; color:var(--ink); }
.qf-chk input{ appearance:none; -webkit-appearance:none; width:18px; height:18px; flex:0 0 auto; margin-top:1px; border:1.5px solid var(--line-strong); border-radius:5px; background:var(--bg); cursor:pointer; position:relative; }
.qf-chk input:checked{ background:var(--brass); border-color:var(--brass); }
.qf-chk input:checked::after{ content:''; position:absolute; left:5px; top:1px; width:5px; height:10px; border:solid var(--brass-on-bg); border-width:0 2px 2px 0; transform:rotate(45deg); }
.qf-checklist{ display:flex; flex-direction:column; gap:0; }
/* radio group / yes-no-na segmented */
.qf-radio{ display:flex; flex-wrap:wrap; gap:8px; }
.qf-radio label{ display:inline-flex; align-items:center; gap:6px; font-size:12.5px; color:var(--ink-dim); border:1px solid var(--line); border-radius:var(--r-pill); padding:6px 12px; cursor:pointer; }
.qf-radio input{ appearance:none; -webkit-appearance:none; width:12px; height:12px; flex:0 0 auto; border-radius:50%; border:1.5px solid var(--line-strong); position:relative; }
.qf-radio input:checked{ border-color:var(--brass); }
.qf-radio input:checked::after{ content:''; position:absolute; inset:2px; border-radius:50%; background:var(--brass); }
.qf-radio label:has(input:checked){ border-color:var(--brass); color:var(--ink); background:color-mix(in srgb, var(--brass) 12%, transparent); }
/* signature pad */
.qf-sig{ border:1px dashed var(--line-strong); border-radius:var(--r-control); background:var(--bg); }
/* Fixed white pad so a signature (drawn with a fixed dark pen) is legible while
   signing in either theme and matches the forced-white record-view card. */
.qf-sig canvas{ display:block; width:100%; height:110px; border-radius:var(--r-control) var(--r-control) 0 0; cursor:crosshair; touch-action:none; background:#fff; }
.qf-sig-foot{ display:flex; align-items:center; justify-content:space-between; padding:6px 8px; border-top:1px solid var(--line); font-size:11px; color:var(--ink-faint); }
.qf-sig-clear{ background:transparent; border:0; color:var(--ink-dim); font:inherit; font-size:11px; cursor:pointer; }
.qf-sig-clear:hover{ color:var(--brass-bright); }
/* file attach stub */
.qf-file{ display:flex; align-items:center; gap:10px; border:1px dashed var(--line-strong); border-radius:var(--r-control); background:var(--bg); padding:12px; font-size:12.5px; color:var(--ink-dim); cursor:pointer; }
.qf-file:hover{ border-color:var(--brass); color:var(--ink); }
.qf-file .qf-file-name{ color:var(--ink); font-weight:600; }
/* template document link chip */
.qf-doc{ display:flex; align-items:center; gap:10px; border:1px solid var(--line); border-radius:var(--r-control); background:var(--bg-panel-solid); padding:10px 12px; margin-top:2px; text-decoration:none; }
.qf-doc .qf-doc-ic{ width:34px; height:34px; border-radius:8px; display:grid; place-items:center; background:color-mix(in srgb, var(--brass) 16%, transparent); color:var(--brass-bright); flex:0 0 auto; }
.qf-doc .qf-doc-t{ flex:1; min-width:0; }
.qf-doc .qf-doc-t b{ display:block; font-size:12.5px; color:var(--ink); }
.qf-doc .qf-doc-t span{ font-size:11px; color:var(--ink-faint); }
/* footer */
.qf-foot{ display:flex; align-items:center; gap:10px; margin-top:16px; padding-top:14px; border-top:1px solid var(--line); }
.qf-foot .qf-spacer{ flex:1; }
/* pills / tags / avatars for the registry (semantic status hues, theme-safe) */
.qf-pill{ display:inline-block; font-size:11px; font-weight:700; padding:3px 9px; border-radius:var(--r-pill); }
.qf-pill.blue{ background:color-mix(in srgb, var(--brass) 20%, transparent); color:var(--brass-bright); }
.qf-pill.green{ background:color-mix(in srgb, var(--good) 20%, transparent); color:var(--good); }
.qf-pill.amber{ background:color-mix(in srgb, #e0a23e 20%, transparent); color:#e0a23e; }
.qf-pill.violet{ background:color-mix(in srgb, #b08be0 22%, transparent); color:#b08be0; }
.qf-pill.grey{ background:var(--hover-bg-strong); color:var(--ink-dim); }
/* darker status text on the light theme for contrast on the pale tint */
html[data-theme="light"] .qf-pill.blue{ color:var(--brass); }
html[data-theme="light"] .qf-pill.green{ color:#1a7a45; }
html[data-theme="light"] .qf-pill.amber{ color:#a86a12; }
html[data-theme="light"] .qf-pill.violet{ color:#6c46a6; }
.qf-tag{ display:inline-block; font-size:11px; font-weight:600; padding:3px 9px; border-radius:var(--r-pill); border:1px solid var(--line); color:var(--ink-dim); }
.qf-nm{ display:inline-flex; align-items:center; gap:8px; }
.qf-av{ width:26px; height:26px; border-radius:50%; display:grid; place-items:center; font-size:10.5px; font-weight:700; color:#fff; flex:0 0 auto; }
.qf-demo{ font-size:10px; font-weight:700; color:var(--ink-faint); border:1px solid var(--line); border-radius:var(--r-pill); padding:1px 7px; }
/* records registry + builder tables */
.qf-controls{ display:flex; align-items:center; gap:9px; flex-wrap:wrap; margin-bottom:12px; }
.qf-controls .qf-spacer{ flex:1; }
.qf-search{ flex:1; min-width:180px; max-width:340px; background:var(--bg); border:1px solid var(--line); border-radius:var(--r-control); padding:8px 11px; font:inherit; font-size:12.5px; color:var(--ink); }
.qf-fltr{ background:var(--bg); border:1px solid var(--line); border-radius:var(--r-control); padding:8px 10px; font:inherit; font-size:12px; color:var(--ink); }
.qf-tblwrap{ overflow-x:auto; border:1px solid var(--line); border-radius:var(--r-card); }
.qf-tbl{ width:100%; border-collapse:collapse; font-size:12.5px; }
.qf-tbl th{ text-align:left; font-size:10.5px; text-transform:uppercase; letter-spacing:.04em; color:var(--ink-faint); font-weight:700; padding:10px 12px; border-bottom:1px solid var(--line); white-space:nowrap; }
.qf-tbl td{ padding:10px 12px; border-bottom:1px solid var(--line); color:var(--ink); vertical-align:middle; }
.qf-tbl tbody tr:last-child td{ border-bottom:0; }
.qf-tbl tbody tr.click{ cursor:pointer; }
.qf-tbl tbody tr.click:hover td{ background:var(--hover-bg); }
.qf-tbl .qf-mono{ font-family:var(--font-mono); font-size:11.5px; color:var(--ink-dim); }
.qf-empty{ padding:26px; text-align:center; color:var(--ink-faint); font-size:12.5px; }
/* builder inner tabs + list rows */
.qf-tabs{ display:flex; gap:6px; margin-bottom:14px; flex-wrap:wrap; }
.qf-tabs button{ background:transparent; border:1px solid var(--line); color:var(--ink-dim); border-radius:var(--r-pill); padding:6px 14px; font:inherit; font-size:12.5px; font-weight:700; cursor:pointer; }
.qf-tabs button.on{ background:color-mix(in srgb, var(--brass) 14%, transparent); border-color:var(--brass); color:var(--brass-bright); }
.qf-fieldrow{ display:flex; align-items:center; gap:8px; padding:8px 10px; border:1px solid var(--line); border-radius:var(--r-control); margin-bottom:6px; background:var(--bg); }
.qf-fieldrow .qf-fr-t{ flex:1; min-width:0; font-size:12.5px; color:var(--ink); }
.qf-fieldrow .qf-fr-t span{ color:var(--ink-faint); font-size:11px; }
/* key-value in record view */
.qf-kv{ display:grid; grid-template-columns:160px 1fr; gap:7px 14px; font-size:12.5px; margin-top:4px; }
.qf-kv dt{ color:var(--ink-dim); }
.qf-kv dd{ margin:0; color:var(--ink); }
/* small note bar */
.qf-note{ display:flex; gap:8px; align-items:flex-start; font-size:11.5px; color:var(--ink-dim); background:var(--hover-bg); border:1px solid var(--line); border-radius:var(--r-control); padding:9px 11px; margin-top:12px; }
/* per-item Yes / N/A checklist + filler-added rows (Forms engine, HQ-466) */
.qf-ynlist{ display:flex; flex-direction:column; gap:6px; }
.qf-ynrow{ display:flex; align-items:center; gap:10px; padding:6px 10px; border:1px solid var(--line); border-radius:var(--r-control); background:var(--hover-bg); }
.qf-ynlabel{ flex:1; min-width:0; font-size:13px; color:var(--ink); }
.qf-seg{ display:inline-flex; gap:4px; flex-shrink:0; }
.qf-seg-opt{ display:inline-flex; align-items:center; gap:5px; padding:3px 11px; border:1px solid var(--line); border-radius:var(--r-pill); cursor:pointer; font-size:12px; font-weight:600; color:var(--ink-dim); }
.qf-seg-opt:hover{ background:var(--hover-bg-strong); }
.qf-seg-opt:has(input:checked){ background:color-mix(in srgb, var(--brass) 14%, transparent); border-color:var(--brass); color:var(--brass-bright); }
.qf-seg-opt input{ margin:0; }
.qf-addrow{ display:flex; gap:8px; margin-top:6px; }
.qf-addrow .qf-input{ flex:1; }
/* attendees tick-list (people_directory) */
.qf-att-grid{ display:grid; grid-template-columns:repeat(auto-fill,minmax(180px,1fr)); gap:2px 12px; max-height:260px; overflow:auto; padding:4px 2px; }
/* builder drag-and-drop reorder */
.qf-grip{ cursor:grab; color:var(--ink-faint); user-select:none; letter-spacing:-3px; flex-shrink:0; }
.qf-fieldrow.dragging{ opacity:.45; }
.qf-fieldrow.drop-above{ box-shadow:0 -2px 0 var(--brass); }
.qf-fieldrow.drop-below{ box-shadow:0 2px 0 var(--brass); }
@media (max-width:640px){ .qf-grid{ grid-template-columns:1fr; } .qf-kv{ grid-template-columns:1fr; } }

/* ---- Sales overview wow pass (reusable, token-based) --------------------
   First-open "wow" primitives: live ticker strip, urgency chips, leaderboard
   value bars, a readability rework of the pipeline funnel, staged entrance and
   new-feed slide-in. All token-based so both themes hold; the only per-record
   values (a ticker dot's colour, a stagebar segment's flex/colour) stay inline
   in the page. Motion collapses under prefers-reduced-motion at the foot. */

/* Live ticker strip. */
.tickbar{ display:flex; align-items:center; background:var(--bg-panel-solid); border:1px solid var(--line); border-radius:14px; height:40px; overflow:hidden; margin-bottom:12px; }
.tickbar .tk-live{ display:inline-flex; align-items:center; gap:7px; flex:none; padding:0 13px; height:100%; border-right:1px solid var(--line); font-size:10px; font-weight:800; letter-spacing:.08em; text-transform:uppercase; color:#4ade80; }
.tickbar .tk-clip{ flex:1; min-width:0; overflow:hidden; height:100%; display:flex; align-items:center; }
.tk-track{ display:flex; align-items:center; gap:34px; white-space:nowrap; padding-left:34px; animation:tkmarquee 45s linear infinite; will-change:transform; }
.tickbar:hover .tk-track{ animation-play-state:paused; }
@keyframes tkmarquee{ to{ transform:translateX(-50%); } }
.tk-item{ display:inline-flex; align-items:center; gap:8px; flex:none; font-size:12px; color:var(--ink-dim); cursor:pointer; }
.tk-item:hover{ color:var(--ink); }
.tk-item b{ color:var(--ink); font-weight:600; }
.tk-item .tk-dot{ width:6px; height:6px; border-radius:999px; flex:none; background:var(--brass-bright); }
.tk-item .tk-dot.green{ background:#4ade80; }
.tk-item .tk-dot.blue{ background:#5d9bd8; }
.tk-item .tk-dot.amber{ background:#e0a23e; }
.tk-item .tk-dot.violet{ background:#b08be0; }
.tk-item .tk-when{ color:var(--ink-faint); }

/* Pulsing LIVE dot (box-shadow ripple, compositor-only). */
.pulse-dot{ width:7px; height:7px; border-radius:999px; background:currentColor; flex:none; animation:qpulse 2s ease-out infinite; }
@keyframes qpulse{ 0%{ box-shadow:0 0 0 0 currentColor; } 70%,100%{ box-shadow:0 0 0 7px transparent; } }

/* Urgency chips (Needs Me). */
.uchip{ display:inline-flex; align-items:center; font-size:10px; font-weight:800; letter-spacing:.04em; text-transform:uppercase; border-radius:999px; padding:2px 8px; }
.uchip.crit{ background:rgba(224,98,94,.16); color:#e0625e; }
.uchip.warn{ background:rgba(224,162,62,.16); color:#e0a23e; }
.uchip.idle{ background:rgba(160,160,168,.14); color:var(--ink-dim); }

/* Compact graphy-dot sparkline (tight rows). */
.spark-mini{ display:block; width:64px; height:16px; flex:none; opacity:.85; }

/* Leaderboard value bar (Top Accounts). Width driven by --vb from JS. */
.valbar{ display:block; height:4px; border-radius:999px; background:var(--bg); overflow:hidden; margin-top:5px; }
.valbar .vb-fill{ display:block; height:100%; border-radius:999px; background:var(--brass-bright); width:var(--vb,0%); transition:width .9s cubic-bezier(.22,.61,.36,1); }

/* Pipeline-by-Stage readability rework (reusable funnel v2). */
.stagebar{ display:flex; height:6px; border-radius:999px; overflow:hidden; background:var(--bg); margin-bottom:12px; }
/* .stagebar sets display:flex, which overrides the UA [hidden] rule - this
   restores hidden semantics (same fix as .sch-alert[hidden] below) so real
   mode can hide an uninformative one-bucket strip. */
.stagebar[hidden]{ display:none; }
.stagebar .sg{ height:100%; min-width:2px; }
.frow-v2{ cursor:pointer; margin-bottom:10px; }
.frow-v2:last-child{ margin-bottom:0; }
.frow-v2 .fmeta{ display:flex; align-items:baseline; gap:8px; margin-bottom:5px; }
.frow-v2 .fname{ font-size:12px; font-weight:600; color:var(--ink-dim); }
.frow-v2:hover .fname{ color:var(--ink); }
.frow-v2 .fcount{ font-size:10.5px; font-weight:700; color:var(--ink-faint); background:var(--bg); border:1px solid var(--line); border-radius:999px; padding:0 7px; line-height:16px; }
.frow-v2 .fspacer{ flex:1; }
.frow-v2 .fpct{ font-size:11px; color:var(--ink-faint); font-variant-numeric:tabular-nums; }
.frow-v2 .fval{ font-size:12px; font-weight:700; color:var(--ink); font-variant-numeric:tabular-nums; }
.frow-v2 .ftrack{ height:8px; border-radius:999px; background:var(--bg); overflow:hidden; }
.frow-v2 .ffill{ display:block; height:100%; border-radius:999px; width:0; transition:width .8s cubic-bezier(.22,.61,.36,1); }

/* CRM stage/status pill variants (sales.html) - the 9-status CRM palette (Lead
   through Lost) as classed pills, replacing a per-call inline hex style
   (statusPill()); shape (.pill base) stays in sales.html's own <style>, only
   the colour lives here. */
.pill.st-lead{color:var(--status-lead);background:color-mix(in srgb,var(--status-lead) 16%,transparent)}
.pill.st-contacted{color:var(--status-contacted);background:color-mix(in srgb,var(--status-contacted) 16%,transparent)}
.pill.st-discussion{color:var(--status-discussion);background:color-mix(in srgb,var(--status-discussion) 16%,transparent)}
.pill.st-nda{color:var(--status-nda);background:color-mix(in srgb,var(--status-nda) 16%,transparent)}
.pill.st-proposal{color:var(--status-proposal);background:color-mix(in srgb,var(--status-proposal) 16%,transparent)}
.pill.st-negotiating{color:var(--status-negotiating);background:color-mix(in srgb,var(--status-negotiating) 16%,transparent)}
.pill.st-customer{color:var(--status-customer);background:color-mix(in srgb,var(--status-customer) 16%,transparent)}
.pill.st-paused{color:var(--status-paused);background:color-mix(in srgb,var(--status-paused) 16%,transparent)}
.pill.st-lost{color:var(--status-lost);background:color-mix(in srgb,var(--status-lost) 16%,transparent)}

/* Pipeline kanban (sales.html) - subtle per-stage accent (column header dot +
   card left border), reusing the SAME st-* tokens above: STAGES' 5 colours are
   literally a subset of the 9 CRM statuses (lead / qualified=contacted /
   quoting=proposal / negotiation=negotiating / won=customer), so no new hex is
   introduced for the stage accents either. */
.pcol .pch .stripe.s-lead{background:var(--status-lead)}
.pcol .pch .stripe.s-qualified{background:var(--status-contacted)}
.pcol .pch .stripe.s-quoting{background:var(--status-proposal)}
.pcol .pch .stripe.s-negotiation{background:var(--status-negotiating)}
.pcol .pch .stripe.s-won{background:var(--status-customer)}
.pcard.s-lead{border-left:3px solid var(--status-lead)}
.pcard.s-qualified{border-left:3px solid var(--status-contacted)}
.pcard.s-quoting{border-left:3px solid var(--status-proposal)}
.pcard.s-negotiation{border-left:3px solid var(--status-negotiating)}
.pcard.s-won{border-left:3px solid var(--status-customer)}

/* HQ-524 round 3 (sales.html Pipeline, real mode only, Adam: "for lead? in the
   pipeline? its more like quote, negotiation, won") - the real-mode board is 3
   document-driven columns (reusing s-quoting/s-negotiation/s-won above for Quote/
   Negotiation/Won - no new colour), not the demo's 5-stage kanban, so #pipe-cols
   gets a 3-track grid instead of the page's own 5-track one when this class is set;
   same equal-column value the page's own 1200px breakpoint already uses, just
   applied unconditionally rather than only at that width. */
#pipe-cols.pipe-cols-3{grid-template-columns:repeat(3,1fr)}
@media(max-width:760px){#pipe-cols.pipe-cols-3{grid-template-columns:1fr}}

/* Staged first-paint entrance (JS adds .in with a per-block delay). */
.ov-reveal{ opacity:0; transform:translateY(10px); }
.ov-reveal.in{ opacity:1; transform:none; transition:opacity .5s ease, transform .5s cubic-bezier(.22,.61,.36,1); }

/* New activity row slide-in (ticker injector prepends with this class). */
.fe-new{ animation:qfeedin .45s ease; }
@keyframes qfeedin{ from{ opacity:0; transform:translateY(-6px); } }

/* Page classes that replace sales.html static inline styles (ratchet budget). */
.meta-gap{ margin-left:6px; }
.meta-gap-8{ margin-left:8px; }
.cnt.neutral{ background:var(--bg); color:var(--ink-dim); border:1px solid var(--line); }
.lnk-inline{ color:var(--brass-bright); cursor:pointer; }
.tbl-empty{ text-align:center; padding:22px; }
/* Fixed-size table thumbnail (inventory catalogue etc.): caps a natural-size <img>
   to the row so it cannot blow out the column. NOT named .thumb - projects.html /
   wiring.html carry page-scoped .thumb rules that would clash. */
.tbl-thumb{ width:36px; height:36px; flex:none; object-fit:cover; border-radius:var(--r-control); border:1px solid var(--line); background:var(--bg-elev); display:inline-block; vertical-align:middle; }
.helper-pad{ margin:4px 2px; }
.sp-menu button{ display:flex; align-items:center; gap:8px; width:100%; background:transparent; border:none; border-radius:7px; font-size:12.5px; font-weight:600; padding:8px 10px; cursor:pointer; font-family:inherit; text-align:left; color:var(--ink-dim); }
.sp-menu button.on{ color:var(--brass-bright); }
/* Hover/focus for .sp-menu rows lived only in support/conversations page style
   blocks; the shared menu (HQ-411 account menu) needs it everywhere. Tokens only. */
.sp-menu button:hover,.sp-menu button:focus-visible{ background:var(--hover-bg-strong); color:var(--ink); }

/* quote.html: view-tab row above the main content surfaces. Padding aligns
   the buttons to the same left edge as the ct-page-head (22px) so the tab
   row and the section headings below it form one consistent column. */
#quote-viewtabs{ padding:10px 22px 0; border-bottom:1px solid var(--line); margin-bottom:0; }

/* Pipeline board: allow horizontal scroll on narrow content areas so all six
   stage columns remain reachable without the board cutting off on the right.
   The KPI row and toolbar sit outside the scroll container so they stay full-
   width. At viewports above the page's 1180px breakpoint (6-column mode), a
   960px floor stops columns from crushing; below that breakpoint the page's
   own 2-column rule takes over and no min-width is needed. */
.pl-board-scroll{ overflow-x:auto; -webkit-overflow-scrolling:touch; margin-bottom:0; }
@media (min-width:1181px){ .pl-board-scroll .pl-board{ min-width:960px; } }

/* Reduced motion: everything collapses to static. */
@media (prefers-reduced-motion: reduce){
  .tk-track,.pulse-dot,.fe-new{ animation:none!important; }
  .ov-reveal{ opacity:1!important; transform:none!important; }
  .ov-reveal.in{ transition:none!important; }
  .frow-v2 .ffill,.valbar .vb-fill{ transition:none!important; }
}

/* Sales globe search, results flyout and drawer activity (E1 daytime, applied by orchestrator
   from the e1b-sales builder's cssSuggestions; quasar-ui.css stays builder-locked) */
.gf-searchbar{padding:0 8px 8px}
.gf-searchbar .fsearch{position:relative}
.gf-searchbar .fsearch svg{position:absolute;left:10px;top:50%;transform:translateY(-50%);width:14px;height:14px;color:var(--ink-faint);pointer-events:none}
.gf-searchbar .fsearch input{box-sizing:border-box;width:100%;background:var(--bg-panel);border:1px solid var(--line);border-radius:8px;color:var(--ink);font:inherit;font-size:12.5px;padding:7px 10px 7px 30px}
.gf-searchbar .fsearch input:focus{outline:none;border-color:var(--brass)}
.ov-globe .gfly{flex-wrap:wrap}
.gfly-results{width:100%;margin-top:7px;max-height:210px;overflow-y:auto;display:flex;flex-direction:column;gap:1px}
.gfly-results[hidden]{display:none}
.gfr .gfr-s{font-size:10.5px;color:var(--ink-faint);flex:none}
.dd-last{font-size:11.5px;color:var(--ink-dim);margin-bottom:13px}
.dd-last-k{color:var(--ink-faint);font-weight:700;text-transform:uppercase;letter-spacing:.05em;font-size:9.5px;margin-right:5px}
.dd-ae.click{cursor:pointer;border-radius:8px}
.dd-ae.click:hover{background:var(--bg)}
.dd-adot.green{background:#5bb98b}
.dd-adot.blue{background:#5d9bd8}
.dd-adot.violet{background:#b08be0}
.dd-adot.amber{background:#e0a23e}

/* ──────────────────────────────────────────────────────────────────────────
   CRM / CUSTOMER 360 (erp.html) — the page's former <style> block folded in
   as token-based classes (Customer 360 unification, 2026-07-06). Every rule
   is scoped under body.crm-page so nothing here can leak into another page.
   Hard hexes were replaced with the shared status tokens (--status-blue /
   --status-green / --status-amber / --status-violet / --status-red) via
   color-mix for the soft washes. Per-record values (avatar hue, sparkline
   stroke) stay inline in the page, per the one-look-one-file rule.
   ────────────────────────────────────────────────────────────────────────── */
body.crm-page{margin:0;background:var(--bg);color:var(--ink);font-family:var(--font-ui)}
.crm-page .gate{position:fixed;inset:0;display:grid;place-items:center;background:var(--bg);z-index:500}
.crm-page .gate .card{background:var(--bg-panel);border:1px solid var(--line);border-radius:14px;padding:28px;width:340px}
.crm-page .gate h1{font-family:var(--font-display);font-size:22px;margin:0 0 4px}
.crm-page .gate .sub{color:var(--ink-dim);margin-bottom:16px}
.crm-page .gate label{display:block;font-size:12px;color:var(--ink-dim);margin:10px 0 4px}
.crm-page .gate input{width:100%;box-sizing:border-box;background:var(--bg);border:1px solid var(--line);border-radius:8px;padding:9px 11px;color:var(--ink);font-family:inherit}
.crm-page .gate button{margin-top:16px;width:100%;background:var(--brass);color:var(--brass-on-bg);border:none;border-radius:8px;padding:10px;font-weight:700;cursor:pointer}
.crm-page .gate .error{color:var(--rust);font-size:12px;margin-top:8px;min-height:14px}
.crm-page .gate .switch-mode{display:none}

/* ---- page shell ---- */
/* The nav shell pins #app position:fixed to the content area (html.qnav-shell
   #app:has(.topbar), above) with default overflow — which leaves everything
   below the fold unreachable. Make the pinned #app the page's single vertical
   scroller ("the page itself is the only scroller"). Scoped to this page. */
body.crm-page #app{overflow-y:auto}
.crm-page .wrap{padding:12px 22px 16px;max-width:1920px;margin:0 auto}

/* ---- breadcrumb + stacked header ---- */
.crm-page .crumbs{display:flex;align-items:center;gap:7px;font-size:12px;color:var(--ink-faint);margin:0 0 9px}
.crm-page .crumbs a{color:var(--ink-faint);text-decoration:none}
.crm-page .crumbs a:hover{color:var(--ink-dim)}
.crm-page .crumbs .sep{opacity:.5}
.crm-page .head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:14px;flex-wrap:nowrap}
.crm-page .head h1{font-family:var(--font-ui);font-size:23px;font-weight:800;margin:0;color:var(--ink);letter-spacing:-.02em;line-height:1.12;display:flex;align-items:center;gap:10px}
.crm-page .head .sub{display:block;font-size:12.5px;color:var(--ink-dim);margin-top:4px;line-height:1.3}
.crm-page .htags{display:inline-flex;align-items:center;gap:7px;margin-left:4px}
.crm-page .pill{display:inline-flex;align-items:center;gap:6px;font-size:11px;font-weight:700;border-radius:999px;padding:3px 10px;border:1px solid var(--line);color:var(--ink-dim);background:var(--bg-panel-solid);white-space:nowrap}
.crm-page .pill .dot{width:7px;height:7px;border-radius:999px;background:currentColor}
.crm-page .pill.good{color:var(--status-green);border-color:color-mix(in srgb,var(--status-green) 40%,transparent);background:color-mix(in srgb,var(--status-green) 12%,transparent)}
.crm-page .pill.blue{color:var(--status-blue);border-color:color-mix(in srgb,var(--status-blue) 40%,transparent);background:color-mix(in srgb,var(--status-blue) 12%,transparent)}
.crm-page .pill.amber{color:var(--status-amber);border-color:color-mix(in srgb,var(--status-amber) 40%,transparent);background:color-mix(in srgb,var(--status-amber) 12%,transparent)}
.crm-page .pill.violet{color:var(--status-violet);border-color:color-mix(in srgb,var(--status-violet) 40%,transparent);background:color-mix(in srgb,var(--status-violet) 12%,transparent)}
.crm-page .pill.bad{color:var(--status-red);border-color:color-mix(in srgb,var(--status-red) 40%,transparent);background:color-mix(in srgb,var(--status-red) 12%,transparent)}
.crm-page .pill.grey{color:var(--ink-dim)}
.crm-page .head-actions{display:flex;align-items:center;gap:8px;flex:none;white-space:nowrap}
/* Buttons defer to the shared .btn / .btn.primary system (glass-lite secondary,
   brass primary) - only page-specific sizing/state lives here. */
.crm-page .btn{border-radius:8px;padding:8px 14px;font-weight:700;font-size:13px;cursor:pointer;font-family:inherit;display:inline-flex;align-items:center;gap:6px;line-height:1.1}
.crm-page .btn svg{width:15px;height:15px}
.crm-page .btn:disabled{opacity:.45;cursor:not-allowed}

/* ---- header meta row (code / region / currency / status / owner) ---- */
.crm-page .head-meta{display:flex;align-items:center;flex-wrap:wrap;gap:6px 18px;margin-top:7px}
.crm-page .head-meta .hm{display:inline-flex;align-items:center;gap:6px;font-size:12.5px;color:var(--ink);white-space:nowrap}
.crm-page .head-meta .hm .hml{color:var(--ink-dim);font-weight:600}
.crm-page .head-meta .hm .hmv{font-weight:700}
.crm-page .head-meta .copy-btn{background:transparent;border:none;color:var(--ink-faint);cursor:pointer;padding:0 0 0 2px;display:inline-flex;align-items:center}
.crm-page .head-meta .copy-btn svg{width:13px;height:13px}
.crm-page .head-meta .copy-btn:hover{color:var(--brass)}
.crm-page .owner-chip{display:inline-flex;align-items:center;gap:6px}
.crm-page .owner-chip .oav{width:20px;height:20px;border-radius:999px;display:grid;place-items:center;font-size:9.5px;font-weight:800;color:var(--brass-on-bg)}

/* actions dropdown */
.crm-page .menu-wrap{position:relative}
/* HQ-520 round 5 (Adam: "the actions button should be blue as well or ... blue
   highlight square around instead with the writing blue"): the outlined-accent
   treatment - border + text in the theme accent (blue dark / gold light), the
   same look Conversations' Context/Assign/Resolve got - so the filled Edit
   beside it stays the page's one filled primary. */
.crm-page #actions-btn{border-color:var(--brass);color:var(--brass)}
.crm-page #actions-btn:hover{border-color:var(--brass-bright,var(--brass));color:var(--brass-bright,var(--brass))}
.crm-page .menu-dd{position:absolute;right:0;top:calc(100% + 6px);min-width:210px;background:var(--bg-panel-solid);border:1px solid var(--line);border-radius:10px;padding:5px;box-shadow:0 12px 40px rgba(0,0,0,.45);z-index:120;display:none}
.crm-page .menu-dd.open{display:block}
.crm-page .menu-dd button{display:flex;align-items:center;gap:9px;width:100%;background:transparent;border:none;color:var(--ink);font-family:inherit;font-size:13px;text-align:left;padding:8px 10px;border-radius:7px;cursor:pointer}
.crm-page .menu-dd button:hover{background:var(--bg)}
.crm-page .menu-dd button svg{width:15px;height:15px;color:var(--ink-dim);flex:none}
.crm-page .menu-dd .sepline{height:1px;background:var(--line);margin:4px 2px}
.crm-page .btn .caret{width:12px;height:12px;margin-left:2px}

/* ---- KPI tiles (Manufacturing dashboard idiom) ---- */
.crm-page .kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:11px;margin:0 0 11px}
@media(max-width:1000px){.crm-page .kpis{grid-template-columns:repeat(2,1fr)}}
@media(max-width:560px){.crm-page .kpis{grid-template-columns:1fr}}
.crm-page .kpi{position:relative;background:var(--bg-panel-solid);border:1px solid var(--line);border-radius:14px;padding:8px 13px 6px;cursor:pointer;transition:border-color .12s}
.crm-page .kpi:hover{border-color:var(--brass)}
.crm-page .kpi .khead{display:flex;align-items:center;gap:12px;margin-bottom:4px}
.crm-page .kpi .kic{width:46px;height:46px;flex:none;display:grid;place-items:center;border-radius:999px;background:color-mix(in srgb,var(--status-blue) 14%,transparent);color:var(--status-blue)}
.crm-page .kpi .kic svg{width:23px;height:23px}
.crm-page .kpi.green .kic{background:color-mix(in srgb,var(--status-green) 16%,transparent);color:var(--status-green)}
.crm-page .kpi.violet .kic{background:color-mix(in srgb,var(--status-violet) 16%,transparent);color:var(--status-violet)}
.crm-page .kpi.amber .kic{background:color-mix(in srgb,var(--status-amber) 16%,transparent);color:var(--status-amber)}
.crm-page .kpi.bad .kic{background:color-mix(in srgb,var(--status-red) 16%,transparent);color:var(--status-red)}
.crm-page .kpi .ktext{min-width:0;flex:1}
.crm-page .kpi .lbl{font-size:11.5px;font-weight:600;color:var(--ink-dim);line-height:1.2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.crm-page .kpi .val{font-size:18px;font-weight:700;color:var(--ink);line-height:1;letter-spacing:-.02em;margin-top:4px}
.crm-page .kpi.green .val.accent{color:var(--status-green)}
.crm-page .kpi.bad .val.accent{color:var(--status-red)}
.crm-page .kpi .val .vsm{font-size:14px;font-weight:700;color:var(--ink-dim);margin-left:4px}
.crm-page .kpi .sub2{display:block;font-size:11.5px;font-weight:700;color:var(--ink-dim);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.crm-page .kpi svg.spark{display:block;width:100%;height:20px;margin:3px 0 4px}
.crm-page .kpi .delta{display:inline-flex;align-items:center;gap:3px;font-size:11.5px;font-weight:700;color:var(--ink-faint)}
.crm-page .kpi .delta svg{width:11px;height:11px}
.crm-page .kpi .delta.up{color:var(--status-green)}
.crm-page .kpi .delta.down{color:var(--status-red)}

/* ---- demo-account note (sa-* path only) ---- */
.crm-page .crm-demobar{font-size:11.5px;color:var(--ink-faint);margin:0 0 9px;display:flex;align-items:center;gap:7px}
.crm-page .crm-demobar svg{width:13px;height:13px;flex:none}

/* ---- gate banner (deal section) ---- */
.crm-page .gatebar{display:flex;align-items:center;gap:12px;background:color-mix(in srgb,var(--status-violet) 10%,transparent);border:1px solid color-mix(in srgb,var(--status-violet) 35%,transparent);border-radius:12px;padding:11px 15px;margin:0 0 14px}
.crm-page .gatebar .gb-ic{width:34px;height:34px;flex:none;display:grid;place-items:center;border-radius:999px;background:color-mix(in srgb,var(--status-violet) 18%,transparent);color:var(--status-violet)}
.crm-page .gatebar .gb-ic svg{width:18px;height:18px}
.crm-page .gatebar .gb-txt{flex:1;min-width:0;font-size:12.5px;color:var(--ink-dim);line-height:1.4}
.crm-page .gatebar .gb-txt b{color:var(--ink);font-weight:700}
.crm-page .gatebar .gb-txt .soft{color:var(--status-red);font-weight:700}

/* ---- card / panel (manufacturing chrome: 14px pad, 16px rhythm) ---- */
.crm-page .cols2{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:16px}
.crm-page .cols3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;margin-bottom:16px}
@media(max-width:1100px){.crm-page .cols2,.crm-page .cols3{grid-template-columns:1fr}}
.crm-page .card{background:var(--bg-panel-solid);border:1px solid var(--line);border-radius:14px;overflow:hidden;display:flex;flex-direction:column}
.crm-page .card-head{display:flex;align-items:center;gap:8px;padding:9px 14px;border-bottom:1px solid var(--line)}
.crm-page .card-head h3{margin:0;font-size:14px;font-weight:700;color:var(--ink);display:flex;align-items:center;gap:8px}
.crm-page .card-head h3 svg{width:16px;height:16px;color:var(--ink-dim)}
.crm-page .card-head .csub{font-size:11.5px;color:var(--ink-faint);font-weight:500;margin-left:2px}
.crm-page .card-head .spacer{flex:1}
.crm-page .card-head .lnk{font-size:12px;font-weight:600;color:var(--brass-bright,var(--brass));cursor:pointer;text-decoration:none;white-space:nowrap}
.crm-page .card-head .lnk:hover{text-decoration:underline}
.crm-page .card-head .cnt{font-size:11px;font-weight:700;color:var(--ink-dim);background:var(--bg);border:1px solid var(--line);border-radius:999px;padding:1px 9px}
.crm-page .card-body{padding:10px 14px 12px}

/* clickable table rows */
.crm-page table.tbl tr.click{cursor:pointer}
.crm-page table.tbl tbody tr.click:hover td{background:color-mix(in srgb,var(--status-blue) 6%,transparent)}
.crm-page table.tbl td .subt{color:var(--ink-faint);font-size:11px}

/* linked-records: three groups side by side, each mini-table full width */
.crm-page .lr-cols{display:grid;grid-template-columns:repeat(3,1fr);gap:16px}
@media(max-width:900px){.crm-page .lr-cols{grid-template-columns:1fr}}
.crm-page .lr-group{min-width:0}
.crm-page .lr-title{font-size:12px;font-weight:700;color:var(--ink);margin:0 0 5px}
.crm-page .lr-title span{color:var(--ink-faint);font-weight:500}
/* key contact: owner block + a two-per-row detail grid */
.crm-page .kc-grid{display:grid;grid-template-columns:1fr 1fr;gap:0 40px}
@media(max-width:700px){.crm-page .kc-grid{grid-template-columns:1fr}}
.crm-page .kc-owner{display:flex;align-items:center;gap:11px;padding:0 0 12px;margin-bottom:6px;border-bottom:1px solid var(--line);cursor:pointer;grid-column:1/-1}
.crm-page .kc-owner .cc-name{margin:0;font-size:14px}
.crm-page .kv-note{color:var(--ink-dim);font-weight:600}
.crm-page .kv-dim{color:var(--ink-dim)}

/* activity/audit two-column card */
.crm-page .aa-grid{display:grid;grid-template-columns:1fr 1fr;gap:0}
@media(max-width:1100px){.crm-page .aa-grid{grid-template-columns:1fr}}
.crm-page .aa-col{padding:2px 4px}
.crm-page .aa-col.left{border-right:1px solid var(--line);padding-right:18px}
@media(max-width:1100px){.crm-page .aa-col.left{border-right:none;padding-right:4px;border-bottom:1px solid var(--line);padding-bottom:8px;margin-bottom:8px}}
.crm-page .aa-col .aa-lbl{font-size:10.5px;letter-spacing:.05em;text-transform:uppercase;color:var(--ink-faint);font-weight:700;margin:0 0 6px}
.crm-page .audit-row{display:flex;align-items:flex-start;gap:10px;padding:8px 0;border-bottom:1px solid var(--line);font-size:12.5px}
.crm-page .audit-row:last-child{border-bottom:none}
.crm-page .audit-row .ash{width:22px;height:22px;flex:none;border-radius:999px;display:grid;place-items:center;background:color-mix(in srgb,var(--status-green) 14%,transparent);color:var(--status-green)}
.crm-page .audit-row .ash svg{width:13px;height:13px}
.crm-page .audit-row .amain{flex:1;min-width:0}
.crm-page .audit-row .aact{color:var(--ink);line-height:1.3}
.crm-page .audit-row .aact .who{color:var(--ink-dim);font-weight:700}
.crm-page .audit-row .awhen{font-size:11px;color:var(--ink-faint);margin-top:2px}

/* rows / lists */
.crm-page .listcap{max-height:222px;overflow:auto}
.crm-page .prow{display:flex;align-items:center;gap:11px;padding:9px 0;border-bottom:1px solid var(--line)}
.crm-page .prow:last-child{border-bottom:none}
.crm-page .avatar{width:32px;height:32px;flex:none;border-radius:999px;display:grid;place-items:center;font-size:12px;font-weight:700;color:var(--brass-on-bg)}
.crm-page .prow .pmain{flex:1;min-width:0}
.crm-page .prow .pname{font-size:13px;font-weight:600;color:var(--ink);line-height:1.2}
.crm-page .prow .prole{font-size:11.5px;color:var(--ink-dim)}
.crm-page .prow .pcontact{font-size:11.5px;color:var(--ink-faint);text-align:right;white-space:nowrap}
.crm-page .prow .pcontact a{color:var(--brass-bright,var(--brass));text-decoration:none}

.crm-page table.tbl{width:100%;border-collapse:collapse;font-size:12.5px}
.crm-page table.tbl th{text-align:left;font-size:10.5px;letter-spacing:.05em;text-transform:uppercase;color:var(--ink-faint);font-weight:700;padding:7px 10px;border-bottom:1px solid var(--line);white-space:nowrap}
.crm-page table.tbl td{padding:8px 10px;border-bottom:1px solid var(--line);color:var(--ink);vertical-align:middle}
.crm-page table.tbl tr:last-child td{border-bottom:none}
.crm-page table.tbl td.num{text-align:right;font-variant-numeric:tabular-nums;font-weight:700;white-space:nowrap}
.crm-page table.tbl td.nw{white-space:nowrap}
.crm-page table.tbl .ref{color:var(--brass-bright,var(--brass));font-weight:700}

/* key-value (Account Standing / Contact) */
.crm-page .kv{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:9px 0;border-bottom:1px solid var(--line);font-size:12.5px}
.crm-page .kv:last-child{border-bottom:none}
.crm-page .kv .kvl{color:var(--ink-dim)}
.crm-page .kv .kvv{color:var(--ink);font-weight:700;text-align:right}
.crm-page .kv .kvv .ref{color:var(--brass-bright,var(--brass))}
.crm-page .contact-card .cc-name{font-size:15px;font-weight:800;color:var(--ink);margin:2px 0 1px}
.crm-page .contact-card .cc-role{font-size:12px;color:var(--ink-dim);margin-bottom:8px}
.crm-page .contact-card .cc-line{display:flex;align-items:center;gap:8px;font-size:12.5px;color:var(--ink-dim);padding:5px 0}
.crm-page .contact-card .cc-line svg{width:14px;height:14px;flex:none;color:var(--ink-faint)}
.crm-page .contact-card .cc-line a{color:var(--brass-bright,var(--brass));text-decoration:none}

/* activity / audit */
.crm-page .act{display:flex;gap:11px;padding:9px 0;border-bottom:1px solid var(--line)}
.crm-page .act:last-child{border-bottom:none}
.crm-page .act .aic{width:28px;height:28px;flex:none;border-radius:999px;display:grid;place-items:center;background:color-mix(in srgb,var(--status-blue) 14%,transparent);color:var(--status-blue)}
.crm-page .act .aic svg{width:15px;height:15px}
.crm-page .act .aic.green{background:color-mix(in srgb,var(--status-green) 16%,transparent);color:var(--status-green)}
.crm-page .act .aic.amber{background:color-mix(in srgb,var(--status-amber) 16%,transparent);color:var(--status-amber)}
.crm-page .act .aic.violet{background:color-mix(in srgb,var(--status-violet) 16%,transparent);color:var(--status-violet)}
.crm-page .act .amain{flex:1;min-width:0}
.crm-page .act .atext{font-size:12.5px;color:var(--ink);line-height:1.35}
.crm-page .act .atext .ref{color:var(--brass-bright,var(--brass));font-weight:700}
.crm-page .act .ameta{font-size:11px;color:var(--ink-faint);margin-top:2px;display:flex;gap:8px}

/* next action */
.crm-page .next-field{margin:6px 0 0}
.crm-page .next-field .nf-lbl{font-size:11px;color:var(--ink-faint);font-weight:700;text-transform:uppercase;letter-spacing:.04em;margin-bottom:4px}
.crm-page .next-field .nf-box{background:var(--bg);border:1px solid var(--line);border-radius:9px;padding:9px 11px;font-size:13px;color:var(--ink);display:flex;align-items:center;gap:8px}
.crm-page .next-field .nf-box svg{width:14px;height:14px;color:var(--ink-faint)}
.crm-page .next-note{font-size:11.5px;color:var(--ink-faint);margin-top:9px}

.crm-page .notebar{display:flex;gap:10px;align-items:flex-start;font-size:12px;color:var(--ink-dim);background:var(--bg-panel);border:1px solid var(--line);border-radius:10px;padding:10px 13px;margin-top:4px}
.crm-page .notebar svg{width:16px;height:16px;flex:none;color:var(--ink-faint);margin-top:1px}

/* hidden subtab strip - the left sidebar drives section switch */
.crm-page .subtabs{display:none}
.crm-page .sec{display:none}
.crm-page .sec.on{display:block}

/* ── HQ-497: Book Tech Call - the centred pop-out and the weekly slot grid ────
   .ov-center is added by the booking flow only (other modals keep their top-anchored
   behaviour): stretch + margin:auto centres BOTH axes while a panel taller than the
   viewport still scrolls from its top - the align-items:center clip trap from the
   ticket. The grid: days across, half-hour slots down; a slot the technician cannot
   take is .busy ("Unavailable" - never why: availability reasons are a SECRET column). */
.ov.ov-center{ align-items:stretch }
.ov.ov-center .sheet{ margin:auto }
.wk-nav{ display:flex; align-items:center; gap:8px; margin-bottom:6px }
.wk-nav .wk-label{ flex:1; text-align:center; font-size:12px; font-weight:700; color:var(--ink) }
.wk-grid{ width:100%; border-collapse:collapse; table-layout:fixed; font-size:11px }
.wk-grid th{ padding:4px 2px; font-size:10px; text-transform:uppercase; letter-spacing:.04em; color:var(--ink-faint); text-align:center }
.wk-grid td{ padding:1px }
.wk-grid .wk-time{ width:44px; color:var(--ink-faint); font-size:10px; text-align:right; padding-right:6px; white-space:nowrap }
.wk-slot{ display:block; width:100%; border:1px solid var(--line); border-radius:5px; background:var(--bg); color:var(--ink-dim); font-family:inherit; font-size:10.5px; padding:3px 0; cursor:pointer; text-align:center }
.wk-slot:hover{ border-color:var(--brass); color:var(--ink) }
.wk-slot.busy{ background:var(--bg-panel-solid); color:var(--ink-faint); border-style:dashed; cursor:not-allowed; text-decoration:line-through }
.wk-slot.sel{ background:var(--brass); color:var(--brass-on-bg,#fff); border-color:var(--brass); font-weight:700 }
/* ── HQ-503: the chat message popup ("Sameera has messaged you") ─────────────
   Top-right, under the topbar, above page content; click opens the chat pop-out,
   auto-dismisses after 6s. Token-based, both themes. */
.qcpop{ position:fixed; top:64px; right:18px; z-index:120; max-width:320px;
  background:var(--bg-panel-solid); border:1px solid var(--line-strong,var(--line));
  border-left:3px solid var(--brass); border-radius:10px; padding:11px 14px;
  font-size:13px; color:var(--ink); cursor:pointer; box-shadow:0 14px 38px rgba(0,0,0,.5);
  animation:qcpop-in .18s ease-out }
.qcpop--out{ opacity:0; transition:opacity .35s }
@keyframes qcpop-in{ from{ opacity:0; transform:translateY(-6px) } to{ opacity:1; transform:none } }

/* ---- HQ-516: order line grips + qty steppers + contact-modal label wrap ----
   The grip drags a line row to a new position; .ln-drop-above paints the insertion
   edge with the brass accent. Steppers stack small up/down arrows beside the qty
   input and step by exactly 1 (fractional service-hour quantities preserved).
   .ncg lets the New contact modal's long uppercase labels wrap instead of clip. */
table.lines th.ln-h{ width:24px }
table.lines td.ln-drag{ width:24px; padding:0 2px; text-align:center }
.ln-grip{ cursor:grab; color:var(--ink-faint); font-size:14px; user-select:none; display:inline-block; padding:2px 5px }
.ln-grip:hover{ color:var(--ink) }
tr.ln-dragging{ opacity:.45 }
tr.ln-drop-above td{ box-shadow:inset 0 2px 0 var(--brass) }
table.lines td.qtycell-wrap{ white-space:nowrap }
.qty-steps{ display:inline-flex; flex-direction:column; vertical-align:middle; margin-left:3px; gap:1px }
.qty-step{ border:1px solid var(--line); background:var(--bg); color:var(--ink-dim); font-family:inherit; font-size:8px; line-height:1; padding:1px 4px; cursor:pointer; border-radius:3px }
.qty-step:hover{ color:var(--ink); border-color:var(--brass) }
.ncg .fld label{ white-space:normal; overflow:visible; line-height:1.5 }
/* HQ-516 round 2 (Adam): the order-page action bar reads as one commanding accent row.
   var(--brass) IS the theme accent - blue in dark (#3a7fc2), gold in light (#b5820e) -
   so this is existing tokens doing exactly what he asked, not a theme change. */
.qhead .actions .btn{ background:var(--brass); color:var(--brass-on-bg); border-color:transparent }
.qhead .actions .btn:hover{ background:var(--brass-bright); color:var(--brass-on-bg) }
.qhead .actions .btn[disabled]{ opacity:.55 }
/* HQ-516 round 3: the "not on any product yet - x" suffix wraps as one unit - the x
   button stops stranding on its own line under long category names. */
.b2b-tbl .cat-note{ white-space:nowrap; display:inline-block }

/* ---- modal (stacks ABOVE the pop-out sheet so sheet actions can compose) ---- */
.crm-page .modal-back{position:fixed;inset:0;background:var(--modal-overlay);backdrop-filter:blur(3px);z-index:1300;display:none;align-items:flex-start;justify-content:center;padding:48px 16px;overflow:auto}
.crm-page .modal-back.open{display:flex}
.crm-page .modal{background:var(--bg-panel-solid);border:1px solid var(--line);border-radius:14px;width:520px;max-width:100%;box-shadow:0 24px 70px rgba(0,0,0,.55);animation:crm-mpop .14s ease}
@keyframes crm-mpop{from{transform:translateY(8px);opacity:.4}to{transform:none;opacity:1}}
.crm-page .modal-head{display:flex;align-items:flex-start;gap:10px;padding:16px 18px;border-bottom:1px solid var(--line)}
.crm-page .modal-head .mh-ic{width:34px;height:34px;flex:none;border-radius:9px;display:grid;place-items:center;background:color-mix(in srgb,var(--status-blue) 14%,transparent);color:var(--status-blue)}
.crm-page .modal-head .mh-ic svg{width:18px;height:18px}
.crm-page .modal-head h2{margin:0;font-size:16px;font-weight:800;color:var(--ink)}
.crm-page .modal-head .mh-sub{font-size:12px;color:var(--ink-dim);margin-top:2px}
.crm-page .modal-head .mx{margin-left:auto;background:transparent;border:none;color:var(--ink-faint);cursor:pointer;font-size:20px;line-height:1;padding:2px 4px}
.crm-page .modal-head .mx:hover{color:var(--ink)}
.crm-page .modal-body{padding:16px 18px}
.crm-page .fgrid{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.crm-page .field{margin-bottom:12px}
.crm-page .field.full{grid-column:1/-1}
.crm-page .field label{display:block;font-size:11.5px;font-weight:700;color:var(--ink-dim);margin-bottom:5px}
.crm-page .field input,.crm-page .field select,.crm-page .field textarea{width:100%;box-sizing:border-box;background:var(--bg);border:1px solid var(--line);border-radius:8px;padding:9px 11px;color:var(--ink);font-family:inherit;font-size:13px}
.crm-page .field textarea{resize:vertical;min-height:74px}
.crm-page .field input:focus,.crm-page .field select:focus,.crm-page .field textarea:focus{outline:none;border-color:var(--brass)}
.crm-page .field .ferr{color:var(--rust);font-size:11px;margin-top:4px;min-height:12px;display:block}
.crm-page .field input.bad,.crm-page .field select.bad,.crm-page .field textarea.bad{border-color:var(--rust)}
.crm-page .modal-foot{display:flex;justify-content:flex-end;gap:8px;padding:14px 18px;border-top:1px solid var(--line)}
.crm-page .kvlist{margin:0}
.crm-page .kvlist .kv{padding:8px 0}

/* ---- centered modal (.ov/.sheet idiom, round-25 HQ-345 - was a right-hand
   pop-out drawer; the OVERLAY RULE moved every edit/create/peek surface to a
   centered modal. Numbers mirror .qf-ov/.qf-sheet, the forms-engine house
   reference.) ---- */
.crm-page .ov{position:fixed;inset:0;z-index:1200;background:rgba(0,0,0,.55);display:none;align-items:flex-start;justify-content:center;padding:40px 16px;overflow:auto}
.crm-page .ov.open{display:flex}
.crm-page .sheet{background:var(--bg-panel-solid);width:min(680px,100%);max-width:100%;max-height:88vh;overflow:auto;padding:18px;box-sizing:border-box;border-radius:var(--r-modal);box-shadow:0 24px 60px rgba(0,0,0,.45)}
.crm-page .sheet h3{margin:0 0 2px;font-size:17px;font-weight:800;color:var(--ink);display:flex;align-items:center;gap:9px;flex-wrap:wrap}
.crm-page .sheet .sub{font-size:12px;color:var(--ink-dim);margin-bottom:12px}
.crm-page .sheet .x{float:right;background:transparent;border:none;color:var(--ink-dim);font-size:22px;cursor:pointer;line-height:1;padding:0 2px}
.crm-page .sheet .x:hover{color:var(--ink)}
.crm-page .sheet .sh-sec{font-size:10.5px;letter-spacing:.05em;text-transform:uppercase;color:var(--ink-faint);font-weight:700;margin:16px 0 4px}
.crm-page .sheet .openin{display:inline-flex;align-items:center;gap:6px;margin-top:16px;font-size:13px;font-weight:700;color:var(--brass-bright,var(--brass));text-decoration:none;cursor:pointer}
.crm-page .sheet .openin:hover{text-decoration:underline}
.crm-page .sheet .sh-actions{display:flex;gap:8px;margin-top:12px;flex-wrap:wrap}
.crm-page .ref[data-act]{cursor:pointer}
.crm-page .ref[data-act]:hover{text-decoration:underline}
.crm-page [data-act="open-staff"].owner-chip,.crm-page [data-act="open-contact"].cc-name{cursor:pointer}
.crm-page .nf-box[data-act]{cursor:pointer;transition:border-color .12s}
.crm-page .nf-box[data-act]:hover{border-color:var(--brass)}
/* clickable feed rows (activity + audit) open the entry detail */
.crm-page .act.click,.crm-page .audit-row.click{cursor:pointer;border-radius:8px}
.crm-page .act.click:hover,.crm-page .audit-row.click:hover{background:color-mix(in srgb,var(--status-blue) 6%,transparent)}
.crm-page [data-act="open-staff"].owner-chip:hover .hmv{color:var(--brass-bright,var(--brass))}

/* toast - fixed bottom-right (patterns.md s.10, matches manufacturing #mf-toasts).
   Shared by erp.html (.crm-page) and inventory.html (.inv-page, HQ-359). */
.crm-page .toast-stack,.inv-page .toast-stack{position:fixed;bottom:20px;right:20px;z-index:1100;display:flex;flex-direction:column;gap:8px;align-items:flex-end}
.crm-page .toast,.inv-page .toast{background:var(--bg-panel-solid);border:1px solid var(--line);border-radius:8px;padding:10px 16px;font-size:13px;color:var(--ink);box-shadow:0 8px 30px rgba(0,0,0,.4)}
.crm-page .toast.good,.inv-page .toast.good{border-color:color-mix(in srgb,var(--status-green) 50%,transparent)}
.crm-page .toast.error,.inv-page .toast.error{border-color:color-mix(in srgb,var(--status-red) 50%,transparent)}
.crm-page #app{display:none}

/* ---- Customer 360 real-record additions (?contact=<uuid> path) ---- */
/* read-only field value line inside the details card */
.crm-page .crm-ro{display:flex;justify-content:space-between;align-items:baseline;gap:12px;padding:8px 0;border-bottom:1px solid var(--line);font-size:12.5px}
.crm-page .crm-ro:last-child{border-bottom:none}
.crm-page .crm-ro .l{color:var(--ink-dim);flex:none}
.crm-page .crm-ro .v{color:var(--ink);font-weight:700;text-align:right;min-width:0;overflow-wrap:anywhere}
.crm-page .crm-ro .v .dim{color:var(--ink-faint);font-weight:500}
/* custom-pricing rows (ported from the quote.html contact editor) */
.crm-page .cp-row{display:flex;gap:8px;align-items:center;margin-bottom:6px;flex-wrap:wrap}
.crm-page .cp-row select,.crm-page .cp-row input{background:var(--bg);border:1px solid var(--line);border-radius:6px;color:var(--ink);font:inherit;font-size:12.5px;padding:6px 8px}
.crm-page .cp-row .x{background:transparent;border:none;color:var(--rust);cursor:pointer;font-size:15px}
.crm-page .cat-row{display:flex;align-items:center;gap:12px;padding:9px 12px;border-radius:8px;cursor:pointer}
.crm-page .cat-row:hover{background:var(--hover-bg-strong)}
.crm-page .cat-row .ci{flex:1;min-width:0}
.crm-page .cat-row .cd{font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-size:12.5px}
.crm-page .cat-row .cs{font-family:var(--font-mono);font-size:11px;color:var(--ink-faint)}
/* inline add-person form (People card) */
.crm-page .crm-addform{border-top:1px solid var(--line);margin-top:10px;padding-top:12px}
.crm-page .crm-addform .fgrid{grid-template-columns:1fr 1fr 1fr}
@media(max-width:900px){.crm-page .crm-addform .fgrid{grid-template-columns:1fr}}
.crm-page .crm-addform .actions{display:flex;gap:8px;justify-content:flex-end}
/* muted helper text */
.crm-page .muted{color:var(--ink-faint);font-size:12px}
/* Explanatory lead-in paragraph inside a modal body (e.g. the archive gate). */
.crm-page .modal-note{color:var(--ink-dim);font-size:13px;line-height:1.5;margin:0 0 14px}
.crm-page .crm-notes{white-space:pre-wrap;font-size:12.5px;color:var(--ink);line-height:1.5;max-height:260px;overflow:auto}
/* Structured note entries on the contact record (HQ-331): author name + date
   line above the text, admin-only remove button on the right. */
.crm-page .crm-note-row{padding:8px 0;border-bottom:1px solid var(--line)}
.crm-page .crm-note-row:last-child{border-bottom:none;padding-bottom:2px}
.crm-page .crm-note-meta{display:flex;align-items:center;gap:4px;font-size:11.5px;margin-bottom:3px}
.crm-page .crm-note-who{font-weight:700;color:var(--ink)}
.crm-page .crm-note-meta .note-x{margin-left:auto;background:none;border:none;color:var(--ink-faint);cursor:pointer;font-size:15px;line-height:1;padding:2px 6px;border-radius:5px}
.crm-page .crm-note-meta .note-x:hover{color:var(--rust);background:var(--hover-bg)}
.crm-page .crm-note-text{font-size:12.5px;color:var(--ink-dim);line-height:1.5;white-space:pre-wrap}

/* ── Table status groups (Support Cases: monday-style Resolved group) ──────
   A full-width clickable group row inside a .tbl table: green-barred title
   with a count; the page JS renders the group's rows only while .openg. */
table.tbl tr.grouprow td{ padding:10px 12px; background:var(--bg); color:var(--ink-dim); font-weight:700; font-size:12px; letter-spacing:.02em; cursor:pointer; box-shadow:inset 3px 0 0 var(--good); }
table.tbl tr.grouprow:hover td{ color:var(--ink); }
table.tbl tr.grouprow .gr-chev{ display:inline-block; width:10px; margin-right:8px; color:var(--ink-faint); transition:transform .12s; }
table.tbl tr.grouprow.openg .gr-chev{ transform:rotate(90deg); }
table.tbl tr.grouprow .cnt{ font-size:11px; font-weight:700; color:var(--ink-faint); background:var(--bg-panel-solid); border:1px solid var(--line); border-radius:999px; padding:1px 7px; margin-left:8px; }
table.tbl tr.grouprow .gr-hint{ float:right; font-weight:500; font-size:11.5px; color:var(--ink-faint); }

/* HQ-444: a "no login yet" row in a shared .tbl table (admin Users & Roles: a
   directory-only person rendered alongside real accounts) - dimmed via the same
   opacity-only convention admin.html's own archived-role rows use, no new tokens. */
table.tbl tr.dironly td{ opacity:.6; }

/* HQ-464 (Adam, 2026-07-26: "can you also put brackets in description what each
   tick box does?"): the muted "what ticking this grants" line under a role name in
   the Administration roles tick list (.rolespick, web/admin.html). It matches the
   .sd sub-label a toggle row already uses, which could not be reused directly -
   that rule is scoped to .switch/.tglrow. Existing tokens only, no new colour.
   white-space:normal is load-bearing: the row's own cells are nowrap, and without
   it a description would run off the side instead of wrapping under the name. */
.rolespick .axrow .rdesc{ display:block; margin-top:2px; font-size:11px; font-weight:400; line-height:1.35; color:var(--ink-faint); white-space:normal; }

/* ── In-table search typeahead (Support Cases search box) ──────────────────
   .searchwrap wraps the input; .cs-sugg is the dropdown of matching records
   (hidden by default; the page JS fills it and flips display). */
.searchwrap{ position:relative; display:inline-flex; }
.cs-sugg{ display:none; position:absolute; top:calc(100% + 6px); right:0; min-width:390px; max-width:470px; max-height:324px; overflow:auto; background:var(--bg-panel-solid); border:1px solid var(--line); border-radius:11px; box-shadow:0 12px 32px rgba(0,0,0,.35); z-index:60; padding:5px; }
.cs-sugg .csr{ display:flex; align-items:center; gap:10px; padding:8px 10px; border-radius:8px; cursor:pointer; }
.cs-sugg .csr:hover,.cs-sugg .csr.sel{ background:var(--hover-bg); }
.cs-sugg .csr .id{ font-family:var(--font-mono,monospace); font-size:11.5px; font-weight:700; color:var(--brass-bright); white-space:nowrap; }
.cs-sugg .csr .meta{ min-width:0; flex:1; }
.cs-sugg .csr .nm{ display:block; font-size:12.5px; font-weight:600; color:var(--ink); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.cs-sugg .csr .mt{ display:block; font-size:11px; color:var(--ink-faint); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.cs-sugg .csr .mt b{ color:var(--ink-dim); font-weight:600; }
.cs-sugg .cs-empty{ padding:12px; text-align:center; font-size:12px; color:var(--ink-faint); }

/* ============================================================
   SUPPORT DASHBOARD - "Option F" (support.html #sec-dashboard)
   Reusable token-based classes moved out of support.html's own
   <style> block per the one-look-one-file rule (dropped its .vbf
   preview scope; renamed the preview vbf-* containers to
   chartrow / dashmid / dashstrip). Purely additive: every class
   here (statusband, chartrow, chartcard, ch-*, plot, type-chips,
   dashmid, dashstrip, strip-*, fb-*, attn-chips, achip, sbchip)
   is unused on any other page, so no other screen changes.
   Colours come from the shared tokens (--status-* / --ink* /
   --line / --bg*); the Google / Trustpilot brand-square identity
   colours are hoisted to --brand-google / --brand-trustpilot
   below so this section stays free of raw hex literals.
   ============================================================ */
:root{--brand-google:#4285F4;--brand-trustpilot:#00b67a}
/* band-dot pulse: lives beside its .statusband consumer, not page-side */
@keyframes hpulse{0%,100%{opacity:1}50%{opacity:.35}}
/* 1) slim live-status band (~44px), replaces the old dash-hero card */
.statusband{display:flex;align-items:center;gap:14px;min-height:44px;background:var(--bg-panel-solid);border:1px solid var(--line);border-radius:12px;padding:0 16px;margin-bottom:16px}
/* dashboard top stack sits on the same 16px rhythm as the lower half:
   status band -> KPIs (band margin above) and KPIs -> charts (below).
   Scoped to the band's own sibling so the shared #dash-kpis id on other
   pages is untouched. */
.statusband + #dash-kpis{margin-bottom:16px}
/* Operations dashboard Approvals card (HQ-520, Adam: "way too big for some
   reason") - #dash-approvals had no height cap at all, unlike the equivalent
   bounded-scroll list elsewhere on this page (.oscol .olist, operations.html's
   own <style>). Bounded the same way rather than adding to that page's already-
   ratcheted inline block ("one look, one file"). */
#dash-approvals{max-height:230px;overflow-y:auto}
.statusband .sb-live{display:inline-flex;align-items:center;gap:8px;font-size:12px;font-weight:800;color:var(--good);white-space:nowrap;flex:none}
.statusband .sb-live .dot{width:8px;height:8px;border-radius:999px;background:currentColor;flex:none;animation:hpulse 1.6s infinite}
.statusband .sb-sent{font-size:12.5px;color:var(--ink-dim);line-height:1.3;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.statusband .sb-sent b{color:var(--ink);font-weight:700}
.statusband .spacer{flex:1;min-width:8px}
.statusband .sb-chips{display:flex;align-items:center;gap:8px;flex:none}
.statusband .sbchip{display:inline-flex;align-items:center;gap:7px;background:var(--bg);border:1px solid var(--line);border-radius:999px;padding:5px 12px;font-size:11.5px;font-weight:600;color:var(--ink-dim);white-space:nowrap}
.statusband .sbchip b{color:var(--ink);font-weight:800;font-variant-numeric:tabular-nums}
.statusband .sbchip .cd{width:7px;height:7px;border-radius:999px;background:var(--brass-bright);flex:none}
.statusband .sbchip.red{border-color:color-mix(in srgb,var(--status-red) 40%,transparent);color:var(--status-red)}
.statusband .sbchip.red b{color:var(--status-red)}
.statusband .sbchip.red .cd{background:var(--status-red)}
@media(max-width:760px){.statusband{flex-wrap:wrap;padding:10px 14px;gap:8px 12px}.statusband .sb-sent{white-space:normal}.statusband .spacer{display:none}.statusband .sb-chips{width:100%}}

/* 2) two COMPACT trend cards side by side (50/50), ported from Option B but
   shrunk to roughly the visual weight of the KPI tiles above (the big hero
   charts were far too large). Tighter head + padding, short plot. */
.chartrow{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:16px;align-items:stretch}
@media(max-width:1000px){.chartrow{grid-template-columns:1fr}}
.chartcard{display:flex;flex-direction:column}
.chartcard .card-head{align-items:flex-start;padding:9px 14px 8px;flex-wrap:wrap;gap:10px}
.chartcard .ch-title{display:flex;flex-direction:column;gap:1px;min-width:0}
.chartcard .ch-title h3{font-size:13px;font-weight:700;color:var(--ink);margin:0;letter-spacing:-.01em}
.chartcard .ch-title .ch-cap{font-size:10.5px;color:var(--ink-faint)}
/* small muted chart-head stats: demoted below the KPI tiles so the page
   reads band -> tiles -> charts (not a jump to the chart corners). The
   figures duplicate the band/tiles, so they sit as quiet context here. */
.chartcard .ch-kpis{display:flex;gap:16px;margin-left:auto;flex-wrap:wrap}
.chartcard .ch-kpis .k{display:inline-flex;align-items:baseline;gap:5px;text-align:right;cursor:pointer;border-radius:8px;padding:2px 4px;margin:-2px -4px;transition:background .12s}
.chartcard .ch-kpis .k:hover{background:var(--hover-bg)}
.chartcard .ch-kpis .k .n{font-size:13.5px;font-weight:700;color:var(--ink-dim);line-height:1;letter-spacing:0;font-variant-numeric:tabular-nums}
.chartcard .ch-kpis .k .n.red{color:var(--status-red)}
.chartcard .ch-kpis .k .n.green{color:var(--ink-dim)}
.chartcard .ch-kpis .k .n.amber{color:var(--ink-dim)}
.chartcard .ch-kpis .k .l{font-size:10.5px;color:var(--ink-faint);text-transform:none;letter-spacing:0;font-weight:600}
.chartcard .card-body{padding:10px 14px 11px;display:flex;flex-direction:column;flex:1}
/* the plot itself: SVG stretches to card width, fixed SHORT height so the card
   reads as a compact trend box (was 220px hero). preserveAspectRatio="none"
   means the vbLineChart SVG just scales to fit - no JS change needed. */
.chartcard .plot{width:100%;height:124px;display:block}
.chartcard .plot svg{display:block;width:100%;height:100%}
/* axis / grid text drawn in the SVG uses currentColor via classes below */
.chartcard .plot .axlbl{font-size:9.5px;fill:var(--ink-faint);font-family:var(--font-mono,monospace)}
.chartcard .plot .gridln{stroke:var(--line);stroke-width:1}
/* tiny chart legend row (opened vs resolved / case volume) */
.chartcard .ch-legend{display:flex;align-items:center;gap:14px;flex-wrap:wrap;font-size:10.5px;color:var(--ink-dim);margin-top:8px}
.chartcard .ch-legend .lg{display:inline-flex;align-items:center;gap:6px}
.chartcard .ch-legend .lg .sw{width:9px;height:9px;border-radius:3px;flex:none}

/* cases-by-type inline count chips (footer of the LEFT chart card) - replaces
   the removed donut card (Option B treatment). Each chip filters the Cases
   list. Sits below the Case Volume legend, tucked tight under the plot. */
.type-chips{display:flex;align-items:center;gap:7px;flex-wrap:wrap;margin-top:9px;padding-top:9px;border-top:1px solid var(--line)}
.type-chips .tc{display:inline-flex;align-items:center;gap:6px;background:var(--bg);border:1px solid var(--line);border-radius:999px;padding:3px 10px;font-size:11px;font-weight:600;color:var(--ink-dim);cursor:pointer;transition:border-color .12s,color .12s}
.type-chips .tc:hover{border-color:var(--brass);color:var(--ink)}
.type-chips .tc .dot{width:8px;height:8px;border-radius:2px;flex:none}
.type-chips .tc b{color:var(--ink);font-weight:800;font-variant-numeric:tabular-nums}

/* 3) lower half (ported from Option B). Middle row: Open RMAs (wider) + Team
   Performance ~55/45. Bottom: one slim full-width strip - Feedback inline
   (left) + Needs Attention count chips (right). */
.dashmid{display:grid;grid-template-columns:1.25fr 1fr;gap:14px;margin-bottom:16px;align-items:start}
@media(max-width:1100px){.dashmid{grid-template-columns:1fr}}
.dashstrip{margin-bottom:0}
.dashstrip .card-body{padding:11px 18px}
.dashstrip .strip-in{display:grid;grid-template-columns:minmax(0,1fr) auto minmax(0,1.1fr);gap:0;align-items:center}
@media(max-width:900px){.dashstrip .strip-in{grid-template-columns:1fr;gap:14px}}
.dashstrip .strip-in .strip-div{width:1px;align-self:stretch;background:var(--line);margin:0 22px}
@media(max-width:900px){.dashstrip .strip-in .strip-div{display:none}}
.dashstrip .strip-lbl{font-size:10px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-faint);font-weight:700;margin-bottom:7px}
/* feedback side, laid out horizontally in the slim strip (condensed inline) */
.dashstrip .fb-b{display:flex;align-items:center;gap:16px;flex-wrap:wrap;min-width:0}
.dashstrip .fb-b .fb-score{display:flex;align-items:baseline;gap:5px}
.dashstrip .fb-b .fb-score .big{font-size:28px;font-weight:800;color:var(--ink);letter-spacing:-.02em;line-height:1}
.dashstrip .fb-b .fb-score .out{font-size:12px;color:var(--ink-faint)}
.dashstrip .fb-b .fb-mid{display:flex;flex-direction:column;gap:3px;min-width:0}
.dashstrip .fb-b .fb-mid .stars{color:var(--status-amber);font-size:14px;letter-spacing:1px;line-height:1}
.dashstrip .fb-b .fb-mid .cap{font-size:10.5px;color:var(--ink-faint);line-height:1.3}
.dashstrip .fb-b .fb-src{display:flex;gap:8px;flex-wrap:wrap}
.dashstrip .fb-b .fb-src .fs{display:inline-flex;align-items:center;gap:7px;cursor:pointer;border:1px solid var(--line);border-radius:999px;padding:3px 11px 3px 4px;transition:border-color .12s}
.dashstrip .fb-b .fb-src .fs:hover{border-color:var(--brass)}
.dashstrip .fb-b .fb-src .fs .sq{width:20px;height:20px;border-radius:6px;display:grid;place-items:center;font-weight:800;font-size:10px;color:var(--brass-on-bg);flex:none}
.dashstrip .fb-b .fb-src .fs .sq.g{background:var(--brand-google)}.dashstrip .fb-b .fb-src .fs .sq.t{background:var(--brand-trustpilot)}
.dashstrip .fb-b .fb-src .fs .txt{font-size:11px;color:var(--ink);font-weight:700;line-height:1.1}
.dashstrip .fb-b .fb-src .fs .txt small{display:block;font-size:10px;color:var(--ink-faint);font-weight:600}
/* needs-attention as clickable count chips (right of strip) */
.dashstrip .attn-chips{display:flex;align-items:center;gap:9px;flex-wrap:wrap;justify-content:flex-end}
@media(max-width:900px){.dashstrip .attn-chips{justify-content:flex-start}}
.dashstrip .attn-chips .achip{display:inline-flex;align-items:center;gap:8px;background:var(--bg);border:1px solid var(--line);border-radius:999px;padding:5px 13px 5px 7px;font-size:12px;font-weight:600;color:var(--ink-dim);cursor:pointer;transition:border-color .12s,color .12s;white-space:nowrap}
.dashstrip .attn-chips .achip:hover{color:var(--ink)}
.dashstrip .attn-chips .achip .cnt{min-width:21px;height:21px;padding:0 6px;border-radius:999px;display:inline-grid;place-items:center;font-size:11.5px;font-weight:800;color:var(--brass-on-bg);flex:none;font-variant-numeric:tabular-nums}
.dashstrip .attn-chips .achip.red{border-color:color-mix(in srgb,var(--status-red) 40%,transparent)}.dashstrip .attn-chips .achip.red .cnt{background:var(--status-red)}.dashstrip .attn-chips .achip.red:hover{border-color:var(--status-red)}
.dashstrip .attn-chips .achip.amber{border-color:color-mix(in srgb,var(--status-amber) 40%,transparent)}.dashstrip .attn-chips .achip.amber .cnt{background:var(--status-amber)}.dashstrip .attn-chips .achip.amber:hover{border-color:var(--status-amber)}
.dashstrip .attn-chips .achip.violet{border-color:color-mix(in srgb,var(--status-violet) 40%,transparent)}.dashstrip .attn-chips .achip.violet .cnt{background:var(--status-violet)}.dashstrip .attn-chips .achip.violet:hover{border-color:var(--status-violet)}
.dashstrip .attn-chips .achip.blue{border-color:color-mix(in srgb,var(--status-blue) 40%,transparent)}.dashstrip .attn-chips .achip.blue .cnt{background:var(--status-blue)}.dashstrip .attn-chips .achip.blue:hover{border-color:var(--status-blue)}
.dashstrip .attn-chips .achip.zero{opacity:.5;cursor:default}
.dashstrip .attn-chips .achip.zero:hover{color:var(--ink-dim);border-color:var(--line)}
.dashstrip .attn-chips .achip.zero .cnt{background:var(--ink-faint)}

/* ──────────────────────────────────────────────────────────────────────────
   MAIL (outbox.html) - Gmail-backed label chips + the compact searchable label
   picker. A label carries its own Gmail colour, which cannot be a class; it
   rides the --lbl custom property set inline per record (the sanctioned
   per-record inline style, same precedent as avatar backgrounds / status dots).
   These classes build the swatch from --lbl so no colour lives in the page.
   ────────────────────────────────────────────────────────────────────────── */
.lchip.gm{background:color-mix(in srgb,var(--lbl) 20%,transparent);color:var(--lbl)}
/* the picker's search box (a direct child of the .lbl-pop popover shell) */
.lblpick-search{width:100%;box-sizing:border-box;background:var(--bg);border:1px solid var(--line);border-radius:var(--r-control);color:var(--ink);font:inherit;font-size:12px;padding:6px 9px;margin-bottom:6px}
.lblpick-search:focus{outline:none;border-color:var(--brass)}
/* scrollable label list, capped at roughly 8 rows so the popover stays small */
.lblpick-list{max-height:228px;overflow-y:auto;display:flex;flex-direction:column;gap:1px}
.lblpick-empty{padding:12px 8px;text-align:center;color:var(--ink-faint);font-size:12px;line-height:1.5}
/* per-row colour dot (solid Gmail colour) + name */
.lbl-dot{width:10px;height:10px;border-radius:var(--r-pill);flex:none;background:var(--lbl,var(--ink-faint))}
.lbl-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* small print inside a generated-attachment chip (was an inline style) */
.att-hint{opacity:.55;font-size:.85em}

/* Inbox view (the "Inbox" chip on outbox.html) - read-only Gmail messages shown
   alongside the AI-draft queues. Rows reuse .drow; these add the snippet line,
   the unread marker and the per-row draft-state chip. All token-built. */
.drow .isnippet{font-size:11px;color:var(--ink-faint);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin:2px 0 0}
.drow .from.unread{font-weight:800;color:var(--ink)}
.unread-dot{display:inline-block;width:7px;height:7px;border-radius:var(--r-pill);background:var(--brass);margin-right:6px;vertical-align:middle}
.draft-state{display:inline-flex;align-items:center;flex:none;font-size:9.5px;font-weight:700;border-radius:var(--r-pill);padding:2px 8px;white-space:nowrap;background:color-mix(in srgb,var(--ink-dim) 16%,transparent);color:var(--ink-dim)}
.draft-state.pending{background:color-mix(in srgb,var(--status-amber) 16%,transparent);color:var(--status-amber)}
.draft-state.sent{background:color-mix(in srgb,var(--status-green) 16%,transparent);color:var(--status-green)}
.draft-state.failed{background:color-mix(in srgb,var(--status-red) 16%,transparent);color:var(--status-red)}

/* Ring / donut centre overlays (operations Supplier Performance avg-score ring,
   compliance Internal Audit Stage 2 readiness ring, inventory components donut):
   the page-block .ctr overlays are display:grid + place-items:center with two
   children (number + label). Grid's default align-content (normal = stretch)
   stretches the two auto rows to fill the overlay, pushing the number above the
   ring centre and the label below it. Centre the row group so the pair sits in
   the middle of the ring. Layout-only - theme-safe in dark and light. */
.ring .ctr,
.donut .ctr{align-content:center}

/* ── Manufacturing: real-mode honest empty state (demo unaffected) ─────────── */
.mf-empty{padding:26px 14px;text-align:center;font-size:12.5px;color:var(--ink-faint)}

/* ── Donut centre text: centre the label lines and keep the label inside the
      ring hole. Fixes the Roster & Capacity donut where a wrapped label painted
      left-aligned and read as off-centre (dctr default text-align is start). ── */
.donut .dctr{text-align:center}
.donut .dctr span{display:block;max-width:84px;line-height:1.25;margin-top:2px}

/* ── Scheduling low-staff banner: the page rule .sch-alert sets display:flex,
      which overrides the UA [hidden] rule - this restores hidden semantics so
      real mode can keep the banner off. Higher specificity wins over .sch-alert. */
.sch-alert[hidden]{display:none}

/* ── Support Calendar Month view (HQ-441, support.html #sec-schedule) ───────
   The week grid already had its own hour-by-hour layout in support.html's own
   <style> block (that page's per-page CSS is not grown further under the
   ui-drift ratchet, so this new view's CSS lives here instead). Mon-Fri only,
   matching the week grid's working-week focus. Built ONLY from tokens already
   used by that week grid (var(--line), var(--ink)/--ink-dim/--ink-faint,
   var(--bg-panel-solid), var(--bg), var(--brass)/--brass-bright,
   var(--hover-bg), var(--r-control), var(--r-pill)) - no new colour/font/token. */
.sch-month-grid{display:flex;flex-direction:column;gap:6px}
.sch-month-grid[hidden]{display:none} /* the markup ships hidden until the first render */
/* support.html RMA detail: an action button rendered inside a key-value row (the
   Create-serial offer) - the page only sizes .ab inside .actgrid/.ch-actions, so an
   unscoped drop-in blew the svg up to fill the row (HQ-441, Adam: "RMA serial icon
   is huge"). Same look as the page's .actgrid buttons, tokens only. */
.kv .ab{display:inline-flex;align-items:center;gap:7px;font-size:12px;color:var(--ink-dim);background:var(--bg);border:1px solid var(--line);border-radius:9px;padding:6px 10px;cursor:pointer;font-family:inherit}
.kv .ab:hover{color:var(--ink);border-color:var(--brass)}
.kv .ab svg{width:14px;height:14px;color:var(--brass-bright);flex:none}
.sch-month-th{display:grid;grid-template-columns:repeat(5,1fr);gap:6px;font-size:11px;font-weight:700;color:var(--ink-faint);text-transform:uppercase;letter-spacing:.04em;padding:0 2px}
.sch-month-row{display:grid;grid-template-columns:repeat(5,1fr);gap:6px}
.sch-month-cell{min-height:88px;border:1px solid var(--line);border-radius:var(--r-control);padding:6px 7px;display:flex;flex-direction:column;gap:3px;cursor:pointer;background:var(--bg-panel-solid);transition:border-color .12s,background .12s}
.sch-month-cell:hover{border-color:var(--brass);background:var(--hover-bg)}
.sch-month-cell.out{opacity:.45}
.sch-month-cell.today{border-color:var(--brass)}
.sch-month-daynum{display:flex;align-items:center;justify-content:space-between;font-size:12px;font-weight:700;color:var(--ink)}
.sch-month-cell.today .sch-month-daynum{color:var(--brass-bright)}
.sch-month-daynum .cnt{font-size:10px;font-weight:700;color:var(--ink-dim);background:var(--bg);border:1px solid var(--line);border-radius:var(--r-pill);padding:0 6px}
.sch-month-appt{font-size:10.5px;color:var(--ink-dim);display:flex;align-items:center;gap:4px;min-width:0}
.sch-month-appt .nm{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1}
.sch-month-time{color:var(--ink-faint);font-weight:600;flex:none}
.sch-month-more{font-size:10px;color:var(--ink-faint);font-weight:600}
@media(max-width:1180px){.sch-month-cell{min-height:64px}}
/* Ops Calendar month view (HQ-441 round 2, operations.html #sec-calendar) reuses
   every .sch-month-* class above as-is - the ops calendar's own week shape is
   Mon-Sun (unlike support.html's Mon-Fri business week: real entries land on
   weekends too), so this is the one genuinely new rule, a 7-column variant of
   the 5-column grid-template-columns already set on .sch-month-th/.sch-month-row. */
.sch-month-th.d7,.sch-month-row.d7{grid-template-columns:repeat(7,1fr)}

/* Honest empty-state note inside cards/panels (real-mode compliance and siblings).
   Token-based; --ink-faint flips with the light theme so it holds in both. */
.empty-note{font-size:12px;color:var(--ink-faint);line-height:1.5}

/* Positive-action button (Clock In). Mirrors .btn.danger's shape with the ok
   token; token-based so it holds in both themes. quasar-ui.css already reserves
   this variant in the readability exclusion at .btn:not(.primary):not(.success)... */
.btn.success{
  color:var(--good);
  border-color:color-mix(in srgb,var(--good) 42%,transparent);
  background:color-mix(in srgb,var(--good) 9%,transparent);
}
.btn.success:hover{ color:var(--good); border-color:var(--good); background:color-mix(in srgb,var(--good) 15%,transparent); }

/* ── Field Service Schedule day board (HQ key pending) ─────────────────────
   Technicians on the x-axis, hour rows 7 AM - 5 PM, support-style card-in-cell
   (cards stack and the row grows - the whole day fits one screen). Extends the
   page's existing table.cal idiom; .flow converts a .calev block from absolute
   time-positioning to in-cell flow. Token-based: correct in both themes. */
table.cal.tsb td{height:40px}
table.cal.tsb th[data-tech]{cursor:pointer}
table.cal.tsb th[data-tech]:hover{background:var(--hover-bg)}
table.cal.tsb th[data-tech].on .dh{color:var(--brass-bright)}
html[data-theme="light"] table.cal.tsb th[data-tech].on .dh{color:var(--brass)}
table.cal.tsb th .thw{display:inline-flex;align-items:center;gap:6px;justify-content:center;max-width:100%}
table.cal.tsb th .thw .dh{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
table.cal.tsb th .thn{font-size:10px;font-weight:700;color:var(--ink-dim);background:var(--bg);border:1px solid var(--line);border-radius:var(--r-pill);padding:1px 7px;flex:none}
table.cal td .calev.flow{position:static;left:auto;right:auto;width:auto;height:auto;margin:2px 3px 3px}
table.cal .calev .cdur{margin-left:auto;font-size:9.5px;font-weight:600;color:var(--ink-dim);font-variant-numeric:tabular-nums;white-space:nowrap}
table.cal.tsb td.now-hr{box-shadow:inset 0 2px 0 0 var(--status-red)}
table.cal.tsb td.timecol.now-hr{box-shadow:none;color:var(--status-red)}

/* ── Support dashboard: Customize (gear) popover + per-user card show/hide ──
   (support.html, Adam 2026-07-10). The popover rides the page's existing
   .sp-menu shell (anchored panel, closed by the shared outside-click handler);
   these classes add the checkbox rows, section headings and footnote. All
   token-built, so both themes (html[data-theme="light"] flips the tokens) work.
   .dash-hidden is a client display preference, never an access control. */
.dash-hidden{display:none!important}
/* a 2-up dashboard row collapses to one column when one half is hidden */
.chartrow.solo,.dashmid.solo{grid-template-columns:1fr}
.sp-menu.dashcfg{min-width:250px;max-width:320px;max-height:min(60vh,420px);overflow-y:auto;padding:7px}
.dashcfg .dc-h{font-size:10px;text-transform:uppercase;letter-spacing:.06em;color:var(--ink-faint);font-weight:700;padding:6px 8px 4px}
.dashcfg .dc-row{display:flex;align-items:center;gap:8px;border-radius:7px;font-size:12.5px;font-weight:600;color:var(--ink-dim);padding:7px 8px;cursor:pointer}
.dashcfg .dc-row:hover{background:var(--hover-bg-strong);color:var(--ink)}
.dashcfg .dc-row input[type=checkbox]{accent-color:var(--brass);margin:0;flex:none}
.dashcfg .dc-sub{margin-left:auto;font-size:10.5px;font-weight:500;color:var(--ink-faint)}
.dashcfg .dc-note{font-size:10.5px;line-height:1.45;color:var(--ink-faint);padding:7px 8px 3px;border-top:1px solid var(--line);margin-top:5px}

/* Labelled boolean toggle - shared, token-built (native checkbox, brass accent, both themes flip),
   so any page can set a per-record flag with one look (People WFH eligibility etc.). admin.html
   still carries an identical local copy; fold that into this shared rule when it is next revisited
   (HQ-312 review nit - two masters for .switch until then). */
.switch{display:flex;align-items:center;gap:9px;font-size:12.5px;color:var(--ink);margin:9px 0;cursor:pointer}
.switch input{width:auto;flex:none;margin:0;accent-color:var(--brass)}
.switch.disabled{opacity:.55;cursor:not-allowed}

/* People "Me" hub - phone-first single-column employee view (unit 5): clock, my jobs, my
   leave. Token-built so both themes flip; the only page that owns its own <style> loses
   nothing because these are shared. */
.pp-me{display:flex;flex-direction:column;gap:14px;max-width:520px;margin:0 auto}
.pp-me .pp-greet{font-size:18px;font-weight:700;color:var(--ink)}
.pp-card{background:var(--bg-panel-solid);border:1px solid var(--line);border-radius:var(--r-card);padding:16px}
.pp-card .pp-h{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--ink-faint);font-weight:700;margin-bottom:10px}
.pp-clock .pp-state{font-size:16px;font-weight:700;color:var(--ink);margin-bottom:6px}
.pp-clock .pp-state.on{color:var(--status-green)}
.pp-clock .pp-state.field{color:var(--status-blue)}
.pp-clock .pp-state.off{color:var(--ink-dim)}
.pp-hours{display:flex;gap:20px;color:var(--ink-dim);font-size:13px;margin-bottom:14px}
.pp-hours b{color:var(--ink);font-size:16px}
.pp-clockin{display:flex;flex-direction:column;gap:8px}
.pp-big{width:100%;justify-content:center;padding:12px;font-size:15px}
.pp-row{display:flex;align-items:center;gap:10px;padding:9px 0;border-top:1px solid var(--line)}
.pp-row:first-of-type{border-top:none}
.pp-row .pp-sub{font-size:12px;color:var(--ink-dim);margin-top:2px}
.pp-empty{color:var(--ink-faint);font-size:13px;padding:6px 0}

/* index.html Dashboard - Sales Pipeline mini-funnel (#dashboard .funnel .seg,
   real-mode only) per-segment colours. Matches the Today's Business Snapshot
   legend/chart on the same page exactly (Quotes/Orders/Paid = C.blue/C.green/
   C.violet there): --status-blue and --status-green are the same hex as
   C.blue (#5d9bd8) and C.green (#5bb98b), so those are reused; no token in
   this file matches C.violet's #967bdc exactly, so that one is a literal per
   the house rule (hex literals allowed here when no token matches).
   Selectors are scoped to #dashboard .funnel .seg (matching the page's own
   base rule's specificity+1) so they win over index.html's inline <style>
   .funnel .seg gradient regardless of link order - a plain .fseg-quotes
   class alone would lose to that ID-scoped rule. Applied by index.html
   alongside the existing .seg base class. */
#dashboard .funnel .seg.fseg-quotes{background:var(--status-blue)}
#dashboard .funnel .seg.fseg-orders{background:var(--status-green)}
#dashboard .funnel .seg.fseg-paid{background:#967bdc}
/* HQ-331: the home dashboard is a fixed overlay above the legacy territory globe; a Mac
   trackpad's elastic overscroll could chain past the overlay's scroller and flash the
   globe beneath. Contain the chain at the overlay. */
#dashboard{ overscroll-behavior: contain; }

/* HQ-331: the order editor's number row - the SO number is the thing the team reaches
   for most, so it leads the row large and bright; the status/lock/integration chips sit
   beside it in one gapped line instead of a dot-separated run-on. (.qnum-row rides on
   quote.html's .qnum base - this class must win, hence the extra specificity.) */
.qhead div.qnum-row{ display:flex; align-items:center; gap:9px; flex-wrap:wrap; }
.qhead .qnum-row .so-num{ font-family:var(--font-mono,monospace); font-size:18px; font-weight:800; letter-spacing:.01em; color:var(--ink); }

/* HQ-331: the Orders browser - every column has always been sortable, but only the
   active one showed an arrow, so the rest read as static labels. Idle headers carry a
   dim updown glyph; the wide text columns cap with an ellipsis so the table stops
   forcing a horizontal scroll (full value stays on the title tooltip). */
.ofs-tbl th.sort .car.idle{ opacity:.4; }
.ofs-tbl td.cust{ max-width:230px; overflow:hidden; text-overflow:ellipsis; }
.ofs-tbl td.loc{ max-width:130px; overflow:hidden; text-overflow:ellipsis; }

/* HQ-331: clickable sort headers (Ship Today; reusable on any .tbl). Every sortable
   header carries a dim idle updown glyph so it reads as clickable (round 11 - same
   affordance as the Orders browser); the active column swaps to a solid arrow and
   direction flips with the .desc class. */
.tbl th.sortable{cursor:pointer;user-select:none}
.tbl th.sortable:hover{color:var(--ink)}
.tbl th.sortable::after{content:' \2195';opacity:.4}
.tbl th.sortable.on::after{content:' \2191';opacity:1}
.tbl th.sortable.on.desc::after{content:' \2193'}

/* HQ-331 round 11: Ship Today row-level stock indicator - red = live back-order rows,
   amber = shortfall (confirmed cache or the indicative on-hand comparison; the binding
   check stays server-side at pick time, G-W1). */
.tbl .stockflag{display:inline-block;margin-right:6px;font-size:12px;cursor:help}
.tbl .stockflag.short{color:var(--status-amber)}
.tbl .stockflag.bo{color:var(--rust)}

/* HQ-331 round 13: the Sales pipeline peek-drawer footer. The drawer sits OUTSIDE
   sales.html's .sp-wrap so the page's brass/ghost button rules never reached it -
   the stacked footer buttons rendered flush as glass slabs (Adam's "two blue
   icons"). Spaced, brass primary + ghost secondary, same !important override the
   page rules use to beat the 32px glass .btn base. */
.deal-drawer .dd-foot{display:flex;flex-direction:column;gap:8px}
.deal-drawer .dd-foot .btn{background:var(--brass)!important;color:var(--brass-on-bg,#fff)!important;border:1px solid var(--brass)!important;border-radius:8px!important;padding:8px 14px!important;height:auto!important;font-weight:700!important;font-size:13px!important;display:inline-flex!important;align-items:center;justify-content:center;gap:6px;box-shadow:none!important;line-height:1.1!important;cursor:pointer}
.deal-drawer .dd-foot .btn:hover{filter:brightness(1.08)}
.deal-drawer .dd-foot .btn.ghost{background:transparent!important;border:1px solid var(--line)!important;color:var(--ink-dim)!important}
.deal-drawer .dd-foot .btn.ghost:hover{filter:none;color:var(--ink)!important;border-color:var(--line-strong,var(--line))!important}

/* HQ-331 round 14: breathing room between the order editor's Field service job-type
   select and the Assign button (Adam: "needs a slight gap"). */
#field-service-card .fld+.fld{margin-top:10px}

/* HQ-334: proof-of-pack photo strip on the shipping order (thumbnails + Add photo). */
.pack-photos{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:10px;padding-top:10px;border-top:1px solid var(--line)}
.pack-photos .pp-label{font-size:10.5px;letter-spacing:.05em;text-transform:uppercase;color:var(--ink-faint);font-weight:700}
.pack-photos .pp-thumb{display:inline-block;width:52px;height:52px;border-radius:8px;overflow:hidden;border:1px solid var(--line)}
.pack-photos .pp-thumb img{width:100%;height:100%;object-fit:cover;display:block}
.pack-photos .pp-thumb:hover{border-color:var(--brass)}

/* HQ-331 overnight: the People onboarding panel (staff drawer) - checklist rows +
   linked-document rows. */
.ob-check{display:flex;align-items:center;gap:8px;font-size:12.5px;color:var(--ink);margin:5px 0;cursor:pointer}
.ob-check input{width:auto;accent-color:var(--brass)}
.ob-doc{display:flex;align-items:center;gap:8px;padding:6px 0;border-bottom:1px solid var(--line);font-size:12.5px}
.ob-doc:last-child{border-bottom:none}
.ob-doc a{color:var(--brass-bright,var(--brass));text-decoration:none;font-weight:600;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ob-doc .ob-x{background:none;border:none;color:var(--ink-faint);cursor:pointer;font-size:14px;padding:2px 6px;border-radius:5px}
.ob-doc .ob-x:hover{color:var(--rust);background:var(--hover-bg)}

/* HQ-331 round 14: the Inventory items table scrolls INSIDE its card (sticky thead
   already pins) instead of growing the page - Adam: "we just scroll down within the
   box based on the screen size". */
#items-wrap{max-height:calc(100vh - 340px);min-height:280px}

/* HQ-331 round 14: the shipping pick-and-scan screen (Adam's rework list).
   1. The Stock shortfall card is SOLID (var(--bg-panel) is 82%-alpha glass and the
      card carries no backdrop blur, so the page bled through), with a bright amber
      banner that cannot be missed.
   2. The QLD / China warehouse tabs: quasar-ui's global glass !important on .chip
      beat the page's .active brass fill while the white text survived - white on
      near-white. The active fill is reasserted here at equal !important, later in
      the cascade, so it wins.
   3. Scan-armed state: #btn-scan.active had NO rule anywhere - only the glyph
      changed. Armed is now an unmissable amber block, and scanned lines keep a
      green bar until the order re-renders. */
#shortfall-modal .mcard{background:var(--bg-panel-solid)}
#shortfall-modal .sf-lownote{display:block;background:var(--status-amber);color:#1c1607;font-weight:800;font-size:13px;padding:9px 12px;border-radius:8px;margin:0 0 10px}
.qp-filters .chip.active{background:var(--brass)!important;border-color:var(--brass)!important;color:var(--brass-on-bg,#fff)!important}
#btn-scan.active{background:var(--status-amber)!important;border-color:var(--status-amber)!important;color:#1c1607!important;box-shadow:0 0 0 3px color-mix(in srgb,var(--status-amber) 30%,transparent)!important;font-weight:800!important}
.detail-panel tr.scanned td{box-shadow:inset 3px 0 0 var(--good);background:color-mix(in srgb,var(--good) 8%,transparent)}

/* HQ-331 round 14 + HQ-360 round 33: Ready-to-Ship fits without a horizontal scroll
   at desktop widths - the wide free-text columns clamp with an ellipsis (full value
   stays on the title tooltip), padding tightens to 5px and multi-word headers may
   wrap (Age / Priority was the widest fixed cell). */
.shiptbl th,.shiptbl td{padding-left:5px;padding-right:5px}
.shiptbl th{white-space:normal}
.shiptbl td.cust{max-width:150px;overflow:hidden;text-overflow:ellipsis}
.shiptbl td.items{max-width:170px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* HQ-420: the Status cell outgrew the HQ-331/360 width budget once the pay chip
   (HQ-394) + credit chip (HQ-400) stacked beside the status pill, bringing the
   card's inner horizontal scrollbar back. td.st lets the cell WRAP (overrides the
   page's table.tbl td nowrap via specificity, the same mechanism td.cust uses) so
   the pill + chips break onto a second line on tight widths instead of forcing the
   table wider; each pill/chip stays a whole one-line unit (.paychip carries its own
   nowrap, .pill gets it here). The raw-carrier fallback badge caps at 110px with an
   ellipsis (known carrier labels are shorter; the full name stays on the title
   tooltip). Structural only - no colours, fonts or tokens. */
.shiptbl td.st{white-space:normal}
.shiptbl td.st .pill{white-space:nowrap}
.shiptbl td .carrier{display:inline-block;max-width:110px;overflow:hidden;text-overflow:ellipsis;vertical-align:middle}

/* HQ-360 round 33: pack-scan line states + recorded-serial chips (shipping.html).
   Green (.scanned above) now means FULLY scanned; the partial state reads amber. */
.detail-panel tr.pk-line-part td{box-shadow:inset 3px 0 0 var(--status-amber);background:color-mix(in srgb,var(--status-amber) 7%,transparent)}
.pk-serials{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px}
.pk-chip{font-family:var(--font-mono);font-size:10.5px;padding:1px 6px;border:1px solid var(--line);border-radius:6px;color:var(--ink-dim)}
.pk-chip.err{border-color:var(--rust);color:var(--rust)}

/* HQ-331 round 17: the round-13 dashboard profile-editor rules (sa-editbtn/sa-editform)
   are retired with the pencil - name/role editing lives in People > Staff now. */
#dashboard .signed-as{flex-wrap:wrap}

/* HQ-331: the stage rows under the funnel carry the SAME stage colour as their segment,
   so the funnel colours are named by the words below them. */
#dashboard .pipe-row .pipe-dot{width:9px;height:9px;border-radius:999px;display:inline-block;margin-right:7px;flex:none;vertical-align:middle}
#dashboard .pipe-row .pipe-dot.fseg-quotes{background:var(--status-blue)}
#dashboard .pipe-row .pipe-dot.fseg-orders{background:var(--status-green)}
#dashboard .pipe-row .pipe-dot.fseg-paid{background:#967bdc}

/* ── HQ-400: credit-standing advisory (shipping.html + warehouse.html) ──────
   ADVISORY only - the credit limit is a RELEASE-time gate and shipGate does not
   re-check it, so none of this ever blocks anything. Moved here from the page
   <style> blocks per the UI-drift ratchet (one look, one file). */
/* shipping.html: amber "check first" banner for credit-account orders,
   deliberately distinct from the rust hard .hold-banner. Within limit = the
   quiet .credit-line instead. */
.credit-banner{border-radius:10px;padding:12px 16px;margin-bottom:14px;font-size:13px;line-height:1.5;background:rgba(228,188,108,.12);border:1px solid rgba(228,188,108,.55)}
.credit-banner .cb-title{font-weight:800;margin-bottom:2px;color:var(--status-amber);display:flex;align-items:center;gap:7px}
.credit-banner .cb-reason{color:var(--ink)}
.credit-banner .cb-figures{margin-top:4px;font-family:var(--font-mono);font-size:12px;color:var(--ink-dim)}
.credit-line{font-size:12px;color:var(--ink-dim);margin:0 0 14px;padding:0 2px}
/* warehouse.html: credit-account marker on a Ship Today row - blue =
   informational (the .pill.at_port tone), NOT a payment state. Drawn only from
   the already-loaded contact row (contactMap); advisory, never a gate. */
.paychip.credit{background:rgba(93,155,216,.16);color:var(--status-blue)}

/* ── HQ-405: dispatch okay (shipping.html) ──────────────────────────────────
   The audited approval that clears a ship-time payment stop (UNPAID or the new
   OVER_LIMIT hard stop). When quotes.data.dispatch_okay is present the rust
   stop banners render as this quiet green line instead - who, when, note,
   reason. The Approve/Revoke buttons are affordances only; the server
   (orders-ship-override okayError/revokeError) is the gate. */
.okay-line{display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-size:12.5px;color:var(--good,#4ade80);margin:0 0 14px;padding:9px 12px;background:rgba(74,222,128,.08);border:1px solid rgba(74,222,128,.3);border-radius:10px}
.okay-line svg{flex:none}
.okay-line .ok-note{color:var(--ink-dim);font-style:italic}
.okay-line .okay-revoke{font-size:11px;padding:3px 9px;margin-left:auto}
.hold-banner .hb-figures{margin-top:4px;font-family:var(--font-mono);font-size:12px;color:var(--ink-dim)}
.hold-banner .hb-approve{margin-top:8px}
.hold-banner .hb-approve .btn{font-size:12px;padding:5px 12px}
#okay-modal #ok-close{padding:4px 10px} /* the page's mhead close-button idiom, as a rule not an inline style */
#okay-modal #ok-sub{font-size:13px;color:var(--ink-dim);margin:0 0 10px}
#okay-modal #ok-label{display:block;font-size:12px;color:var(--ink-faint);margin:0 0 4px}
#okay-modal textarea{width:100%;box-sizing:border-box;background:var(--bg);border:1px solid var(--line);border-radius:8px;padding:9px 11px;color:var(--ink);font-family:inherit;font-size:13px;resize:vertical}
#okay-modal textarea:focus{outline:none;border-color:var(--brass)}
#okay-modal #ok-err{color:var(--rust);font-size:12px;margin-top:8px;min-height:14px}

/* ── HQ-401: B2B account review (quote.html Contacts view) ──────────────────
   The card, worklist table, review-state chips and the shared note/reason
   modal. quote.html loads this file AFTER its own <style>, so these same- or
   higher-specificity rules win their ties there. The admin-only confirm /
   unconfirm gate is SERVER-side (b2b-review-act); nothing here is security. */
#b2br-card{margin:0 0 14px;display:none}
#b2br-card .b2br-head{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:0}
#b2br-card .b2br-hint{font-weight:400;font-size:12px}
#b2br-card .b2br-spacer{flex:1}
#b2br-progress{font-size:12px;font-weight:600}
#b2br-body{display:none}
.b2br-pad{padding:8px}
/* review-state chips ride quote.html's .tierpill shape; state colours here.
   Unreviewed grey / reviewed amber / confirmed green (--good). */
.tierpill.b2br-chip-un{background:rgba(139,147,161,.14);color:var(--ink-dim);border:1px solid rgba(139,147,161,.35)}
.tierpill.b2br-chip-ok{background:rgba(74,222,128,.12);color:var(--good);border:1px solid rgba(74,222,128,.4)}
.tierpill.b2br-chip-rev{background:rgba(224,162,62,.14);color:var(--status-amber);border:1px solid rgba(224,162,62,.4)}
.b2br-td-acct{max-width:280px}
.b2br-td-acct a{color:inherit}
.b2br-who{font-size:10.5px;color:var(--ink-dim)}
.b2br-td-actions{white-space:nowrap;text-align:right}
.b2br-over{color:var(--rust)}
/* the shared note/reason modal */
#b2br-modal .panel{width:440px}
#b2br-modal .phead strong{flex:1}
#b2br-modal .pbody{padding:18px}
#b2br-m-sub{font-size:12px;margin-bottom:10px}
#b2br-m-err{color:var(--rust);font-size:12px;margin-top:6px}
.b2br-m-foot{display:flex;gap:8px;justify-content:flex-end;margin-top:14px}

/* inventory.html (body.inv-page) - Movements pinned header (Adam night batch
   2026-07-20 item 1: "the top should stay current when I scroll"). The
   conversations round-3 bounded-column idiom (.case-list rail, above): while the
   Movements section is on, the page column (#app) is bounded to the viewport and
   becomes a flex column, so the title row and the filter controls stay put and
   ONLY the movements table scrolls (its page-scoped sticky thead keeps the column
   headers visible inside the scroller). Lives here, not in the page <style> block
   (the page sits at its ui-drift baseline - same note as the .inv-page R25 chrome
   above). Scoped to the nav shell, where the body is the page's scroller; without
   the shell the page keeps its natural scroll. #sec-movementpage (the movement
   full page) is a different section, so detail views keep the normal page scroll.
   align-items:stretch overrides the base .detail-cols align-items:start, which
   would otherwise shrink-wrap the card's width under a flex column; gap:0 drops
   the phantom 16px before the retired, always-empty #mv-detail rail. Existing
   tokens only. */
html.qnav-shell body.inv-page:has(#sec-movements.sec.on) #app{
  display:flex; flex-direction:column; box-sizing:border-box;
  height:calc(100vh - var(--qtop-h)); overflow:hidden;
}
/* impersonation banner shifts the shell down 40px (html.qimp-on rules above) */
html.qnav-shell.qimp-on body.inv-page:has(#sec-movements.sec.on) #app{
  height:calc(100vh - var(--qtop-h) - 40px);
}
.inv-page:has(#sec-movements.sec.on) .inv-head{ flex:none; }
.inv-page #sec-movements.sec.on{ display:flex; flex-direction:column; flex:1 1 auto; min-height:0; }
.inv-page #sec-movements.sec.on .controls{ flex:none; }
.inv-page #sec-movements.sec.on .detail-cols{ display:flex; flex-direction:column; align-items:stretch; gap:0; flex:0 1 auto; min-height:0; }
.inv-page #sec-movements.sec.on .detail-cols>.card{ display:flex; flex-direction:column; flex:0 1 auto; min-height:0; }
.inv-page #sec-movements.sec.on .card-head{ flex:none; }
.inv-page #sec-movements.sec.on .tblwrap{ flex:1 1 auto; min-height:0; overflow-y:auto; }
.inv-page #sec-movements.sec.on .notebar{ flex:none; }
/* phones/tablets keep the natural page scroll - 900px is the shell's own
   sidebar-collapse breakpoint; bounded inner scrollers fight collapsing mobile
   browser chrome. */
@media (max-width:900px){
  html.qnav-shell body.inv-page:has(#sec-movements.sec.on) #app{ display:block; height:auto; overflow:visible; }
  .inv-page #sec-movements.sec.on{ display:block; }
  .inv-page #sec-movements.sec.on .detail-cols{ display:grid; }
}

/* Costing (HQ-520, Adam: "still has a horizontal scroll... also the whole page
   scrolls") never got the Movements/Customers/Ship treatment above, so it ran
   both problems those already fixed: the page itself scrolling AND its own wide
   10-column table forcing a second, horizontal scrollbar (the uncapped Product
   name column, same root cause as HQ-518 on Customers). Same two-part fix: pin
   the section to one internal scroll region, and cap/ellipsize the name so the
   table fits without its own scrollbar (a title-free ellipsis is fine here - the
   SKU sub-line under the name already gives the exact identifier). Costing has
   two stacked cards (the costing table, then Component drift), so - unlike
   Movements' single table - the WHOLE section scrolls as one region rather than
   pinning just one inner .tblwrap; both cards keep their natural height. */
html.qnav-shell body.inv-page:has(#sec-costing.sec.on) #app{
  display:flex; flex-direction:column; box-sizing:border-box;
  height:calc(100vh - var(--qtop-h)); overflow:hidden;
}
html.qnav-shell.qimp-on body.inv-page:has(#sec-costing.sec.on) #app{
  height:calc(100vh - var(--qtop-h) - 40px);
}
.inv-page:has(#sec-costing.sec.on) .inv-head{ flex:none; }
.inv-page #sec-costing.sec.on{ display:flex; flex-direction:column; flex:1 1 auto; min-height:0; overflow-y:auto; }
.inv-page #sec-costing.sec.on > .kpis{ flex:none; }
#sec-costing .tblwrap{ overflow-x:hidden; }
#sec-costing .tbl td:first-child b{ display:inline-block; max-width:260px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; vertical-align:bottom; }
@media (max-width:900px){
  html.qnav-shell body.inv-page:has(#sec-costing.sec.on) #app{ display:block; height:auto; overflow:visible; }
  .inv-page #sec-costing.sec.on{ display:block; overflow:visible; }
}

/* ── Warehouse Map (web/warehouse-map.html) - token-only, no new colours/fonts/tokens ── */
.whm-wrap{padding:12px 22px 24px;max-width:1920px;margin:0 auto}
.whm-head{display:flex;align-items:flex-start;gap:12px;margin-bottom:14px;flex-wrap:wrap}
.whm-head .titlewrap{display:flex;flex-direction:column;gap:2px;min-width:0}
.whm-head h1{font-size:23px;font-weight:800;margin:0;color:var(--ink);letter-spacing:-.02em;line-height:1.12}
.whm-head .sub{font-size:12.5px;color:var(--ink-dim);line-height:1.2}
.whm-head .spacer{flex:1}
.whm-head .actions{display:flex;align-items:center;gap:8px;flex:none}
.whm-controls{display:flex;align-items:center;gap:8px;margin-bottom:10px;flex-wrap:wrap}
.whm-controls select,.whm-controls input{background:var(--bg);border:1px solid var(--line);border-radius:var(--r-control);padding:7px 10px;font-size:12.5px;color:var(--ink);font-family:inherit}
.whm-controls input{min-width:260px}
.whm-controls select:focus,.whm-controls input:focus{outline:none;border-color:var(--brass)}
.whm-legend{display:flex;gap:14px;align-items:center;font-size:11.5px;color:var(--ink-dim);margin:0 0 12px;flex-wrap:wrap}
.whm-legend span{display:inline-flex;align-items:center;gap:5px}
.whm-zones{display:grid;grid-template-columns:repeat(auto-fill,minmax(340px,1fr));gap:14px;align-items:start}
.whm-zone{background:var(--bg-panel-solid);border:1px solid var(--line);border-radius:var(--r-card);overflow:hidden}
.whm-zone-h{display:flex;align-items:baseline;gap:8px;padding:9px 14px;border-bottom:1px solid var(--line)}
.whm-zone-h h3{margin:0;font-size:14px;font-weight:700;color:var(--ink)}
.whm-zone-h .zsub{font-size:11px;color:var(--ink-faint);margin-left:auto;white-space:nowrap}
.whm-zone-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(118px,1fr));gap:8px;padding:12px 14px}
.whm-bin{background:var(--bg);border:1px solid var(--line);border-radius:var(--r-control);padding:8px 10px;cursor:pointer;min-width:0;transition:border-color .12s,opacity .12s}
.whm-bin:hover{border-color:var(--brass)}
.whm-bin.sel{border-color:var(--brass);box-shadow:inset 0 0 0 1px var(--brass)}
.whm-bin.hit{border-color:var(--brass)}
.whm-bin.dim{opacity:.35}
.whm-bin .code{display:block;font-family:var(--font-mono);font-size:12px;font-weight:700;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.whm-bin .meta{display:block;font-size:10.5px;color:var(--ink-dim);margin-top:2px;line-height:1.3}
.whm-bin .top{display:block;font-size:10.5px;color:var(--ink-faint);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.whm-dots{display:flex;gap:4px;margin-top:6px}
.whm-dot{display:inline-block;width:7px;height:7px;border-radius:var(--r-pill);flex:none}
.whm-dot.green{background:var(--status-green)}
.whm-dot.yellow{background:var(--status-amber)}
.whm-dot.blue{background:var(--status-blue)}
.whm-dot.red{background:var(--status-red)}
.whm-detail{background:var(--bg-panel-solid);border:1px solid var(--line);border-radius:var(--r-card);margin-top:14px;overflow:hidden}
.whm-detail-h{display:flex;align-items:center;gap:8px;padding:9px 14px;border-bottom:1px solid var(--line)}
.whm-detail-h h3{margin:0;font-size:14px;font-weight:700;color:var(--ink);font-family:var(--font-mono)}
.whm-detail-h .spacer{flex:1}
.whm-rows{padding:6px 14px 10px}
.whm-row{display:flex;align-items:center;gap:10px;padding:7px 0;border-bottom:1px dashed var(--line);font-size:12.5px;color:var(--ink-dim)}
.whm-row:last-child{border-bottom:none}
.whm-row .nm{flex:1;min-width:0;color:var(--ink);font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.whm-row .sku{font-family:var(--font-mono);font-size:11px;color:var(--ink-faint)}
.whm-row .qty{font-variant-numeric:tabular-nums;font-weight:700;color:var(--ink)}
.whm-state{display:inline-flex;align-items:center;gap:5px;font-size:10.5px;font-weight:700;border-radius:var(--r-pill);padding:2px 9px;border:1px solid var(--line);color:var(--ink-dim)}
.whm-state .whm-dot{width:6px;height:6px}
.whm-empty{padding:40px;text-align:center;color:var(--ink-faint);font-size:12.5px;line-height:1.6}
.whm-note{display:flex;gap:10px;align-items:flex-start;font-size:12px;color:var(--ink-dim);background:var(--bg-panel-solid);border:1px solid var(--line);border-radius:var(--r-card);padding:10px 12px;margin-top:14px;line-height:1.5}
.whm-signin{padding:40px;text-align:center;color:var(--ink-dim)}

/* ===== HQ-415 dropdown/layout polish (docks + orders + globe) ===== */
.qai-panel.qai-dock{
  top:auto; left:auto; bottom:auto;
  width:min(440px, calc(100vw - 24px));
  height:min(680px, calc(100vh - var(--qtop-h) - 24px));
  border:1px solid var(--line);
  border-radius:14px;
  background:var(--bg-panel-solid);
  box-shadow:0 22px 64px rgba(0,0,0,.55);
  transform:translateY(-8px); opacity:0; pointer-events:none;
  transition:opacity .18s ease, transform .18s ease;
}
.qai-panel.qai-dock.open{ transform:none; opacity:1; pointer-events:auto; }
.qai-scrim.qai-dock{ background:transparent; }

/* HQ-415: Ask Quasar docked as a topbar dropdown. quasar-nav.js adds .qai-dock
   to quasar-assistant.js's .qai-panel/.qai-scrim (that file stays untouched)
   and sets top/right inline (the buildCommsPanel.position idiom). Chrome
   mirrors the .qchat comms dock (quasar-ui.css line 954). Specificity beats
   the assistant's injected .qai-panel (0,1,0) and .qai-panel.open (0,2,0)
   rules without !important because .qai-panel.qai-dock and
   .qai-panel.qai-dock.open both have higher specificity (0,2,0 and 0,3,0
   respectively) than the rules they need to override. Place near the .qchat
   block (quasar-ui.css line 954-959). Confirmed all tokens used (--line,
   --bg-panel-solid, --qtop-h) already exist - no new token/colour/font. This
   is required before/with the quasar-nav.js dockAskQuasar() change - without
   it the panel opens as the old full-height right-edge slide-over instead of
   a dropdown under the sparkle button.*/

/* HQ-415: quote.html Orders work pane adopts the bounded fixed-header pattern
   (the page's own #view-contacts idiom = the sales.html Customers layout):
   the persistent Orders header pins, ONLY the editor scrolls, and the inner
   1400px cap lifts to the house 1920px so no dead page-ground bands show.
   Desktop only - phones keep the natural scroll (the shell's 900px rule). */
#work-inner{ max-width:1920px; }
@media (min-width:901px){
  html.qnav-shell .work:has(> #work-inner){
    display:flex; flex-direction:column; min-height:0;
    overflow:hidden; padding:16px 22px 0;
  }
  html.qnav-shell #work-inner{
    flex:1 1 auto; min-height:0; width:100%;
    display:flex; flex-direction:column;
  }
  html.qnav-shell #work-inner > #orders-head{ flex:none; }
  html.qnav-shell #work-inner > #empty-state{ flex:none; }
  html.qnav-shell #work-inner > #editor{
    flex:1 1 auto; min-height:0; overflow-y:auto; padding-bottom:24px;
  }
}

/* HQ-425: hide the Orders landing strip while a quote is open - the editor's own
   .qhead (Back to Orders + action strip) is the header in that state. The list
   surfaces (landing hint, pipeline board) keep the HQ-415 pinned strip. Unscoped
   from the 901px media query so phones also lose the stacked double header. */
#work-inner.q-editing > #orders-head{ display:none; }

/* HQ-426: with a quote open the editor scrolls as ONE normal page - its own .qhead
   scrolls away with the content. Undo the HQ-415 bounded fixed-header layout for
   #work-inner.q-editing ONLY, so .work resumes its base whole-pane scroll and
   #work-inner/#editor are normal block flow. No .q-editing == the Orders LIST,
   which keeps HQ-415 byte-for-byte. Desktop only (matches HQ-415 901px scope);
   phones already scroll naturally. Tokens untouched. */
@media (min-width:901px){
  html.qnav-shell .work:has(> #work-inner.q-editing){ display:block; overflow:auto; }
  html.qnav-shell #work-inner.q-editing{ display:block; }
  html.qnav-shell #work-inner.q-editing > #editor{ flex:none; min-height:0; overflow-y:visible; }
}

/* HQ-426: Gold Support member marker on sales.html Customers (read-only; Manage
   stays on gold-support.html). --status-amber only - no new token/colour/font. */
.gs-star{ display:inline-flex; align-items:center; vertical-align:middle; margin-left:6px; color:var(--status-amber); flex:none; }
.gs-star svg{ width:14px; height:14px; }

/* HQ-415: Sales > Globe - pin the 'Accounts by Area' header (title, search,
   All-regions filter) and scroll ONLY the account rows: the conversations
   .case-list bounded-rail idiom. sales.html loads this file BEFORE its own
   <style>, so the #sec-globe id scope is what wins over the page's .gf-list
   rule; the page's max-height (620px desktop / 280px phone) keeps being the
   outer bound. Layout only, tokens untouched; #sec-customers unaffected. */
#sec-globe .gf-list{ display:flex; flex-direction:column; overflow:hidden; }
#sec-globe .gf-list .glh,
#sec-globe .gf-list .gf-searchbar,
#sec-globe .gf-list .gf-filter{ flex:none; }
#sec-globe #gf-rows{ flex:1 1 auto; min-height:0; overflow-y:auto; }
/* single-column stack (the page's 1100px rule drops max-height): restore the
   page's own whole-box scroll behaviour so the 700px 280px cap keeps working */
@media (max-width:1100px){
  #sec-globe .gf-list{ display:block; overflow-y:auto; }
  #sec-globe #gf-rows{ min-height:0; overflow:visible; }
}

/* HQ-427 / HQ-441 round 2b: quote.html Orders LIST view (the full-screen order
   browser #orders-fs) keeps the Customers box idiom - head + KPI strip pinned
   (flex:none), the .card fills the remaining column height, and #ofs-grid is
   the ONE scroll box with the sticky .ofs-tbl th pinned to its top (Adam,
   2026-07-23: "have the box locked but just have the order scroll through
   cleanly, like the customer box"). A brief round-2 page-scroll conversion of
   this pane was reverted the same day. Does not touch #work-inner/#editor/
   .q-editing (HQ-415/425/426) - a different pane. */
#orders-fs .ofs-head{ flex:none; }
#orders-fs .ofs-kpis{ flex:none; }
#orders-fs .ofs-grid{ min-height:0; }
/* HQ-420-idiom column clamp: Customer and Location are free-text columns with no width cap, so a long company name or "City, Country" pushes .ofs-tbl wider than the card and puts a horizontal scrollbar under the table - the same bug .shiptbl td.cust/.items already fixed on shipping.html. Ellipsis only; the full value stays on the row's existing title tooltip (renderOfsRows() already sets title= on both cells). */
#orders-fs .ofs-tbl td.cust{ max-width:200px; overflow:hidden; text-overflow:ellipsis; }
#orders-fs .ofs-tbl td.loc{ max-width:140px; overflow:hidden; text-overflow:ellipsis; }
/* A Xero-driven row can show two status pills plus a field-service badge in the Status cell; quote.html's own td.st{white-space:nowrap} was flipped to `normal` in this change so that cell wraps onto a second line instead of forcing the whole table wider. This keeps each individual pill/chip a whole, unbroken unit while the cell wraps (mirrors .shiptbl td.st .pill on shipping.html). */
#orders-fs .ofs-tbl td.st .pill{ white-space:nowrap; }

/* HQ-434: quote.html Orders - the pinned column-header bar reads as a
   detached black strip (Adam, 2026-07-23 preview review) because quote.html's
   own <style> gives .ofs-tbl th (and its per-column filter-row twin,
   tr.ofs-filter th) background:var(--bg) - the page ground colour - while the
   card it sits on (.ofs-grid's parent .card, quote.html's own <style> ~line
   394) is background:var(--bg-panel-solid). Those two tokens render close but
   visibly different, so the sticky header band looks like a separate bar
   glued on top of the card instead of one clean pinned bar - unlike
   sales.html Customers and warehouse.html Ship Today, whose sticky
   table.tbl th (this file's HQ-427 block above / HQ-434 Ship Today block
   below) already sits on var(--bg-panel-solid), the Customers idiom. Fixing
   ONLY the background token here (an ID selector beats quote.html's own
   .ofs-tbl th on specificity, so no page edit needed) matches that idiom
   exactly - same token, same border-bottom:1px solid var(--line), same
   uppercase/weight/size typography already shared by all three tables - so
   only the data rows read as moving under one clean bar. No handler, sort
   binding, id or column touched; #ofs-filter-toggle's th.sort click handlers
   are untouched. Existing token only, no new colour/font/token. */
#orders-fs .ofs-tbl th{ background:var(--bg-panel-solid); }
#orders-fs .ofs-tbl tr.ofs-filter th{ background:var(--bg-panel-solid); }

/* Orders stat tiles (HQ-520, Adam: "make the orders bar look like the inventory
   items bar, it flows nicer"). Moved out of quote.html's own <style> block (that
   page is over its ui-drift budget for new inline CSS) and restyled with the
   Items KPI icon-badge layout (renderKpis()'s .khead/.kic pattern) instead of a
   flat label+value button - same tokens/classes, a per-status tint reusing the
   shared --status-* tokens (the same ones .crm-page .kpi.green/.amber/.bad etc
   already use elsewhere), not a new colour. */
.ofs-kpis{ display:flex; gap:10px; padding:12px 22px; border-bottom:1px solid var(--line); background:var(--bg-panel); overflow-x:auto; }
.okpi{ flex:1 1 0; min-width:130px; background:var(--bg-panel-solid); border:1px solid var(--line); border-radius:12px; padding:9px 13px; cursor:pointer; text-align:left; font-family:inherit; transition:border-color .13s,background .13s; }
.okpi:hover{ border-color:var(--brass); }
.okpi .khead{ display:flex; align-items:center; gap:10px; }
.okpi .kic{ width:38px; height:38px; flex:none; display:grid; place-items:center; border-radius:999px; background:color-mix(in srgb,var(--status-blue) 14%,transparent); color:var(--status-blue); }
.okpi .kic svg{ width:19px; height:19px; }
.okpi.q-quote .kic{ background:color-mix(in srgb,var(--ink-dim) 16%,transparent); color:var(--ink-dim); }
.okpi.q-proposal .kic{ background:color-mix(in srgb,var(--status-amber) 16%,transparent); color:var(--status-amber); }
.okpi.q-paid .kic{ background:color-mix(in srgb,var(--status-green) 16%,transparent); color:var(--status-green); }
.okpi.q-shipped .kic{ background:color-mix(in srgb,var(--status-blue) 14%,transparent); color:var(--status-blue); }
.okpi.q-cancelled .kic{ background:color-mix(in srgb,var(--status-red) 16%,transparent); color:var(--status-red); }
.okpi .ktext{ min-width:0; }
.okpi .ok-lbl{ display:block; font-size:10.5px; font-weight:700; color:var(--ink-dim); letter-spacing:.05em; text-transform:uppercase; white-space:nowrap; }
.okpi .ok-val{ display:block; font-size:19px; font-weight:750; color:var(--ink); line-height:1.15; margin-top:1px; font-variant-numeric:tabular-nums; }
.okpi.on{ border-color:var(--brass); background:color-mix(in srgb,var(--brass) 10%,transparent); }
.okpi.on .ok-lbl{ color:var(--brass-bright,var(--brass)); }
/* Canonical graphy-dot sparkline on each status tile (spark(), components.html). */
.okpi svg.spark{ display:block; width:100%; height:26px; margin-top:6px; }

/* HQ-427: sales.html Customers - bounded scroll (persistent header/filter, only rows
   scroll). Reuses the inventory.html Movements #app-bounded flex-column idiom
   (quasar-ui.css ~line 2913, itself borrowed from the conversations bounded-column
   pattern) and sits alongside the Globe section's own HQ-415 rail (~line 3077).
   While the Customers section is on, #app becomes a flex column bounded to the
   viewport, so the page header (.sp-head), the card header (Accounts/People toggle
   + count), and the filter row (search, package/type/stage selects, the HQ-426 Gold
   Support chip, Clear) all stay fixed - only the grouped account/people rows
   (Large/Medium/Small/New, HQ-331) scroll, with the page's own sticky thead
   (table.tbl th, sales.html <style>) keeping the column headers visible inside the
   scroller. id="sec-customers" and id="cust-filter" are unique to sales.html, so no
   body-class scope is needed the way .inv-page scopes the equivalent Movements rule.
   #app carries an inline style="display:block" written by the page's own auth-gate
   JS ($('app').style.display='block'); !important on display matches the existing
   #app:has(.topbar) overrides above, which fight the same inline style for the same
   reason (inventory.html's #app clears its inline style to '' instead, so its
   equivalent rule needs no !important - sales.html's does). Desktop only (matches
   the inv-page Movements 900px scope); phones keep the natural whole-page scroll.
   Existing tokens/classes only - no new colour, font or token. */
html.qnav-shell body:has(#sec-customers.sec.on) #app{
  display:flex !important; flex-direction:column; box-sizing:border-box;
  height:calc(100vh - var(--qtop-h)); overflow:hidden;
}
html.qnav-shell.qimp-on body:has(#sec-customers.sec.on) #app{
  height:calc(100vh - var(--qtop-h) - 40px);
}
body:has(#sec-customers.sec.on) .sp-head{ flex:none; }
#sec-customers.sec.on{ display:flex; flex-direction:column; flex:1 1 auto; min-height:0; }
#sec-customers.sec.on > .card{ display:flex; flex-direction:column; flex:1 1 auto; min-height:0; }
#sec-customers.sec.on .card-head{ flex:none; }
#sec-customers.sec.on #cust-filter{ flex:none; }
#sec-customers.sec.on .tblwrap{ flex:1 1 auto; min-height:0; overflow-y:auto; overflow-x:hidden; }
/* HQ-518 (Adam: "i believe its too wide hence why the horztional bar is there"): this
   page's own table.tbl td is nowrap with no cap on any cell, so a long person/account
   name pushed the table wider than its wrap and forced a horizontal scrollbar under the
   header - worse on the People tab, which shows a person's name AND the account name as
   two separate uncapped columns instead of one. Capped and ellipsised (a title attribute
   keeps the full value one hover away) instead of just hiding overflow-x above, which
   would have silently clipped a genuinely long name rather than fix why it got wide. */
#sec-customers .who-chip .strong, #sec-customers .cust-acct-name{ display:inline-block; max-width:240px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; vertical-align:middle; }
#sec-customers.sec.on .tblfoot{ flex:none; }
@media (max-width:900px){
  html.qnav-shell body:has(#sec-customers.sec.on) #app{ display:block !important; height:auto; overflow:visible; }
  #sec-customers.sec.on{ display:block !important; }
}

/* HQ-434: the standard queue search (docs/design/patterns.md "The standard
   queue search", Adam 2026-07-23) - one prominent search at the top of a
   queue page, with an always-visible brass-tinted border instead of the
   plain --line border most inputs carry at rest (the quote.html Orders
   #ofs-search idiom). Shared class so any queue page's search input can opt
   in without a page-scoped override; !important on the load-bearing
   border/shadow because a host page already owns its own input border-color
   (e.g. warehouse.html's own <style> block ".controls input.search") that
   would otherwise win on selector specificity. Percentages match values
   already used elsewhere in this file for the same var(--brass) mix - 40%
   matches .qchat-anchorchip's border, 18% matches .qf-input:focus's shadow -
   so no new colour/font/token. */
input.q-search-hero{
  border-color:color-mix(in srgb, var(--brass) 40%, transparent) !important;
}
input.q-search-hero:focus{
  outline:none;
  border-color:var(--brass) !important;
  box-shadow:0 0 0 3px color-mix(in srgb, var(--brass) 18%, transparent);
}

/* Styled filter dropdown (HQ-520 round 3, Adam: "in that drop down list i want
   a nicer drop down like on the company overview for the month"). A native
   <select>'s OPENED list is OS-rendered and unstylable, so the dashboard's
   "This Month" pattern (index.html .ph-ctl button + popMenu popover) is the
   only way to get a themed list - promoted here as a shared pair so the
   Customers filterbar and the Orders browser head stop re-inventing it.
   .qsel matches .ph-ctl's resting spec (index.html ~2119); .qsel-menu matches
   .crm-page .menu-dd's popover spec (above, ~2263) but position:fixed so a
   page helper can anchor it to the button's rect (the popMenu idiom).
   Existing tokens and an already-used shadow only - no new colour/font/token. */
.qsel{ display:inline-flex; align-items:center; justify-content:center; gap:6px; padding:8px 13px; background:transparent; border:1px solid var(--line); border-radius:8px; font-size:12.5px; font-weight:700; line-height:1.1; color:var(--ink-dim); white-space:nowrap; cursor:pointer; font-family:inherit; transition:border-color .12s, color .12s; }
.qsel:hover{ border-color:var(--line-strong, var(--line)); color:var(--ink); }
.qsel svg.caret{ width:11px; height:11px; flex:none; opacity:.7; margin-left:2px; }
.qsel-menu{ position:fixed; min-width:150px; max-height:min(420px,70vh); overflow-y:auto; background:var(--bg-panel-solid); border:1px solid var(--line); border-radius:10px; padding:5px; box-shadow:0 12px 40px rgba(0,0,0,.45); z-index:300; }
/* Section heading inside a menu (HQ-520 round 4: admin.html's Functional-roles
   picker keeps its HQ-503 grouping as non-clickable headings - the stand-in for
   a native <optgroup> label). */
.qsel-menu .mh{ font-size:10px; font-weight:700; letter-spacing:.06em; text-transform:uppercase; color:var(--ink-faint); padding:8px 11px 3px; }
.qsel-menu .mi{ display:flex; align-items:center; gap:9px; width:100%; background:transparent; border:none; color:var(--ink-dim); font-family:inherit; font-size:12.5px; font-weight:600; text-align:left; padding:8px 11px; border-radius:7px; cursor:pointer; white-space:nowrap; }
.qsel-menu .mi:hover{ background:var(--hover-bg); color:var(--ink); }
.qsel-menu .mi.on{ color:var(--brass-bright, var(--brass)); }
.qsel:disabled{ opacity:.5; cursor:not-allowed; }
/* HQ-520 round 4: table-embedded pickers ride a compact scale so a row stays a row -
   the b2b Edit-groups group-move (quote.html ?view=b2b) and Ship Today's per-line
   warehouse routing (shipping.html Items to pack). */
#view-b2b .qsel{ font-size:11.5px; padding:4px 9px; }
#items-section .qsel.line-wh{ font-size:10.5px; padding:2px 7px; margin-left:6px; vertical-align:middle; }
/* HQ-520 round 5: the Orders Filters popover stacks full-width dropdowns
   (quote.html's own .ofs-pop button{width:100%}) - label left, caret right,
   like a form control, instead of the pill's centred pair. */
#orders-fs .ofs-pop .qsel{ justify-content:space-between; }

/* HQ-441 (Adam, 2026-07-23): operations.html's Manage staff button joins the
   cal-bar's quiet segmented vocabulary (the .seg/.navbtns metrics) instead of
   the page-wide brass .wh-wrap .btn, which read as an oversized primary among
   the calendar's grey controls. Lives here because operations.html's own
   <style> block is at its ui-drift byte ceiling; same page-scoped-id
   precedent as the #orders-fs rules above. Existing tokens only. */
.cal-bar #cal-manage-staff{background:var(--bg-panel-solid)!important;border:1px solid var(--line)!important;color:var(--ink-dim)!important;font-size:12px!important;font-weight:600!important;padding:7px 12px!important;border-radius:8px!important}
.cal-bar #cal-manage-staff:hover{color:var(--ink)!important}

/* HQ-441 round 2b (Adam, 2026-07-23: "lock the Ready to Ship box and just
   have the orders scroll underneath, like the Customers box"): the Customers
   flex-chain (#sec-customers above) transplanted onto Ship Today, superseding
   both the HQ-434 bounded-region idiom (whose scroll container was
   .ship-scroll, so the Ready-to-Ship CARD HEAD slid away and the pinned th
   detached - the "black title box goes funny") and the brief round-2
   page-scroll conversion. #app locks to the viewport (its gate JS writes an
   inline display, hence !important, same as sales.html's rule); KPIs and
   controls pin; .ship-scroll stays the outer region so the inline pick
   workspace (#pack-work) and the notebar remain reachable below the fold of
   it; the queue grid fills the region exactly and the scroll the eye sees is
   INSIDE the Ready to Ship card's .tblwrap - card head fixed, th (sticky in
   warehouse.html's own <style>) pinned to the box top, rows sliding under it.
   No ancestor overflow-clearing needed any more: .tblwrap itself is the
   scroll box, exactly like Customers. The side cards cap at the region height
   and scroll themselves if taller. Desktop only; phones keep the natural
   whole-page scroll. Existing tokens/classes only - no new colour, font or
   token. */
html.qnav-shell body:has(#sec-ship.sec.on) #app{
  display:flex !important; flex-direction:column; box-sizing:border-box;
  height:calc(100vh - var(--qtop-h)); overflow:hidden;
}
html.qnav-shell.qimp-on body:has(#sec-ship.sec.on) #app{
  height:calc(100vh - var(--qtop-h) - 40px);
}
body:has(#sec-ship.sec.on) .wh-head{ flex:none; }
#sec-ship.sec.on{ display:flex; flex-direction:column; flex:1 1 auto; min-height:0; }
#sec-ship.sec.on .kpis{ flex:none; }
#sec-ship.sec.on .controls{ flex:none; }
#sec-ship.sec.on .ship-scroll{ flex:1 1 auto; min-height:0; overflow-y:auto; }
/* Adam 2026-07-23 (ultrawide review): the Receive Stock / RMA side column read
   wrong on BOTH ultrawide and standard 16:9 - the side cards now stack BELOW
   the queue box on every desktop width ("if those items sit on the bottom of
   that module then keep it there"). The queue card fills the visible region
   exactly (the Customers box), the side cards sit below its fold inside
   .ship-scroll. */
#sec-ship.sec.on .ship-scroll > .cols{ display:block; height:100%; box-sizing:border-box; }
#sec-ship.sec.on .cols > .card:has(.shiptbl){ display:flex; flex-direction:column; height:100%; box-sizing:border-box; min-height:0; }
#sec-ship.sec.on .cols > .card:has(.shiptbl) .card-head{ flex:none; }
#sec-ship.sec.on .tblwrap:has(.shiptbl){ flex:1 1 auto; min-height:0; overflow:auto; }
#sec-ship.sec.on .cols > .side2{ margin-top:16px; }
@media (max-width:900px){
  html.qnav-shell body:has(#sec-ship.sec.on) #app{ display:block !important; height:auto; overflow:visible; }
  #sec-ship.sec.on{ display:block; }
  #sec-ship.sec.on .ship-scroll{ overflow:visible; }
  #sec-ship.sec.on .ship-scroll > .cols{ display:grid; height:auto; }
  #sec-ship.sec.on .cols > .card:has(.shiptbl){ display:block; height:auto; }
  #sec-ship.sec.on .tblwrap:has(.shiptbl){ overflow:auto; }
  #sec-ship.sec.on .cols > .side2{ margin-top:0; }
}


/* Cost of Billing Drift - the management needs-attention box (index.html,
   product costing). Floats bottom-right of the globe, under the slide-over
   detail panel's z-index. Hidden via the native hidden attribute until the
   costing function (server-gated) has something to report; [hidden] alone
   would lose to the id's own display:flex, so it is restated here (same
   fix as .stagebar[hidden] elsewhere in this file). */
#costing-drift-box {
  position: absolute;
  right: 18px;
  bottom: 18px;
  z-index: 5;
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-width: 320px;
  padding: 12px 16px;
  background: var(--bg-panel);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  border: 1px solid var(--rust);
  border-radius: 12px;
  text-decoration: none;
  color: var(--ink);
}
#costing-drift-box[hidden] { display: none; }
#costing-drift-box:hover { border-color: var(--brass-bright); }
#costing-drift-box .cdb-eyebrow { font-size: 10px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--rust); }
#costing-drift-box .cdb-title { font-size: 14px; font-weight: 800; }
#costing-drift-box .cdb-count { font-size: 22px; font-weight: 700; color: var(--rust); font-variant-numeric: tabular-nums; }
#costing-drift-box .cdb-sub { font-size: 11px; color: var(--ink-dim); line-height: 1.5; }

/* Cost of Billing Drift - compact management-only strip (manufacturing.html,
   product costing). Hidden via the native hidden attribute until the costing
   function (server-gated) reports something to look at. */
.costing-strip {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin: 8px 14px 0;
  padding: 8px 14px;
  background: color-mix(in srgb, var(--rust) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--rust) 55%, transparent);
  border-radius: 8px;
  font-size: 12.5px;
  color: inherit;
  text-decoration: none;
}
.costing-strip[hidden] { display: none; }
.costing-strip .cs-label { letter-spacing: .06em; text-transform: uppercase; font-size: 10.5px; color: var(--rust); }
.costing-strip .cs-cta { margin-left: auto; font-weight: 700; }

/* Product costing (management only, inventory.html Costing tab + BOM
   breakdown detail sheet): status colours and small layout notes for the
   ratio/drift cells, built from the shared status tokens - the same
   red/amber/green the .pill classes already use in this repo. */
.costing-bad { color: var(--status-red); font-weight: 700; }
.costing-good { color: var(--status-green); font-weight: 600; }
.costing-drift-note { font-size: 11px; color: var(--status-amber); }
.costing-sub-note { font-size: 11px; color: var(--ink-dim); }
.costing-row-flag { background: color-mix(in srgb, var(--status-amber) 8%, transparent); }
.costing-detail-sub { margin-bottom: 10px; }
.costing-bom-h4 { margin: 12px 0 6px; }
.costing-hist-h4 { margin: 16px 0 6px; }
.costing-drift-card { margin-top: 16px; }

/* KPI tile click-to-filter affordance (HQ-402): the Items/support house idiom for
   a tile that carries an onClick - cursor + a quiet hover, and a brass border while
   its filter is the active one. Existing tokens only, additive (a tile with no
   click callback never gets the 'kclick' class, so every other page's KPI row is
   unaffected). */
.kpi.kclick { cursor: pointer; transition: border-color .12s, background .12s; }
.kpi.kclick:hover { border-color: var(--line-strong); background: var(--hover-bg); }
.kpi.kpi-active { border-color: var(--brass); }

/* Cross-document view transitions (HQ-437): module switches are full page
   loads (one page per module, no SPA), so between pages the browser used to
   flash white/blank. Opting in makes same-origin navigations crossfade the old
   page into the new one - and when the target was prerendered (the HQ-437
   hover speculation rules in quasar-nav.js), the swap reads as instant. Kept
   to a fast fade so navigation never feels slower; browsers without
   @view-transition ignore the whole block; reduced-motion users keep the
   plain cut. No colours, fonts or tokens - theme-lock unaffected. */
@view-transition { navigation: auto; }
/* The group pseudo must be capped too: the UA default gives ::view-transition-group(root)
   0.25s, and the transition (which holds INPUT while it runs) only finishes when the
   slowest of the three does - without this line the page is input-dead ~250ms per
   navigation instead of ~90ms (verified in Chrome 150). */
::view-transition-group(root) { animation-duration: 90ms; }
::view-transition-old(root) { animation-duration: 90ms; }
::view-transition-new(root) { animation-duration: 90ms; }
@media (prefers-reduced-motion: reduce) {
  @view-transition { navigation: none; }
}

/* HQ-442: the Field Service "Import from Miro" panel controls, laid out like the
   support-case Miro box - a compact left-aligned column with the search field above its
   buttons. Scoped to #miro-panel; flex/size only, no colour/font/token. */
#miro-panel .miro-controls{ max-width:340px; }
#miro-panel .miro-controls .searchwrap{ width:100%; margin-bottom:8px; }
#miro-panel .miro-controls .frow{ margin-bottom:0; }
#miro-panel .miro-controls .btn.sm{ height:36px !important; box-sizing:border-box !important; }
/* The field-service page's own search inputs use a solid var(--bg) fill (.controls
   input.search), not the shared .searchwrap glass fill (--qctl-bg) - so on this page the
   glass box looked lighter than the rest. Match the page: solid bg + line border. */
#miro-panel .searchwrap input.search{ background:var(--bg) !important; border:1px solid var(--line) !important; box-shadow:none !important; }
/* HQ-442: inset the Miro panel body to line up with its header (.panel .ph has 12px
   14px padding; the shared .panel .pb has 0 horizontal, which left the note + button
   row flush against the panel edge). Scoped to #miro-panel so no other panel moves. */
#miro-panel .pb{ padding-left:14px; padding-right:14px; }
/* Order notes (HQ-443): a compact notes list + add-note row, shared by
   shipping.html's order detail view and warehouse.html's Ship Today pick
   workspace. Both write through the SAME /order-timeline 'note' action
   quote.html's Activity trail already uses (HQ-283) - an order's notes live
   in ONE place (audit_events, table_name='quotes'), never a parallel notes
   table (CLAUDE.md "one master per data object"). Existing tokens only. */
.order-notes-add { display: flex; gap: 8px; margin-bottom: 10px; }
.order-notes-add input {
  flex: 1;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--r-control);
  padding: 8px 10px;
  font-size: 13px;
  color: var(--ink);
  font-family: inherit;
}
.order-notes-add input:focus { outline: none; border-color: var(--brass); }
.order-notes-list { display: flex; flex-direction: column; gap: 8px; max-height: 220px; overflow: auto; }
.order-notes-list .onote {
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  padding: 8px 11px;
  font-size: 12.5px;
  color: var(--ink);
  line-height: 1.45;
}
.order-notes-list .onote .om { font-size: 10.5px; color: var(--ink-faint); margin-bottom: 2px; }
.order-notes-empty { color: var(--ink-faint); font-size: 12px; }

/* ── HQ-443: invoice pop-out + "Email: ready to ship" (shipping.html) ───────
   Both are additive .modal instances using the page's existing #id .mcard/
   .mhead/.mbody/.mfoot idiom (see #okay-modal above) - only their own
   id-scoped rules live here, per the UI-drift ratchet (one look, one file). */
#invoice-modal #inv-close{padding:4px 10px}
/* Outstanding figure: tabular-nums so the digits line up like every other
   money column on this page - reuses --font-mono, no new token. */
.val-mono{font-family:var(--font-mono);font-variant-numeric:tabular-nums}

#ready-email-modal .mcard{width:560px}
#ready-email-modal #rem-close{padding:4px 10px}
#ready-email-modal #rem-already{font-size:12.5px;color:var(--ink-dim);background:var(--bg);border:1px solid var(--line);border-radius:8px;padding:8px 11px;margin:0 0 10px}
#ready-email-modal #rem-err{color:var(--rust);font-size:12px;margin-top:8px;min-height:14px}
#ready-email-modal .qf-textarea{min-height:220px}

/* ── HQ-452: dashboard modal outcome row (#dm-overlay, web/index.html) ──────
   openModal's `onSubmit` path keeps the modal OPEN across a real server call
   (workflows-and-gates.md: refuse, return the missing fields, keep the entered
   data), so it needs somewhere inside the panel to say what the server said.
   The rest of that modal's look is still page-scoped in index.html; this row is
   NEW css, so per "one look, one file" it lands here instead - built only from
   existing tokens (--rust / --good / --ink-dim), no new colour and no new token,
   so both themes follow automatically. */
#dm-overlay .dm-msg{
  margin-top:12px;
  padding:9px 11px;
  border:1px solid var(--line);
  border-radius:9px;
  background:var(--bg);
  font-size:12.5px;
  line-height:1.45;
  color:var(--ink-dim);
}
#dm-overlay .dm-msg.err{
  color:var(--rust);
  border-color:color-mix(in srgb, var(--rust) 45%, transparent);
  background:color-mix(in srgb, var(--rust) 10%, transparent);
}
#dm-overlay .dm-msg.ok{
  color:var(--good);
  border-color:color-mix(in srgb, var(--good) 45%, transparent);
  background:color-mix(in srgb, var(--good) 10%, transparent);
}

/* ── HQ-452: My Dashboard card lead figure (index.html #dash-focus-mydash) ──
   Adam: "these graphys do me no good so lets put a little bit more infomation
   here". Each personal card now leads with the number that matters and keeps
   its spark strip underneath as context, so the card reads before the graph
   does. Sits alongside the page's existing .qs-empty / .mc-note card-body
   idioms and is built only from existing tokens - no new colour, no new token. */
.mc-figure{
  padding:4px 0 8px;
  font-size:13px;
  line-height:1.4;
  color:var(--ink-dim);
}
.mc-figure b{
  /* HQ-523 round 3 (Adam: "this font ... looks funny"): the lead figure now uses
     the UI font like the KPI tile values beside it, not the serif display face -
     same size and weight, one look across every number on the dashboard. */
  font-family:var(--font-ui);
  font-size:20px;
  font-weight:800;
  letter-spacing:-.02em;
  color:var(--ink);
  margin-right:5px;
}
.mc-figure .sub{ color:var(--ink-faint); }

/* ── HQ-452 review fixes (web/index.html dashboard) ─────────────────────────
   1. The leave status pills. `.pill.st-*` (above) supplies only the COLOUR - the
      pill SHAPE lives in `.crm-page .pill`, and web/index.html carries no
      .crm-page class, so a pill rendered on the dashboard had colour but no
      radius, padding or nowrap and read as bare tinted text. This repeats the
      existing .crm-page metrics verbatim (no new value, no new token) under the
      two dashboard scopes only - deliberately NOT a bare global `.pill`, because
      sales.html, shipping.html and quote.html each carry their own page-scoped
      .pill treatment that a global base could fight. */
#dashboard .pill,
#dm-overlay .pill{
  display:inline-flex;
  align-items:center;
  gap:6px;
  font-size:11px;
  font-weight:700;
  border-radius:999px;
  padding:3px 10px;
  white-space:nowrap;
}
/* 2. Interactive rows on My Dashboard. .fs-row is a READ-ONLY list row, so a row
      that opens the withdraw / approve-decline gate looked identical to one that
      does nothing - no pointer, no hover. Reuses the same --hover-bg the other
      clickable rows on this page use. HQ-519 adds the Mail / Needs-me rows, which
      are real <a> links straight to the source record (not a role="button" div
      like the other two) - the anchor reset below stops the default underline/
      link colour fighting the same row look every other card on this page uses. */
#dash-focus-mydash .fs-row.mb-lvrow,
#dash-focus-mydash .fs-row.mb-mailrow,
#dash-focus-mydash .fs-row.mb-needsrow,
#dash-focus-mydash .fs-row.mb-fsrow,
#dash-focus-mydash .fs-row.mb-porow,
#dash-focus-mydash .fs-row.mb-qrow{ cursor:pointer; border-radius:8px; }
#dash-focus-mydash .fs-row.mb-lvrow:hover,
#dash-focus-mydash .fs-row.mb-mailrow:hover,
#dash-focus-mydash .fs-row.mb-needsrow:hover,
#dash-focus-mydash .fs-row.mb-fsrow:hover,
#dash-focus-mydash .fs-row.mb-porow:hover,
#dash-focus-mydash .fs-row.mb-qrow:hover{ background:var(--hover-bg); }
#dash-focus-mydash .fs-row.mb-lvrow:focus-visible,
#dash-focus-mydash .fs-row.mb-mailrow:focus-visible,
#dash-focus-mydash .fs-row.mb-needsrow:focus-visible,
#dash-focus-mydash .fs-row.mb-fsrow:focus-visible,
#dash-focus-mydash .fs-row.mb-porow:focus-visible,
#dash-focus-mydash .fs-row.mb-qrow:focus-visible{ outline:2px solid var(--brass); outline-offset:-2px; }
#dash-focus-mydash a.fs-row.mb-mailrow,
#dash-focus-mydash a.fs-row.mb-fsrow,
#dash-focus-mydash a.fs-row.mb-needsrow{ text-decoration:none; color:inherit; }
/* HQ-523: a single full-width card row on My Dashboard (the Mail list, whose
   sender + subject columns need the width). The page's own .focus-grid rules
   are two-column; this variant keeps the same gap/margin rhythm with one
   column. Layout only - no colour, no new token. */
#dashboard .focus-grid.one{ grid-template-columns:1fr; }
/* HQ-523 round 3 (Adam: "a blue outline and blue writing for darkmode and gold
   for lightmode"): the accent-outline button. var(--brass) IS blue in dark and
   gold in light, so one token-built rule delivers exactly that ask - existing
   tokens only, no new colour. */
#dashboard .mc-btn.outline{
  background:transparent;
  border-color:var(--brass);
  color:var(--brass);
}
#dashboard .mc-btn.outline:hover{
  border-color:var(--brass-bright);
  color:var(--brass-bright);
  background:var(--hover-bg);
}
/* 3. Field help text. openModal reused .dm-hint for BOTH a field's help text and
      its validation message - and .dm-hint is display:none until the field errors
      AND coloured with the error colour, so every hint was invisible until the
      user made a mistake and then appeared as if it were the error. Help text now
      has its own always-visible muted class. */
#dm-overlay .dm-field .dm-help{
  margin-top:5px;
  font-size:11px;
  line-height:1.4;
  color:var(--ink-dim);
}

/* ── HQ-457 site kiosk (web/kiosk.html) ─────────────────────────────────────
   The shared on-site tablet: name grid -> PIN pad -> clock in/out. A NEW page
   carries no ui-drift allowance, so EVERY kiosk style lives here, built only
   from the existing tokens (no new colour/font/token - the theme is locked).
   Sizing is deliberately big-touch (a wall tablet used with work gloves). */
.kk-body{
  min-height:100vh;
  display:flex;
  flex-direction:column;
  font-family:var(--font-ui);
  color:var(--ink);
  padding:env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
}
.kk-head{
  display:flex;
  align-items:center;
  gap:16px;
  padding:14px 22px;
  border-bottom:1px solid var(--line);
  background:var(--bg-panel);
  -webkit-backdrop-filter:blur(12px);
  backdrop-filter:blur(12px);
}
.kk-title{
  font-family:var(--font-title);
  font-size:22px;
  font-weight:800;
  letter-spacing:-.02em;
  margin:0;
}
.kk-device{ font-size:12px; color:var(--ink-faint); }
.kk-head .kk-spacer{ flex:1; }
.kk-clock{
  font-family:var(--font-mono);
  font-size:26px;
  font-weight:700;
  color:var(--ink);
}
.kk-main{
  flex:1;
  display:grid;
  grid-template-columns:1fr 320px;
  gap:18px;
  padding:18px 22px;
  align-items:start;
}
@media (max-width:900px){ .kk-main{ grid-template-columns:1fr; } }
.kk-grid{
  display:grid;
  grid-template-columns:repeat(auto-fill,minmax(160px,1fr));
  gap:12px;
}
.kk-person{
  display:flex;
  flex-direction:column;
  align-items:flex-start;
  gap:6px;
  min-height:84px;
  padding:14px;
  background:var(--bg-elev);
  border:1px solid var(--line);
  border-radius:var(--r-card);
  color:var(--ink);
  font-family:inherit;
  font-size:17px;
  font-weight:700;
  text-align:left;
  cursor:pointer;
}
.kk-person:hover{ border-color:var(--line-strong); background:var(--hover-bg-strong); }
.kk-person:focus-visible{ outline:2px solid var(--brass); outline-offset:-2px; }
/* Presence states (HQ-457 DELTA 2, the WHS register split): on = physically on
   site (the muster list), remote = clocked on at home, field = a field job or
   left-site-job-related. Three distinct dots so the tablet reads at a glance. */
.kk-person.on{ border-color:var(--status-green); }
.kk-person.remote{ border-color:var(--status-blue); }
.kk-person.field{ border-color:var(--status-amber); }
.kk-person.nopin{ opacity:.45; }
.kk-person .kk-dot{
  width:10px; height:10px;
  border-radius:var(--r-pill);
  background:var(--ink-faint);
  flex:none;
}
.kk-person.on .kk-dot{ background:var(--status-green); }
.kk-person.remote .kk-dot{ background:var(--status-blue); }
.kk-person.field .kk-dot{ background:var(--status-amber); }
.kk-person .kk-meta{
  display:flex; align-items:center; gap:6px;
  font-size:11px; font-weight:500; color:var(--ink-dim);
}
.kk-register{
  background:var(--bg-panel);
  -webkit-backdrop-filter:blur(12px);
  backdrop-filter:blur(12px);
  border:1px solid var(--line);
  border-radius:var(--r-card);
  padding:16px;
}
.kk-register h2{
  margin:0 0 10px;
  font-family:var(--font-ui);
  font-size:14px;
  font-weight:800;
  letter-spacing:.02em;
  color:var(--ink-dim);
  text-transform:uppercase;
}
.kk-reg-row{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:8px;
  padding:8px 0;
  border-bottom:1px solid var(--line);
  font-size:14px;
}
.kk-reg-row:last-child{ border-bottom:none; }
.kk-reg-row .kk-reg-state{ font-size:11px; color:var(--ink-dim); white-space:nowrap; }
.kk-empty{ font-size:13px; color:var(--ink-faint); padding:6px 0; }
/* The muster headline count (the number the fire warden reads) + the two
   visually distinct strips for people working but NOT on the premises. */
.kk-reg-count{
  display:inline-flex; align-items:center; justify-content:center;
  min-width:22px; height:22px;
  margin-left:6px; padding:0 7px;
  border-radius:var(--r-pill);
  font-family:var(--font-mono);
  font-size:12px; font-weight:700;
  color:var(--status-green);
  border:1px solid var(--status-green);
}
.kk-reg-strip{
  margin-top:14px;
  padding-top:12px;
  border-top:1px dashed var(--line-strong);
}
.kk-reg-strip[hidden]{ display:none; }
.kk-reg-strip h2{ font-size:12px; }
.kk-reg-strip.remote h2{ color:var(--status-blue); }
.kk-reg-strip.remote .kk-reg-count{ color:var(--status-blue); border-color:var(--status-blue); }
.kk-reg-strip.field h2{ color:var(--status-amber); }
.kk-reg-strip.field .kk-reg-count{ color:var(--status-amber); border-color:var(--status-amber); }
.kk-overlay{
  position:fixed; inset:0;
  z-index:100;
  display:flex; align-items:center; justify-content:center;
  background:var(--modal-overlay);
  padding:18px;
}
.kk-overlay[hidden]{ display:none; }
.kk-modal{
  width:min(430px,94vw);
  max-height:92vh;
  overflow:auto;
  background:var(--bg-panel-solid);
  border:1px solid var(--line);
  border-radius:var(--r-modal);
  padding:22px;
}
.kk-modal h3{ margin:0 0 4px; font-size:19px; font-weight:800; }
.kk-modal .kk-sub{ font-size:12.5px; color:var(--ink-dim); margin:0 0 14px; }
.kk-dots{
  display:flex; justify-content:center; gap:12px;
  margin:14px 0 18px;
}
.kk-dots i{
  width:14px; height:14px;
  border-radius:var(--r-pill);
  border:1px solid var(--line-strong);
  background:transparent;
}
.kk-dots i.f{ background:var(--ink); border-color:var(--ink); }
.kk-pad{
  display:grid;
  grid-template-columns:repeat(3,1fr);
  gap:10px;
}
.kk-key{
  height:62px;
  font-family:var(--font-mono);
  font-size:22px;
  font-weight:700;
  color:var(--ink);
  background:var(--bg-elev);
  border:1px solid var(--line);
  border-radius:var(--r-control);
  cursor:pointer;
}
.kk-key:hover{ border-color:var(--line-strong); }
.kk-key:active{ background:var(--hover-bg-strong); }
.kk-key.ghost{ background:transparent; font-family:var(--font-ui); font-size:14px; color:var(--ink-dim); }
.kk-choice{
  display:block;
  width:100%;
  padding:15px 16px;
  margin:8px 0 0;
  font-family:inherit;
  font-size:16px;
  font-weight:700;
  text-align:center;
  color:var(--ink);
  background:var(--bg-elev);
  border:1px solid var(--line);
  border-radius:var(--r-control);
  cursor:pointer;
}
.kk-choice:hover{ border-color:var(--line-strong); }
.kk-choice.primary{ background:var(--brass); border-color:var(--brass); color:var(--brass-on-bg); }
.kk-choice.primary:hover{ background:var(--brass-bright); }
.kk-choice.warn{ border-color:var(--status-amber); }
.kk-field{ margin:10px 0; }
.kk-field label{ display:block; font-size:12px; color:var(--ink-dim); margin-bottom:5px; }
.kk-field select,
.kk-field input{
  width:100%;
  padding:12px;
  font-size:16px;
  font-family:inherit;
  color:var(--ink);
  background:var(--bg-elev);
  border:1px solid var(--line);
  border-radius:var(--r-control);
}
.kk-toggle{
  display:inline-flex; align-items:center; gap:8px;
  padding:11px 14px;
  font-family:inherit; font-size:14px; font-weight:700;
  color:var(--ink-dim);
  background:var(--bg-elev);
  border:1px solid var(--line);
  border-radius:var(--r-control);
  cursor:pointer;
}
.kk-toggle[aria-pressed="true"]{ color:var(--ink); border-color:var(--brass); }
.kk-msg{ min-height:18px; margin-top:10px; font-size:13px; color:var(--ink-dim); }
.kk-msg.err{ color:var(--status-red); }
.kk-msg.ok{ color:var(--status-green); }
.kk-pair-wrap{
  flex:1;
  display:flex; align-items:center; justify-content:center;
  padding:22px;
}
.kk-pair-card{
  width:min(430px,94vw);
  background:var(--bg-panel);
  -webkit-backdrop-filter:blur(12px);
  backdrop-filter:blur(12px);
  border:1px solid var(--line);
  border-radius:var(--r-modal);
  padding:24px;
}

/* ── HQ-458 kiosk: admin sign-in, visitors and contractors ──────────────────
   Additive only, built from the same tokens as the HQ-457 block above (no new
   colour, font or token - the theme is locked). Two new register strips reuse
   the existing .kk-reg-strip shape; visitors take --status-violet and
   contractors --brass so the muster reads at a glance without a new hue. */

/* The visitor call-to-action: a first-class path beside the staff grid, not a
   link buried in a corner - a contractor arriving must see it immediately. */
.kk-cta{
  display:flex;
  flex-direction:column;
  align-items:flex-start;
  gap:5px;
  width:100%;
  margin:0 0 16px;
  padding:16px 18px;
  background:var(--bg-elev);
  border:1px solid var(--brass);
  border-radius:var(--r-card);
  color:var(--ink);
  font-family:inherit;
  text-align:left;
  cursor:pointer;
}
.kk-cta:hover{ background:var(--hover-bg-strong); }
.kk-cta:focus-visible{ outline:2px solid var(--brass); outline-offset:-2px; }
.kk-cta-t{ font-size:18px; font-weight:800; }
.kk-cta-s{ font-size:12px; font-weight:500; color:var(--ink-dim); }

/* Small helper line under a field (e.g. "suggested from your schedule"). */
.kk-hint{ font-size:11px; color:var(--ink-faint); margin-top:5px; }
.kk-hint[hidden]{ display:none; }

/* The muster headline breakdown: the count is everyone on the premises, so the
   line under it says how that number splits (staff / visitors / contractors). */
/* Sits directly under the h2, so it pulls up against the heading's own margin. */
.kk-reg-sub{ font-size:11px; color:var(--ink-faint); margin:-4px 0 10px; }
.kk-reg-strip.visitor h2{ color:var(--status-violet); }
.kk-reg-strip.visitor .kk-reg-count{ color:var(--status-violet); border-color:var(--status-violet); }
.kk-reg-strip.contractor h2{ color:var(--brass); }
.kk-reg-strip.contractor .kk-reg-count{ color:var(--brass); border-color:var(--brass); }

/* A tappable register row (a visitor signs OUT by finding their own name). Same
   row shape as the static .kk-reg-row, made a real button for touch + keyboard. */
button.kk-reg-row{
  width:100%;
  background:transparent;
  border:none;
  border-bottom:1px solid var(--line);
  border-radius:0;
  font-family:inherit;
  color:var(--ink);
  text-align:left;
  cursor:pointer;
}
button.kk-reg-row:hover{ background:var(--hover-bg-strong); }
button.kk-reg-row:focus-visible{ outline:2px solid var(--brass); outline-offset:-2px; }
button.kk-reg-row:last-child{ border-bottom:none; }
.kk-reg-name{ display:flex; flex-direction:column; gap:2px; min-width:0; }
.kk-reg-name em{ font-style:normal; font-size:11px; font-weight:500; color:var(--ink-faint); }

/* The WHS + emergency induction, sized to be actually read on a tablet. */
.kk-modal.wide{ width:min(680px,96vw); }
.kk-whs{
  max-height:44vh;
  overflow:auto;
  margin:8px 0 12px;
  padding:14px 16px;
  background:var(--bg-elev);
  border:1px solid var(--line);
  border-radius:var(--r-card);
}
.kk-whs h4{
  margin:0 0 7px;
  font-family:var(--font-ui);
  font-size:12px;
  font-weight:800;
  letter-spacing:.03em;
  text-transform:uppercase;
  color:var(--ink-dim);
}
.kk-whs p{ margin:0 0 9px; font-size:14px; line-height:1.55; color:var(--ink); }
.kk-whs p:last-child{ margin-bottom:0; }
.kk-whs-sec{ margin-top:14px; padding-top:12px; border-top:1px solid var(--line); }
.kk-whs-sec[hidden]{ display:none; }
.kk-ver{ font-family:var(--font-mono); font-size:11px; color:var(--ink-faint); }

/* The explicit acknowledgement tick. Big enough to hit with gloves on, and the
   Sign in button stays disabled until it is on. */
.kk-check{
  display:flex;
  align-items:center;
  gap:12px;
  padding:14px;
  background:var(--bg-elev);
  border:1px solid var(--line);
  border-radius:var(--r-control);
  font-size:15px;
  font-weight:700;
  color:var(--ink);
  cursor:pointer;
}
.kk-check input{ width:24px; height:24px; accent-color:var(--brass); flex:none; }
.kk-check.on{ border-color:var(--brass); }
.kk-choice[disabled]{ opacity:.45; cursor:not-allowed; }
.kk-choice[disabled]:hover{ border-color:var(--line); }
.kk-choice.primary[disabled]:hover{ background:var(--brass); border-color:var(--brass); }

/* ── HQ-461 kiosk re-layout: muster dashboard, three actions, on-demand lists ─
   Adam, after using the paired tablet: show employees on site at the top, then
   visitors and contractors, then a Staff sign in (name -> centre PIN pad ->
   confirm), a visitor/contractor sign in, and a Sign out that offers a staff
   member three choices. The permanent grid of every staff name is gone from the
   resting screen; it now lives behind "Staff sign in".

   Additive and token-only (the theme is locked): no new colour, font or token.
   Everything here is built on the HQ-457/458 kiosk shapes above and reuses them
   where they already fit - the sign-in list IS .kk-grid + .kk-person, the
   sign-out list IS button.kk-reg-row, the three big actions ARE .kk-cta. The two
   .kk-reg-strip.visitor / .contractor modifier rules above are no longer used by
   the page (visitors and contractors moved into the muster cards below); they are
   left in place rather than deleted so the strip shape stays complete.
   Sizing stays deliberately big-touch: a wall tablet used with work gloves. */

/* The resting screen is one column now (muster on top, actions under it), not
   the old content + register split. */
.kk-main.stack{ display:block; }

/* The whole-premises evacuation figure in the panel heading, given a bit more
   presence than a strip count. */
.kk-reg-count.big{ min-width:30px; height:28px; padding:0 9px; font-size:16px; }

/* The honesty line under the heading. It turns amber when the number on screen
   is not known to be true (never loaded, or a refresh that failed), because a
   confident "0 on site" from a failed read is a WHS lie. */
.kk-reg-sub.warn{ color:var(--status-amber); font-weight:600; }

/* The three muster groups. Employees first and biggest - that is the number the
   room reads from across the workshop - then visitors, then contractors. */
.kk-mgrid{
  display:grid;
  grid-template-columns:1.6fr 1fr 1fr;
  gap:14px;
  align-items:start;
}
@media (max-width:900px){ .kk-mgrid{ grid-template-columns:1fr; } }
.kk-mcard{
  min-width:0;
  padding:16px 18px;
  background:var(--bg-elev);
  border:1px solid var(--line);
  border-radius:var(--r-card);
}
.kk-mcard.staff{ border-color:var(--status-green); }
.kk-mcard.visitor{ border-color:var(--status-violet); }
.kk-mcard.contractor{ border-color:var(--brass); }
.kk-mcount{
  font-family:var(--font-mono);
  font-size:52px;
  font-weight:700;
  line-height:1;
  letter-spacing:-.03em;
  color:var(--ink);
}
.kk-mcard.staff .kk-mcount{ font-size:82px; color:var(--status-green); }
.kk-mcard.visitor .kk-mcount{ color:var(--status-violet); }
.kk-mcard.contractor .kk-mcount{ color:var(--brass); }
@media (max-width:900px){
  .kk-mcard.staff .kk-mcount{ font-size:62px; }
  .kk-mcount{ font-size:44px; }
}
.kk-mlabel{
  margin-top:7px;
  font-size:12px;
  font-weight:800;
  letter-spacing:.03em;
  text-transform:uppercase;
  color:var(--ink-dim);
}
.kk-mcard.staff .kk-mlabel{ font-size:14px; }
/* Who is on site, under each count - three clearly separated groups. Text only:
   nothing here is tappable, so a repaint discards no listener. */
.kk-mnames{
  display:flex;
  flex-wrap:wrap;
  gap:7px;
  margin-top:11px;
}
.kk-name{
  padding:6px 11px;
  background:var(--bg-panel-solid);
  border:1px solid var(--line);
  border-radius:var(--r-pill);
  font-size:13.5px;
  font-weight:600;
  color:var(--ink);
}
.kk-mcard.staff .kk-name{ font-size:15px; }
.kk-mnone{ font-size:12.5px; color:var(--ink-faint); }

/* The three big touch actions - the only interactive thing on the resting
   screen. They extend .kk-cta (same shape, taller, one primary). */
.kk-actions{
  display:grid;
  grid-template-columns:repeat(3,1fr);
  gap:14px;
  margin-top:18px;
}
@media (max-width:900px){ .kk-actions{ grid-template-columns:1fr; } }
.kk-act{
  min-height:126px;
  justify-content:center;
  gap:7px;
  margin:0;
  padding:20px 22px;
  border-color:var(--line);
}
.kk-act .kk-cta-t{ font-size:23px; }
.kk-act .kk-cta-s{ font-size:13px; line-height:1.45; }
.kk-act.primary{ background:var(--brass); border-color:var(--brass); color:var(--brass-on-bg); }
.kk-act.primary:hover{ background:var(--brass-bright); }
.kk-act.primary .kk-cta-s{ color:var(--brass-on-bg); opacity:.85; }

/* The on-demand name lists. 'Staff sign in' is the tile grid (.kk-grid +
   .kk-person, the shape Adam already knows); 'Sign out' is a short row list of
   only the people on the premises, so it doubles as the muster. */
.kk-picks{ max-height:56vh; overflow:auto; margin:4px 0 6px; }
.kk-picks.kk-grid{ grid-template-columns:repeat(auto-fill,minmax(190px,1fr)); }
.kk-picks .kk-person{ min-height:96px; }
.kk-picks button.kk-reg-row{ padding:15px 8px; font-size:17px; font-weight:700; }
.kk-picks .kk-reg-name em{ font-size:12px; }
.kk-who{ display:flex; flex-direction:column; gap:3px; min-width:0; }
/* An incomplete record: the roster could only fall back to the login handle
   because this person has no People directory row (roster's name_is_handle).
   Amber marks it as something to fix, rather than reading a handle as a name. */
.kk-flag{
  font-style:normal;
  font-size:10.5px;
  font-weight:700;
  letter-spacing:.02em;
  text-transform:uppercase;
  color:var(--status-amber);
}
/* Staff / Visitor / Contractor on a sign-out row. */
.kk-tag{
  display:inline-block;
  margin-right:8px;
  padding:2px 7px;
  border:1px solid var(--line-strong);
  border-radius:var(--r-pill);
  font-size:10.5px;
  font-weight:700;
  letter-spacing:.03em;
  text-transform:uppercase;
  color:var(--ink-dim);
}
/* The one-line footnote under the sign-out list (where the remote and off-site
   people are, since only bodies on the premises are listed). */
.kk-foot{
  margin:8px 0 0;
  font-size:12px;
  line-height:1.45;
  color:var(--ink-faint);
}
.kk-foot[hidden]{ display:none; }

/* The centred PIN pad: bigger keys, a wider 0, and the masked entry showing how
   many digits are in. Confirm is a .kk-choice and stays disabled until 6. */
.kk-pinstep{ text-align:center; }
.kk-pinstep .kk-dots{ gap:14px; }
.kk-pinstep .kk-dots i{ width:18px; height:18px; }
.kk-pinstep .kk-pad{ gap:12px; }
.kk-pinstep .kk-key{ height:72px; font-size:26px; }
.kk-pinstep .kk-key.wide{ grid-column:span 2; }
.kk-pin-count{ margin:0 0 14px; text-align:center; }

/* A second line inside a choice button. Whether you are still being PAID is the
   difference between "finished for the day", "leaving for a break" and "leaving
   for work", so each of those buttons says so under its own label. */
.kk-choice small{
  display:block;
  margin-top:5px;
  font-size:12px;
  font-weight:500;
  line-height:1.4;
  color:var(--ink-dim);
}
.kk-choice.primary small{ color:var(--brass-on-bg); opacity:.85; }

/* ── HQ-461 breaks and the one-tap return ─────────────────────────────────────
   Adam: "if they come back from a break as well, then sign back into site the
   idea is they are quick to just go click pin click. Then this helpes me with
   who is on site". Two things to dress, both additive and token-only (no new
   colour, font or token; nothing existing is restyled):
     1. the "on a break - expected back" strip, which sits with the other
        not-on-the-premises strips and is DELIBERATELY quiet. remote owns blue and
        off-site-on-a-job owns amber; a break is neither a presence nor a concern
        (unpaid, off site, back shortly), so its count is neutral rather than
        borrowing a status colour that already means something else here.
     2. the on-a-break marker on a sign-in tile, so a returning person spots
        their own name fast. Neutral for the same reason: .kk-flag's amber means
        "an incomplete record to fix", and a break is not a fault. */
.kk-reg-strip.onbreak .kk-reg-count{ color:var(--ink-dim); border-color:var(--line-strong); }
/* The ONE thing on the strip worth noticing: an absence long past a lunch break
   (the page's LONG_BREAK_MINS). A 30 minute lunch and a two hour absence must
   not read the same. */
.kk-reg-strip.onbreak .kk-reg-state.long{ color:var(--status-amber); font-weight:700; }
.kk-person.brk{ border-color:var(--ink-dim); }
.kk-person.brk .kk-dot{ background:var(--ink-dim); }
/* "on a break" under a name on a sign-in tile. Same shape as .kk-flag, neutral
   colour - a state, not a fault. */
.kk-back{
  font-style:normal;
  font-size:10.5px;
  font-weight:700;
  letter-spacing:.02em;
  text-transform:uppercase;
  color:var(--ink-dim);
}

/* ============================================================
   Operations calendar - the pending tray (HQ-140)
   ============================================================
   The Ops Calendar's right rail is a place to drag FROM: pending leave
   requests, a palette of block types (TAFE, sick, toolbox, public holiday),
   and unassigned jobs. Every one of these classes is a DRAG AFFORDANCE or a
   DROP AFFORDANCE - none of them restyle an existing component, and all of
   them are built from the existing tokens, so both themes follow. */

/* A draggable row in the tray (a leave request, an unassigned job). The row
   itself keeps whatever .lrow already gives it - this adds only the grab
   cursor and the lifted state while it is being dragged. */
.trayrow{ cursor:grab; }
.trayrow:active{ cursor:grabbing; }
.trayrow.dragging{ opacity:.45; }

/* The block palette. Chips wrap in the rail's width; each one is a whole
   block type waiting to be placed, so it never empties like a queue does. */
.traypal{ display:flex; flex-wrap:wrap; gap:6px; }
.traychip{
  display:inline-flex;
  align-items:center;
  gap:6px;
  cursor:grab;
  border:1px solid var(--line-strong);
  border-radius:999px;
  background:var(--bg-elev);
  color:var(--ink);
  font-family:inherit;
  font-size:11px;
  font-weight:600;
  line-height:1;
  padding:6px 10px;
}
.traychip:hover{ background:var(--hover-bg-strong); border-color:var(--brass); }
.traychip:active{ cursor:grabbing; }
.traychip.dragging{ opacity:.45; }
/* The swatch carries the entry type's colour, which is the ONE thing here that
   must be a per-record inline style (the JS paints it from the same CAL_COLOR
   map the calendar blocks and the legend use, so a chip always matches the
   block it will create). */
.traychip .sw{ width:9px; height:9px; border-radius:3px; flex:none; }

/* Drop affordance on the calendar. .dropok is set on dragover for a cell that
   can take the payload, .dropno for one that cannot (a filtered-out lane, or a
   site-wide block over a person row) - so a refusal is visible BEFORE the drop
   rather than as a toast after it. */
.ccell.dropok{ background:color-mix(in srgb, var(--brass) 16%, transparent); outline:1px dashed var(--brass); outline-offset:-2px; }
.ccell.dropno{ background:color-mix(in srgb, var(--status-red) 12%, transparent); outline:1px dashed var(--status-red); outline-offset:-2px; }

/* The one-line instruction under the palette. Same voice as .helper, smaller
   because it sits inside a rail card rather than a sheet. */
.trayhint{ font-size:11px; color:var(--ink-faint); line-height:1.45; margin-top:8px; }

/* The block confirm hides its "Until" row with the native hidden attribute, but
   pages set .fld{display:block} - an author rule that outranks the UA
   [hidden]{display:none} default. This restores the attribute's meaning for
   form rows (same reasoning as .sch-month-grid[hidden] above). */
.fld[hidden]{ display:none; }

/* The Ops Calendar's right-hand tray column. One gap rule replaces the
   per-card inline margin the rail used to carry, so adding a card to the tray
   needs no inline style at all. */
.calrail{ display:flex; flex-direction:column; gap:16px; align-items:stretch; }

/* ── HQ-475: internal notes on the shared inbox (web/inbox.html) ──────────────
   Adam built this composer in round 5 (web/conversations.html) and it was lost when
   that page was shelved; conversation_messages.kind (migration 20260723002500) gives
   it live backing again. The rules live HERE, not in the page's <style> block, per
   "one look, one file" - which is also what keeps inbox.html's ui-drift baseline flat.

   Colour language is the shipped support.html pair, expressed with EXISTING tokens
   only - internal = brass/amber, reply = the blue already on the page. No new hex, no
   new token, so theme-lock stays green and both themes follow the tokens. */
.composer.internal{ background:rgba(201,164,92,.08); border-color:rgba(201,164,92,.45) }
.composer.internal textarea{ border-color:rgba(201,164,92,.4) }
.composer .cmode{ display:inline-flex; border:1px solid var(--line); border-radius:8px; overflow:hidden; margin-bottom:9px }
.composer .cmode button{ background:none; border:none; color:var(--ink-dim); font-size:11.5px; font-weight:700; padding:6px 13px; cursor:pointer; font-family:inherit }
.composer .cmode button.on{ background:rgba(93,155,216,.2); color:var(--ink) }
.composer .cmode button[data-m="note"].on{ background:rgba(201,164,92,.22); color:var(--brass-bright) }
/* An internal note in the thread: amber, amber left rule, and its own chip colour so
   it can never be mistaken for something the customer saw.

   HQ-475 fix (Adam, 2026-07-29: "they dont come through it is a email reply, or internal
   note as the colour should reflect"). The left rule and the chip text used --brass /
   --brass-bright, which are only amber in the LIGHT theme - in dark they resolve to the
   blues that --status-blue also lands on, so a note's border and label came out the same
   colour as a REPLY's and the two were genuinely hard to tell apart. --status-amber has
   no per-theme override, so the note reads amber in both. The rgba fills were already
   theme-neutral amber and are unchanged. */
.tmsg.internal{ background:rgba(201,164,92,.10); border-color:rgba(201,164,92,.35); border-left:3px solid var(--status-amber) }
.tmsg.internal .tkind{ background:rgba(201,164,92,.16); color:var(--status-amber) }

/* HQ-478 - `.helper`, the small muted note under a control.

   Adam, 2026-07-27, of the Linked-documents empty state on the person page: "this is
   way to large as well". It was not a sizing choice - `.helper` was never DEFINED for
   that page, so every `<p class="helper">` fell back to the browser default (16px, full
   --ink) and became the largest text in the panel, louder than the field labels it was
   meant to sit quietly under.

   Seven pages (admin, support, warehouse, inventory, compliance, operations, sales)
   each define their own private copy of this class in their own <style> block, which is
   exactly the duplication "one look, one file" exists to remove - and two pages
   (people, field-service) use the class without defining it anywhere, which is the bug.
   Defining it HERE fixes those two and gives the other seven something to delete as
   they are revisited. The values are the ones those seven already agree on, so no page
   changes appearance: their local copy has equal specificity and loads later, so it
   still wins, and where there is no local copy this is what should always have applied. */
.helper{ font-size:11.5px; color:var(--ink-faint); line-height:1.5; margin:10px 0 0 }

/* HQ-478 - one tidy row per onboarding form (Adam: "the spacing on forms at the bottom
   could be better alligned"). Each form used to render as a .kv label/status row FOLLOWED
   by a separate .acts row carrying the button, and .acts adds a 12px top margin - so a
   list of four forms came out as eight ragged rows with the buttons detached from the
   labels they belong to. This puts label, status and action on ONE baseline with a hairline
   between forms, so the column of statuses and the column of buttons both line up. */
.obform{ display:flex; align-items:center; gap:10px; padding:9px 0; border-top:1px solid var(--line) }
.obform:first-of-type{ border-top:none }
.obform .obf-label{ flex:1 1 auto; min-width:0; font-size:12.5px; color:var(--ink) }
.obform .obf-status{ flex:none; font-size:11.5px; color:var(--ink-faint); white-space:nowrap }
.obform .obf-acts{ flex:none; display:flex; gap:6px }
/* HQ-478 - a form that has actually been REQUESTED of somebody, still outstanding. Four
   rows all reading "Not completed" say nothing about which two are being waited on. Brass
   left rule from the existing token set - no new colour. */
.obform.pending{ box-shadow:inset 2px 0 0 var(--brass) }
.obform.pending .obf-status{ color:var(--brass-bright, var(--brass)) }

/* HQ-477 - wrapping table cells (Adam, 2026-07-27: Roles and Departments is "so hard to
   read and follow and there is a lot of the horztional scrolls as well").

   THE CAUSE, for whoever reads this next: admin.html's own style block sets
   `table.tbl td{...white-space:nowrap}` on EVERY cell, and `.tblwrap{overflow:auto}`
   turns any cell that cannot wrap into a sideways scrollbar. The Roles table holds a
   sentence of prose per row, the Departments and Roles tables hold lists of people
   chips, and the Team chat channels table holds one checkbox per role - around twenty
   of them. None of those can fit on one unbroken line, so all three scrolled.

   Not fixed by deleting that nowrap: it is load-bearing for the narrow data cells on
   the same page (counts, dates, pills, the sticky matrix column), and dropping it
   globally would reflow every table in Administration to no one's request. So the
   default stands and the cells that hold prose or lists opt IN to wrapping.

   Specificity is deliberate: `table.tbl td.wrapcell` (0,2,2) beats `table.tbl td`
   (0,1,2), so this wins from the shared stylesheet even though the page's inline
   <style> block loads after it. A plain `.wrapcell` would silently lose.
   No colour, no font, no new token - layout only, so theme-lock is untouched. */
table.tbl td.wrapcell{ white-space:normal }
/* Prose cell: readable measure, and never the column that stretches a table. */
table.tbl td.wrapcell.prose{ min-width:220px; max-width:520px; line-height:1.45 }
/* A list of chips (people, roles) laid out as a wrapping flex row rather than a single
   inline run - guarantees the break regardless of what the cell inherits. */
.cellchips{ display:flex; flex-wrap:wrap; gap:5px 12px; align-items:center }

/* HQ-251 unit 4 - the Audit Log DETAIL cell (Adam, 2026-07-30: "the audit log is showing
   too much detail when for exmaple bruce makes a new guide or post" - a website_news
   insert was dumping its whole post body, hundreds of words, into one table cell).
   Same clamp-never-truncate idea as .tmsg's .tb-clamp/.tb-more (HQ-475): the full text
   always stays in the DOM, .adet-clamp only HIDES the overflow tail, and admin.html's
   "Show full detail" button removes that class in place - no re-render, no modal. This
   is a length clamp rather than inbox.html's height-mask one: admin.html slices the
   summary text at a character count before it ever reaches the DOM (one table-row line,
   not a multi-paragraph message body), so there is no tall block to mask, only a hidden
   tail span to reveal. .adet-more matches .tb-more's look exactly (brass link, same
   weight/size) - no new colour or token. table.tbl td.adet also needs the plain-.wrapcell
   white-space:normal above (added alongside it in admin.html), or the nowrap default on
   every other .tbl td would force this cell into the sideways scrollbar HQ-477 fixed. */
table.tbl td.adet .adet-clamp{ display:none }
table.tbl td.adet .adet-more{ margin-left:6px; background:none; border:0; padding:0; font:inherit; font-size:11.5px; font-weight:700; color:var(--brass); cursor:pointer }
table.tbl td.adet .adet-more:hover{ text-decoration:underline }
table.tbl td.adet{ color:var(--ink) }
/* HQ-477 - the full role matrix, collapsed by default. Twenty modules by two ticks is
   irreducibly wide, which is why the per-role editor (openRoleModal) is now the primary
   path and this is the opt-in view. Styled as a real disclosure, not a bare marker. */
.mxdet > summary{ cursor:pointer; padding:9px 14px; font-size:12px; font-weight:700; color:var(--ink-dim); border:1px solid var(--line); border-radius:var(--r-control); background:var(--bg-elev); list-style:none; display:flex; align-items:center; gap:8px }
.mxdet > summary::-webkit-details-marker{ display:none }
.mxdet > summary:hover{ color:var(--ink) }
.mxdet > summary::before{ content:'\25B8'; color:var(--ink-faint); transition:transform .12s }
.mxdet[open] > summary::before{ transform:rotate(90deg) }
.mxdet > summary .sub{ font-weight:500; color:var(--ink-faint) }
.mxdet[open] > summary{ margin-bottom:10px }
/* HQ-477 - the "opens the editor" affordance on a role row. The per-role panel existed
   since HQ-455 but was reachable only by clicking a row that did not look clickable,
   with the instruction buried in a paragraph under the matrix below it. */
.rowgo{ color:var(--ink-faint); font-weight:700; font-size:11px; white-space:nowrap }
table.tbl tr.clk:hover .rowgo{ color:var(--brass-bright, var(--brass)) }
/* Normalise type=search so the users filter keeps the .in look in both themes (the
   native decorations are removed; HQ-477 gives the filter its own Clear button, which
   is keyboard-reachable as the native one is not). */
input.in[type="search"]{ -webkit-appearance:none; appearance:none }
input.in[type="search"]::-webkit-search-cancel-button,
input.in[type="search"]::-webkit-search-decoration{ -webkit-appearance:none; appearance:none; display:none }
/* ── HQ-481: dashboard quick-create modal (index.html #dm-overlay) additions ──
   .dm-selwrap - the modal styles its selects identically to its text inputs, so a
   dropdown gave no sign it held options (Adam, 2026-07-28: "location should give
   options"). The wrap draws a chevron and suppresses the (already invisible)
   native arrow; existing tokens only.
   .dm-scanrow - the incoming.html scanbox idiom for the modal's armable barcode
   Scan control: input + button on one row, --brass ring while armed (the same
   signal incoming.html's .scanning carries). */
.dm-selwrap{ position:relative; display:block }
.dm-selwrap::after{ content:"\25BE"; position:absolute; right:11px; top:50%; transform:translateY(-50%); pointer-events:none; color:var(--ink-dim); font-size:11px; line-height:1 }
.dm-selwrap select{ appearance:none; -webkit-appearance:none; padding-right:30px }
.dm-scanrow{ display:flex; gap:8px; align-items:stretch }
.dm-scanrow input{ flex:1; min-width:0 }
.dm-scanrow .dm-scanbtn{ flex:none; padding:0 14px; background:var(--bg); border:1px solid var(--line-strong); border-radius:9px; font-size:12.5px; font-weight:600; color:var(--ink); cursor:pointer }
.dm-scanrow .dm-scanbtn:hover{ border-color:var(--brass) }
.dm-scanrow.scanning input{ border-color:var(--brass); box-shadow:0 0 0 3px color-mix(in srgb, var(--brass) 22%, transparent) }
.dm-scanrow.scanning .dm-scanbtn{ border-color:var(--brass); color:var(--brass-bright) }

/* ── HQ-489: order-header flags (quote.html) ────────────────────────────────
   Standing facts about an order, beside its number: "Sent" (the document was
   emailed to the customer), "Follow-up sent" (a payment reminder went out) and
   "Overdue". Adam, 2026-07-28: the Email-to-customer button only ever flashed a
   toast and reset itself, so after a reload nothing on the page showed the
   invoice had gone out - "should i have a another green oral there saying
   invoice has been sent, and then another if a follow up as been sent or over
   due?".
   The colour formula is deliberately the same one .crm-page .pill uses (a
   --status-* token at 12% fill, 40% border, full-strength text) - repeated
   rather than reused because quote.html is not a .crm-page, so that rule set
   does not reach it. Existing tokens only: no new colour, no new token. */
.q-flag{display:inline-flex;align-items:center;gap:5px;font-size:11px;font-weight:700;border-radius:999px;padding:2px 9px;border:1px solid var(--line);color:var(--ink-dim);background:var(--bg-panel-solid);white-space:nowrap}
.q-flag.good{color:var(--status-green);border-color:color-mix(in srgb,var(--status-green) 40%,transparent);background:color-mix(in srgb,var(--status-green) 12%,transparent)}
.q-flag.amber{color:var(--status-amber);border-color:color-mix(in srgb,var(--status-amber) 40%,transparent);background:color-mix(in srgb,var(--status-amber) 12%,transparent)}
.q-flag+.q-flag{margin-left:5px}
/* ── HQ-490: the "From" picker in quote.html's Email-to-customer modal ───────
   Which connected mailbox a document goes out AS - personal by default, with the
   shared sales@ / support@ / production@ boxes selectable. The surrounding fields
   in that modal still carry their original inline styles (pre-existing drift);
   this new one is a class so it adds none, and matches them exactly. Tokens only.
   Uses the `hidden` ATTRIBUTE rather than an inline display:none so the source
   stays free of style attributes - hence the explicit [hidden] rule. */
.ed-fld{ margin-bottom:10px }
.ed-fld[hidden]{ display:none }
.ed-fld select{ width:100%; background:var(--bg); border:1px solid var(--line); border-radius:7px; padding:8px 10px; font-size:13px; color:var(--ink) }
.ed-fld select:disabled{ color:var(--ink-dim); cursor:default }
.ed-fld .ed-note{ margin-top:4px; font-size:11.5px }

/* ── HQ-492: the Inbox LINK in outbox.html's views rail ─────────────────────
   Adam, 2026-07-28: "on the mail module can you add in inbox above the pending
   tab." It is an <a> to /inbox.html sitting among <button> queue filters, so it
   needs the underline off to match them - outbox.html has no global
   a{text-decoration:none} (only a .agent-line-scoped one), and adding bytes to
   that page's own <style> block would raise its ui-drift count. More specific
   than the page's `.views .vrow`, so load order cannot undo it. */
.views a.vrow{ text-decoration:none }

/* ── HQ-491: .ph-sub base rule (the muted explainer paragraph) ───────────────
   Adam, 2026-07-29: the Shared-mailboxes explainer and the AI-providers text "do
   not match our theme" - both rendered at full body size. Cause: admin.html
   defines this class ONLY as `.panel .ph-sub`, so every use inside a `.card`
   instead of a `.panel` (the shared-mailbox intro, the AI-providers note, and
   the "No AI providers registry yet" empty state) matched nothing and fell back
   to the default paragraph size.
   Same three declarations as admin.html's scoped rule - this is not a restyle,
   it is the intended look finally reaching the elements the narrower selector
   missed. The scoped rule still wins inside a panel (two classes beats one) with
   identical values, so nothing already correct changes. .ph-sub is used only by
   admin.html, so a base rule here cannot reach another page. */
.ph-sub{ font-size:12px; color:var(--ink-dim); margin:0 0 12px }
/* HQ-251: a zero-margin variant for a lone .ph-sub paragraph inside a card-body (the
   Functional roles explainer and the role detail panel's placeholder). Additive and
   scoped to .ph-sub so it reaches nothing else - avoids adding to the inline
   style="margin:0" already repeated across admin.html, which the ui-drift ratchet
   tracks and must only fall, never rise. */
.ph-sub.m0{ margin:0 }

/* ── HQ-493: anchor the Orders header controls to the right ──────────────────
   Adam, 2026-07-29: "all statuses, this montht, filters, new order need to move
   back to the right again". They did not move - HQ-486 removed "+ New Quote"
   from the END of that row, and because the group was never anchored, deleting a
   button simply left its width as dead space on the right.
   .ofs-head is a flex row whose .searchwrap grows but caps at max-width:560px,
   so on a wide screen the leftover space collects AFTER the controls instead of
   before them. margin-left:auto on the first item after the search hands that
   space to the left of the group, pinning the group to the right edge - so the
   row now survives adding or removing a control without re-tuning anything. */
.ofs-head .rf-count{ margin-left:auto }

/* ── HQ-493: breathing room above the Shared mailboxes card ──────────────────
   Adam: "the share mail boxes need a gap above the boxes above." It sat directly
   under the integrations grid with no separation.

   16px, which is admin.html's actual card rhythm (8 occurrences of margin-top:16px;
   nothing on that page uses 18). A pre-merge review caught the first version: it used
   18px and claimed in this very comment to be matching a rhythm that does not exist,
   which would have left 18 above the card and 16 below it. Measured, not asserted. */
#sm-card{ margin-top:16px }

/* HQ-495: the Email signatures card sits in the same stack, same measured 16px rhythm. */
#sig-card{ margin-top:16px }

/* ── HQ-501: B2B pricing "What the customer pays" ────────────────────────────
   The right-hand column of .b2b-cols now stacks two cards (the per-product
   override and the new preview). 16px matches the gap .b2b-cols already sets
   between its grid children, so the vertical rhythm reads as one grid rather
   than two systems. align-content:start keeps the pair at the top instead of
   stretching to the (much taller) category table beside it. */
.b2b-stack{ display:grid; gap:16px; align-content:start }
/* Money and percentages read right-aligned; the surrounding .b2b-tbl already
   right-aligns its th, this carries it to the cells the preview adds. */
.b2b-prev-tbl td.b2b-num, .b2b-prev-tbl th.b2b-num{ text-align:right; white-space:nowrap }
/* The preview is read-only text, not the fixed-width input grid .b2b-tbl was
   built for, so let the product column take the space it needs. */
.b2b-prev-tbl{ table-layout:auto }
.b2b-prev-tbl th:not(:first-child){ width:auto }
.b2b-prev-tbl td{ padding:7px 8px; border-bottom:1px solid var(--line); vertical-align:top }

/* HQ-506 round 3: the category table's two heading levels (Adam's dictated Batteries /
   Star Range / Scotty AI / BMG / ... structure). The BRAND row becomes the big divider
   with a clear gap above; the SECTION row sits inside a brand, smaller, with its own
   breathing room. #view-b2b outweighs the page's legacy .b2b-grp rule (that style block
   is ratcheted and cannot grow, so the override lives here - one look, one file). */
#view-b2b .b2b-tbl tr.b2b-grp td{ padding:20px 8px 6px; font-size:12.5px; letter-spacing:.06em; color:var(--ink); border-bottom:1px solid var(--line-strong) }
#view-b2b .b2b-tbl tr.b2b-sec td{ padding:14px 8px 4px; font-size:10.5px; font-weight:700; color:var(--ink-faint); text-transform:uppercase; letter-spacing:.05em; border-bottom:1px solid var(--line) }
/* HQ-506 round 6: Edit-groups drag - a draggable row shows a grab cursor, and the
   section heading under the pointer lights up as the drop slot. */
#view-b2b .b2b-tbl tr[draggable="true"]{ cursor:grab }
#view-b2b .b2b-tbl tr.b2b-sec.b2b-drop td{ background:var(--hover-bg); color:var(--ink); border-bottom:1px solid var(--brass) }
/* Round 8 (Adam: "this edit groups didnt let me change the product groups under that
   section"): the page's legacy .b2b-tbl clips the first column with an ellipsis, which
   was SWALLOWING the per-row group selects and rename inputs - the controls rendered but
   were invisible on any long category name. The first column wraps instead. */
#view-b2b .b2b-tbl td:first-child{ white-space:normal; overflow:visible; text-overflow:clip }
/* The group-rename input reads as an input, not a heading: normal case, left-aligned. */
#view-b2b .b2b-sec-rn{ width:220px; text-transform:none; letter-spacing:0; text-align:left; font-size:12px; font-weight:600; color:var(--ink); background:var(--bg); border:1px solid var(--line); border-radius:7px; padding:4px 8px }
/* HQ-507 (Adam: "still has a random x next to some names"): the pending-remove and
   empty-group-remove buttons carried no styling on this page at all (the app's other
   ".x" rule is scoped to .crm-page, which this page never carries) - a bare unstyled
   <button> reads as a stray grey box wedged into the row. Scoped the same small
   borderless glyph the rest of the app uses for a remove control. */
#view-b2b .b2b-tbl .x{ background:transparent; border:none; color:var(--rust); cursor:pointer; font-size:15px; line-height:1; padding:0 0 0 6px; vertical-align:middle }
#view-b2b .b2b-tbl .x:hover{ color:var(--ink) }
/* The category-rename input sits in the name column: same shape as the group-rename
   input above so the two read as one family of control, not two different widgets. */
#view-b2b .b2b-cat-rn{ width:200px; text-transform:none; letter-spacing:0; text-align:left; font-size:12.5px; font-weight:500; color:var(--ink); background:var(--bg); border:1px solid var(--line); border-radius:7px; padding:4px 8px }
/* Round 8: compact result rows for the preview's customer/product pick lists - the page's
   own .cat-row is sized for the catalog modal and blew the panel up. */
#view-b2b #b2b-prev-cust-results .cat-row, #view-b2b #b2b-prev-prod-results .cat-row{ padding:6px 10px }
#view-b2b #b2b-prev-cust-results .cd, #view-b2b #b2b-prev-prod-results .cd{ font-size:12.5px }
#view-b2b #b2b-prev-cust-results .cs, #view-b2b #b2b-prev-prod-results .cs{ font-size:11px }

/* HQ-501 round 2: each per-product override is its own block - a header naming
   the product + its RRP, then one labelled cell per tier with the effective
   price under it. Replaces a table row of six unlabelled inputs. */
.b2b-ovr{ border:1px solid var(--line); border-radius:9px; padding:10px 12px; margin-bottom:10px }
.b2b-ovr-head{ display:flex; align-items:center; gap:8px; margin-bottom:8px }
.b2b-ovr-head>div{ flex:1; min-width:0 }
.b2b-ovr-tiers{ display:grid; grid-template-columns:repeat(3,1fr); gap:10px }
@media(max-width:700px){ .b2b-ovr-tiers{ grid-template-columns:1fr } }
/* Round 4 (Adam: "small and medium and large should be close to there box on the right
   hand side"): the whole tier cell right-aligns, so the tier name sits directly over its
   inputs and the label text hugs the field instead of floating a column-width away. */
.b2b-ovr-tier{ font-size:11px; font-weight:700; color:var(--ink-dim); text-transform:uppercase; letter-spacing:.04em; margin-bottom:4px; text-align:right }
.b2b-ovr-cell label{ display:flex; align-items:center; justify-content:flex-end; gap:8px; font-size:11.5px; color:var(--ink-dim); margin-bottom:5px }
.b2b-ovr-cell input{ width:90px }
.b2b-ovr-pays{ font-size:11.5px; color:var(--ink-dim); margin-top:2px; text-align:right }
/* HQ-520: both remove-icons in this panel ("Remove this override" and the legacy
   "Clear the saved fixed price") picked up no colour rule at all, so one read as a
   plain grey glyph next to the other - same destructive-x red used everywhere else
   (.crm-page .cp-row .x, #view-b2b .b2b-tbl .x), consistently this time. */
.b2b-ovr-head .x, .b2b-ovr-pays .x{ background:transparent; border:none; color:var(--rust); cursor:pointer; font-size:15px; line-height:1; padding:0 0 0 6px; vertical-align:middle }
.b2b-ovr-head .x:hover, .b2b-ovr-pays .x:hover{ color:var(--ink) }

/* Round 4: the locked-customer box - who every price and saved deal on this panel
   belongs to, persistently visible while products are searched under it. */
.b2b-lockbox{ display:flex; align-items:center; gap:9px; border:1px solid var(--line); border-radius:9px; padding:8px 12px; margin-bottom:8px; background:var(--bg) }
.b2b-lockbox .b2b-lock-ic{ display:inline-flex; color:var(--ink-dim) }
.b2b-lockbox .b2b-lock-ic svg{ width:15px; height:15px }
.b2b-lockbox .b2b-lock-who{ flex:1; min-width:0; font-size:12.5px }
.b2b-lockbox .b2b-lock-open{ font-size:12px; font-weight:600; white-space:nowrap }

/* Conversations inbox (HQ-520): Context/Assign/Resolve are the three primary
   actions on an open conversation, but shared the same neutral .ab look as every
   other chip in the thread head (inbox.html's own <style>, e.g. "Not in CRM",
   the AI summary Refresh). Give just these three the theme accent (blue dark /
   gold light) so they read as the primary actions, without recolouring every
   .ab chip on the page. Scoped to #thread-head .ch-actions (not a bare
   [data-to] selector) so this never reaches an unrelated data-to attribute on
   another page sharing this stylesheet. */
#thread-head .ch-actions #chip-ctx, #thread-head .ch-actions #chip-assign,
#thread-head .ch-actions [data-to="resolved"], #thread-head .ch-actions [data-to="in-progress"]{
  border-color:var(--brass); color:var(--brass);
}
#thread-head .ch-actions #chip-ctx:hover, #thread-head .ch-actions #chip-assign:hover,
#thread-head .ch-actions [data-to="resolved"]:hover, #thread-head .ch-actions [data-to="in-progress"]:hover{
  border-color:var(--brass-bright); color:var(--brass-bright);
}

/* Conversations inbox Context panel (HQ-520): "Link an order" search box + the
   "Create a case" handoff link, both new/changed in this round. The link colour
   is the same pattern already used elsewhere for a link inside body copy
   (.crm-page .prow .pcontact a, .ob-doc a) - was a hardcoded inline style
   before, now the shared token via a class. */
.conv-page #ctx-order-link .search-bare{ width:100%; margin-top:2px; }
.conv-page #ctx-order-results{ margin-top:4px; }
.conv-page .dpanel .note a{ color:var(--brass-bright,var(--brass)); text-decoration:none; }
.conv-page .dpanel .note a:hover{ text-decoration:underline; }

/* ── Conversation thread bubbles (HQ-475) ────────────────────────────────────
   Adam, 2026-07-29: "when looking into the conversations inbox, the comment section
   boxs look liek they drag a little... they dont come through it is a email reply, or
   internal note as the colour should reflect and a label on each comment seciton
   within that thread."

   Moved here from web/inbox.html's <style> block ("one look, one file"). The rules
   arrived carrying raw hex for the reply and AI accents, which the light theme could
   not follow; both values are already tokens (--status-blue / --status-violet), so
   this is the same colour in dark and a correct one in light. .tmsg.internal is NOT
   redefined here - it already lives with the note composer styles above, where its
   dark-theme bug is fixed in the same round.

   The "drag" is not styling: a quoted mail chain made a single bubble hundreds of
   lines tall. .tb-clamp caps a long body and .tb-more reveals the rest, so the thread
   stays readable without ever hiding content the reader cannot get back to. */
.thread{ display:flex; flex-direction:column; gap:10px; max-height:56vh; overflow-y:auto; padding-right:2px; margin-top:10px }
.tmsg{ background:var(--bg-panel-solid); border:1px solid var(--line); border-radius:12px; padding:11px 14px }
.tmsg .th{ display:flex; align-items:center; gap:8px; font-size:11.5px; color:var(--ink-faint); margin-bottom:6px; flex-wrap:wrap }
.tmsg .th b{ color:var(--ink); font-size:12px }
.tmsg .th .when{ margin-left:auto }
.tmsg .tb{ font-size:13px; color:var(--ink); line-height:1.55; white-space:pre-wrap; overflow-wrap:anywhere }
.tmsg.customer{ border-left:3px solid var(--ink-faint) }
.tmsg.reply{ background:color-mix(in srgb, var(--status-blue) 9%, transparent); border-color:color-mix(in srgb, var(--status-blue) 35%, transparent); border-left:3px solid var(--status-blue) }
.tmsg.ai{ background:color-mix(in srgb, var(--status-violet) 9%, transparent); border-color:color-mix(in srgb, var(--status-violet) 35%, transparent); border-left:3px solid var(--status-violet) }
.tkind{ font-size:9px; font-weight:800; letter-spacing:.06em; text-transform:uppercase; border-radius:5px; padding:2px 6px; background:var(--hover-bg); color:var(--ink-dim); flex:none }
.tmsg.reply .tkind{ background:color-mix(in srgb, var(--status-blue) 14%, transparent); color:var(--status-blue) }
.tmsg.ai .tkind{ background:color-mix(in srgb, var(--status-violet) 16%, transparent); color:var(--status-violet) }
.tmsg .tacts{ display:flex; gap:7px; margin-top:10px; flex-wrap:wrap }
.tmsg .tacts .ab.primary{ background:var(--brass); border-color:var(--brass); color:var(--brass-on-bg); font-weight:700 }
.tmsg textarea.edit-box{ width:100%; box-sizing:border-box; min-height:84px; background:var(--bg); border:1px solid color-mix(in srgb, var(--status-violet) 40%, transparent); border-radius:9px; padding:10px 12px; font-size:13px; line-height:1.5; color:var(--ink); resize:vertical; font-family:var(--font-ui) }
/* The long-body clamp. max-height rather than -webkit-line-clamp: the body is pre-wrap
   plain text, so a line count is not a reliable height, and line-clamp would also
   swallow the trailing newline structure of a quoted chain.

   The soft bottom edge is a MASK, not a gradient overlay. The first version painted a
   gradient ending in var(--bg-panel-solid) - correct on a plain .tmsg, wrong on every
   tinted one, because .reply, .internal and .ai each layer a colour-mix background over
   that. A pre-merge review caught it. Masking fades the CONTENT instead, so it is
   colour-independent and there is no fourth variant to keep in step. `black` here is a mask
   ALPHA channel, not a paint colour - theme-lock rightly rejected the hex form. */
.tmsg .tb.tb-clamp{ max-height:190px; overflow:hidden;
  -webkit-mask-image:linear-gradient(to bottom, black calc(100% - 44px), transparent);
  mask-image:linear-gradient(to bottom, black calc(100% - 44px), transparent) }
.tmsg .tb-more{ margin-top:6px; background:none; border:0; padding:0; font:inherit; font-size:11.5px; font-weight:700; color:var(--brass); cursor:pointer }
.tmsg .tb-more:hover{ text-decoration:underline }

/* ── The amber informational note (HQ-496) ────────────────────────────────────
   Lifted out of web/inbox.html's <style> block, where it was the ONLY definition -
   so web/admin.html, which also uses the class, was rendering it with no background,
   no border and no font-size at all: a bare 16px paragraph in a card whose every
   other line is 11.5-12.5px. Found by measuring the rendered sizes, not by reading
   the markup, which looked correct.

   The two raw amber values it carried are now --status-amber, which has no per-theme
   override, so this reads correctly in light as well as dark. The shift is 4 units in
   one channel - imperceptible, and it buys the light theme. */
.unmatched-note{ font-size:12.5px; color:var(--ink-dim); line-height:1.6;
  background:color-mix(in srgb, var(--bg) 90%, var(--status-amber));
  border:1px solid color-mix(in srgb, var(--status-amber) 27%, transparent);
  border-radius:10px; padding:10px 12px; margin-bottom:9px }

/* ── HQ-251 Unit E: card-subhead - a labelled divider INSIDE one .card-body ──
   admin.html's AI Chatbot section (Adam, 2026-07-29: "there is a lot going on")
   folded its separate Knowledge and Documents cards into one Knowledge card, split
   by a subhead instead of a second card-head. Same uppercase-label idiom as
   .kk-whs h4, generalised so any card can group its body into labelled parts
   instead of spawning another card. Existing tokens only (--line, --ink-faint). */
.card-subhead{ margin:18px 0 8px; padding-top:14px; border-top:1px solid var(--line);
  font-size:11px; font-weight:800; letter-spacing:.05em; text-transform:uppercase;
  color:var(--ink-faint) }
.card-subhead:first-child{ margin-top:0; padding-top:0; border-top:none }

/* ── HQ-251 Unit F: subnav-hd - a muted, non-clickable group label INSIDE
   admin.html's #subtabs strip (Adam, 2026-07-29: "so much happening on this page").
   Nine flat data-sec buttons are grouped into Access / System / Content & AI; the
   label is a plain <span>, never a <button> or a wrapping <div>, so it is inert to
   the strip's own click-wiring (which only iterates its <button> children) and never
   introduces an extra </div> ahead of the strip's close. flex-basis:100% forces each
   label onto its own line in the strip's flex-wrap layout. Same uppercase-label idiom
   as .card-subhead / .tkind. Existing tokens only (--ink-faint). */
.subnav-hd{ flex-basis:100%; margin:6px 0 1px; font-size:10px; font-weight:800;
  letter-spacing:.06em; text-transform:uppercase; color:var(--ink-faint) }
.subnav-hd:first-child{ margin-top:0 }

/* HQ-251: the SIDEBAR twin of .subnav-hd above - the muted group label inside the
   .qside-subs strip (Access / System / Content and AI over the ten Administration
   items). Separate class because the sidebar strip is a vertical column with its own
   left rule, not admin.html's horizontal pill strip; same tokens, no new colour. */
.qside-subhd{ display:block; margin:7px 0 2px 14px; font-size:10px; font-weight:800;
  letter-spacing:.06em; text-transform:uppercase; color:var(--ink-faint) }
.qside-subhd:first-child{ margin-top:2px }

/* ── HQ-520 round 6: Gold Support register (web/gold-support.html) ───────────
   gold-support.html's own <style> block is over its ui-drift byte ratchet, so every
   class this round adds lives here under a .gsp- prefix. Three pieces:

   1. .gsp-kpis / .gsp-kpi - the "Total / Expiring soon / Expired" click-to-filter
      tiles (Adam: "some boxes on top which could have total Gold Support Customer,
      Expirying soon and Expired"). Same numeric spec as the Orders .okpi tiles
      above (icon-badge kic, uppercase dim label, 18-19px bold value) but a plain
      CSS grid of 3 - this board has no per-status spark series to draw, unlike
      Orders' okpi. Existing --status-* / color-mix tint idiom, --brass 'on' border.
      (An asterisk immediately followed by a slash, written together anywhere
      inside a CSS comment, closes it early - the space in "--status-* /" two
      lines up is load-bearing, not decorative; this exact typo is what silently
      dropped every rule below from the live CSSOM the first time this shipped -
      see test/quasar-ui-css-syntax.test.js, which now catches the whole class.)
   2. .gsp-cust-wrap / .gsp-cust-results / .gsp-pick-x - the add-member box's new
      CRM contact search (Adam: "the add member gives the option to search a
      customer to add gold support to that customers CRM"), a popover under the
      input built to the same spec as .qsel-menu (bg-panel-solid, 1px line, radius
      10, the same shadow) rather than inventing a second popover look.
   No new colour/font/token - --status-green/amber/red, --bg-panel-solid, --line,
   --ink/--ink-dim/--ink-faint, --brass and --hover-bg all already exist above. */
.gsp-kpis{ display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:12px; margin-bottom:16px; }
.gsp-kpi{ display:flex; align-items:center; gap:12px; background:var(--bg-panel-solid); border:1px solid var(--line); border-radius:14px; padding:12px 16px; cursor:pointer; text-align:left; font-family:inherit; transition:border-color .12s,background .12s; }
.gsp-kpi:hover{ border-color:var(--brass); }
.gsp-kpi .kic{ width:42px; height:42px; flex:none; display:grid; place-items:center; border-radius:999px; }
.gsp-kpi .kic svg{ width:20px; height:20px; }
.gsp-kpi.green .kic{ background:color-mix(in srgb,var(--status-green) 16%,transparent); color:var(--status-green); }
.gsp-kpi.amber .kic{ background:color-mix(in srgb,var(--status-amber) 16%,transparent); color:var(--status-amber); }
.gsp-kpi.bad .kic{ background:color-mix(in srgb,var(--status-red) 16%,transparent); color:var(--status-red); }
.gsp-kpi .ktext{ min-width:0; display:flex; flex-direction:column; gap:2px; }
.gsp-kpi .lbl{ font-size:11px; font-weight:700; letter-spacing:.04em; text-transform:uppercase; color:var(--ink-dim); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.gsp-kpi .val{ font-size:19px; font-weight:700; color:var(--ink); line-height:1.15; font-variant-numeric:tabular-nums; }
.gsp-kpi.on{ border-color:var(--brass); }
.gsp-kpi.on .lbl{ color:var(--brass-bright,var(--brass)); }

.gsp-cust-wrap{ position:relative; flex:1; min-width:0; display:flex; align-items:center; }
/* input targeted by element, not by its #add-email id - a literal "#add" in this
   file reads as a 3-digit hex colour to the theme-lock palette scanner (the same
   trap "#b2b" sprang in round 4). The wrap holds exactly one input. */
.gsp-cust-wrap input{ padding-right:30px; }
.gsp-pick-x{ position:absolute; right:7px; top:50%; transform:translateY(-50%); width:20px; height:20px; display:grid; place-items:center; border:none; background:transparent; color:var(--ink-faint); font-size:15px; line-height:1; font-family:inherit; padding:0; cursor:pointer; border-radius:999px; }
.gsp-pick-x:hover{ color:var(--ink); background:var(--hover-bg); }
.gsp-cust-results{ position:absolute; top:calc(100% + 6px); left:0; right:0; z-index:60; max-height:300px; overflow-y:auto; background:var(--bg-panel-solid); border:1px solid var(--line); border-radius:10px; box-shadow:0 12px 40px rgba(0,0,0,.45); }
.gsp-cust-row{ display:block; width:100%; text-align:left; background:transparent; border:none; padding:9px 12px; cursor:pointer; font-family:inherit; }
.gsp-cust-row:hover{ background:var(--hover-bg); }
.gsp-cust-row .nm{ display:block; font-size:13px; font-weight:700; color:var(--ink); }
.gsp-cust-row .sub{ display:block; font-size:11.5px; color:var(--ink-dim); margin-top:2px; }
.gsp-cust-empty{ padding:10px 12px; font-size:12.5px; color:var(--ink-dim); }

/* ── HQ-520 round 7 ──────────────────────────────────────────────────────────
   1. .gsp-head / .gsp-wrap - gold-support.html joins the standard page shell
      (Adam: "the boxs seperate like how we have our inventory then having the
      gold support title on the top left ... jonthan['s page] doesnt match our
      quasar hq ui style"). Same numeric spec as inventory.html's .inv-head
      (23px/800 title, 12.5px dim sub); the KPI row and the register card then
      stack under it as siblings, exactly like Inventory's Items page.
   2. :root[data-embedded="1"] - a page loaded inside another page's iframe
      shows ONLY its overlay/modal surface: the app shell (sidebar/topbar,
      built by quasar-nav.js, which also skips building in embedded mode) and
      the page body hide. Precedent: quote.html's own per-page embedded rules
      for the projects.html Quotes tab - promoted here because inbox.html now
      embeds support.html's New Case modal (Adam: "can that new case centre
      pop out stay on that page").
   3. .qframe-* - the host side of that embed: a fixed backdrop + panel
      holding the iframe, and a footer that appears on success ("go to case
      or stay in conversations"). Same tokens as the app's other overlays.
   4. .qtoggle - the Customers Accounts/People/Gold Support toggle buttons
      (Adam: "do not match our theme for long squares") - the .qsel resting
      look for a click-toggle; .active takes the .okpi.on treatment (brass
      border + tint) instead of the glass pill fill. A separate class, not a
      .chip restyle, because .chip carries app-wide !important pill rules
      other pages still want. */
.gsp-wrap{ padding:12px 22px 24px; max-width:1920px; margin:0 auto; }
.gsp-head{ display:flex; align-items:flex-start; gap:16px; margin-bottom:14px; flex-wrap:nowrap; }
.gsp-head .titles{ display:flex; flex-direction:column; gap:3px; min-width:0; }
.gsp-head h1{ font-family:var(--font-ui); font-size:23px; font-weight:800; margin:0; color:var(--ink); letter-spacing:-.02em; line-height:1.12; }
.gsp-head .sub{ display:block; font-size:12.5px; color:var(--ink-dim); margin-top:3px; line-height:1.3; }
.gsp-head .spacer{ flex:1; }

:root[data-embedded="1"] #app{ display:none !important; }
/* the shell never builds in embedded mode (quasar-nav.js returns early), so the
   page's own legacy .topbar - normally hidden by the html.qnav-shell rule that
   embedded mode never adds - needs hiding here instead */
:root[data-embedded="1"] .topbar{ display:none !important; }
:root[data-embedded="1"] body{ background:transparent !important; }
/* HQ-520 round 8 (Adam: the case pop-out "doesnt look right"): inside the embed,
   support.html's own .ov/.sheet modal chrome must NOT render a second modal - the
   host's .qframe is the one visible frame. Neutralise the inner dim + panel so the
   sheet fills the iframe edge to edge and the whole thing reads as ONE modal. */
:root[data-embedded="1"] .ov{ background:transparent; padding:0; align-items:stretch; }
:root[data-embedded="1"] .ov .sheet{ width:100%; max-height:none; border:0; border-radius:0; box-shadow:none; }

.qframe-ov{ position:fixed; inset:0; z-index:1400; background:var(--modal-overlay, rgba(5,6,8,.78)); display:flex; align-items:center; justify-content:center; padding:24px; }
.qframe{ position:relative; width:min(880px, 100%); height:min(760px, 100%); background:var(--bg-panel-solid); border:1px solid var(--line); border-radius:14px; overflow:hidden; display:flex; flex-direction:column; box-shadow:0 20px 60px rgba(0,0,0,.4); }
.qframe iframe{ flex:1 1 auto; width:100%; border:0; background:transparent; }
.qframe-x{ position:absolute; top:10px; right:12px; z-index:2; width:26px; height:26px; display:grid; place-items:center; border:none; background:transparent; color:var(--ink-faint); font-size:17px; line-height:1; font-family:inherit; cursor:pointer; border-radius:999px; }
.qframe-x:hover{ color:var(--ink); background:var(--hover-bg); }
.qframe-foot{ flex:none; display:flex; align-items:center; gap:10px; padding:12px 16px; border-top:1px solid var(--line); }
.qframe-foot .msg{ flex:1; min-width:0; font-size:13px; color:var(--ink); }

/* HQ-520 round 8 (Adam: the topbar mail dropdown is "not much use") - the inbox
   counts strip quasar-mail.js renders at the top of the Mail dock: Unassigned /
   over-SLA / pending drafts, each a click-through. Tokens only; the warn state
   reuses --rust (the existing alert hue) rather than introducing a colour. */
.qmail-stats{ display:flex; gap:8px; padding:10px 12px 8px; border-bottom:1px solid var(--line); }
/* display:flex overrides the UA [hidden] rule (same trap as .stagebar above) -
   without this, the empty strip renders as a bare bordered bar while the count
   fetch is in flight or failed, exactly the states it must be invisible in. */
.qmail-stats[hidden]{ display:none; }
.qmail-stats .st{ flex:1; min-width:0; background:var(--bg); border:1px solid var(--line); border-radius:10px; padding:7px 10px; cursor:pointer; text-align:left; font-family:inherit; }
.qmail-stats .st:hover{ border-color:var(--line-strong, var(--line)); }
.qmail-stats .st .n{ display:block; font-size:16px; font-weight:700; color:var(--ink); line-height:1.1; }
.qmail-stats .st .l{ display:block; font-size:10.5px; color:var(--ink-dim); margin-top:2px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.qmail-stats .st.warn .n{ color:var(--rust); }

/* support.html's new case-page "Gold Support" row (round 7): faint helper text
   for the Member-years / Not-a-member spans. A UNIQUE class, not a .dim rule -
   the fork's existing bare .dim outside tables deliberately renders uncolored,
   and recoloring it would restyle surfaces this round never touched. Lives
   here because the fork's own <style> block is at its inline-style ratchet. */
.sp-wrap .gsdim{ color:var(--ink-faint); }

.qtoggle{ display:inline-flex; align-items:center; gap:6px; padding:7px 13px; background:transparent; border:1px solid var(--line); border-radius:8px; font-size:12.5px; font-weight:700; line-height:1.1; color:var(--ink-dim); white-space:nowrap; cursor:pointer; font-family:inherit; transition:border-color .12s, color .12s, background .12s; }
.qtoggle:hover{ border-color:var(--line-strong, var(--line)); color:var(--ink); }
.qtoggle.active{ border-color:var(--brass); background:color-mix(in srgb, var(--brass) 10%, transparent); color:var(--brass-bright, var(--brass)); }
/* HQ-524 round 2 (Adam: "can the section names stand out a little more") - the
   order editor's CUSTOMER / LINE ITEMS / SHIPPING / FIELD SERVICE / TOTALS
   headings are quote.html's page-scoped .card h3 (11px, --ink-faint). Additive
   override, scoped to the editor (#editor) only so it never touches any other
   page's .card h3. Same letter-spacing, existing tokens only - just a stronger
   colour/weight/size so the labels read instead of fading into the card. */
#editor .card h3{ color:var(--ink); font-weight:800; font-size:12px }

/* HQ-526 round 2 (Adam, 2026-08-02): the muted helper paragraph right under the
   Assign-to-Field-Service row on quote.html sat with no gap above it - the row's
   own bottom margin is zero, so the paragraph ran straight into the button/state
   text above it. Scoped to the one card so it never touches an unrelated .muted
   line elsewhere in the editor. */
#editor #field-service-card > .muted{ margin-top:10px }

/* ── qdrop - a themeable trigger + popover pair, generalised from quote.html's
   page-local .ofs-fbtn/.ofs-pop (HQ-524 round 2's Field-Service job-type picker)
   so any page can front a native <select> with a themed custom dropdown without
   duplicating the CSS per page. Added for support.html's case-status dropdown
   (HQ-526 round 2, Adam 2026-08-02: house dropdown treatment, not a native OS
   popup) - lives here, shared, so the next page that needs one reuses it instead
   of growing another page-local copy. Same shape/tokens as .ofs-fbtn/.ofs-pop. */
.qdrop{ position:relative; display:inline-flex }
.qdrop>.btn{ display:inline-flex; align-items:center; gap:6px }
.qdrop .fchev{ font-size:10px; opacity:.8 }
.qdrop-pop{ display:none; position:absolute; top:calc(100% + 6px); left:0; flex-direction:column;
  gap:8px; padding:12px; min-width:200px; background:var(--bg-panel-solid); border:1px solid var(--line);
  border-radius:12px; box-shadow:0 12px 38px rgba(0,0,0,.45); z-index:60;
  /* HQ-526 round 4: the composer pickers (Saved replies / Insert article) front
     selects with long option lists - cap the popover and scroll inside it. */
  max-height:320px; overflow-y:auto }
.qdrop-pop.open{ display:flex }
.qdrop-pop button{ width:100% }
/* HQ-543: the row a qdrop popover shows for the state you are ALREADY in. It is not
   a choice - clicking it is a no-op by definition - so it reads as a marked current
   value rather than an option that silently failed. Existing tokens only. */
.qdrop-pop button.on{ color:var(--brass-bright); opacity:1; cursor:default }
.qdrop-pop button.on::after{ content:"\2713"; margin-left:auto; font-weight:800 }
.qdrop-pop button.on[disabled]{ opacity:1 }
/* HQ-543: a qdrop fronting a filter that is actually narrowing the list. Existing
   tokens only - it must read as "this is on", not as a new colour. */
.qdrop.set>.btn{ color:var(--brass-bright); border-color:var(--brass) }
.qdrop.set>.btn .fchev{ color:var(--brass-bright) }

/* ── HQ-526 round 4 (Adam's support/inbox/mail pass, 2026-08-02) ─────────────
   All additive, token-based, scoped by container id/wrapper so nothing leaks
   onto other pages. */

/* Mail (outbox.html): the reading pane's "Replying to" / "Your reply (edit
   freely)" h3s are the page-local 11px --ink-faint shape that washes out in
   both themes - same defect and same fix as #editor .card h3 above. #read-view
   is the one container all of them render into. */
#read-view h3{ color:var(--ink); font-weight:800; font-size:12px }

/* Inbox (inbox.html): the right-rail section titles (CUSTOMER / LINK AN ORDER /
   ORDER HISTORY / QUOTES / PAST CONVERSATIONS...) - same washed .dpanel h4
   shape, scoped to the context column so support/conversations' own .dpanel
   copies are untouched. */
#ctx-col .dpanel h4{ color:var(--ink); font-weight:800; font-size:12px }

/* Inbox ORDER HISTORY + SALES ORDERS rows (Adam: "the words are not alligned"):
   the flex .lrow let each row's title start wherever that row's order-number
   chip ended. A fixed first column makes every title start at the same x - the
   same visual grid erp.html's Customer 360 table gives this exact data. Pure
   layout, no colour. Scoped to the TWO number-chip-first cards only: .lrow is
   reused by five other rail cards with different child shapes (title-first,
   2-child) that a fixed 96px first column would break - never widen this to
   #ctx-col .lrow. The old flex .spacer is redundant inside the grid. */
#ctx-orders .lrow, #ctx-sales-orders .lrow{
  display:grid; grid-template-columns:96px minmax(0,1fr) auto; align-items:center }
#ctx-orders .lrow .spacer, #ctx-sales-orders .lrow .spacer{ display:none }

/* Inbox STATUS filter strip (Adam: "the boxes are oral not our square style"):
   the .chip class is the sitewide 999px pill by convention (section 7 forces
   it), right for tags/badges but wrong for this segmented status filter - the
   square-tab equivalent support.html's #cases-qtabs already uses. Scoped to the
   one strip; every other .chip keeps its pill. */
#status-chips .chip{ border-radius:var(--r-control) !important }

/* .dpanel empty-state / caption lines (support RMA "No money intents on this
   case yet...", + siblings): the pages define .sub only under other containers
   (.sp-head/.sheet), so a bare .sub inside a .dpanel rendered at ambient size
   and full ink - oversized against its own card. One shared rule, values
   matched to the existing .sheet .sub shape. */
.dpanel .sub{ font-size:12px; color:var(--ink-dim); line-height:1.4 }

/* support.html case-page action pills (.ab in the header action row and the
   RMA action/decision grids - Reply / Add Internal Note / Assign / Raise RMA /
   Authorise replacement...): Adam's round-4 direction brings his fork onto the
   Option C tiers, and these are exactly the "real actions" the accent tier
   exists for. Same construction as .btn.accent, scoped to support's own
   containers (.ch-actions/.actgrid) under .sp-wrap - inbox.html shares .sp-wrap
   AND has its own unrelated .ab chips, so the container class is load-bearing:
   never widen this to a bare ".sp-wrap .ab". */
.sp-wrap .ch-actions .ab, .sp-wrap .actgrid .ab{
  color:var(--brass);
  border-color:color-mix(in srgb,var(--brass) 42%,transparent);
  background:color-mix(in srgb,var(--brass) 9%,transparent);
}
.sp-wrap .ch-actions .ab:hover, .sp-wrap .actgrid .ab:hover{
  color:var(--brass-bright);
  border-color:var(--brass-bright);
  background:color-mix(in srgb,var(--brass) 15%,transparent);
}

/* ── HQ-537 (Craig Smith access-model round): the Sales globe account panel's
   "Staff note" (a persistent, difficult-customer style warning), read-only
   until Edit is clicked. sales.html's own account-profile classes (.gd-sec,
   .gd-facts etc.) live in that page's per-page <style> block - legacy debt,
   never added to - so a genuinely NEW control here goes into quasar-ui.css
   per ui-and-design.md, token-based, scoped to its own class only. */
.gd-staffnote{ display:flex; flex-direction:column; gap:8px; margin-bottom:13px }
.gd-staffnote-hint{ font-size:11px; color:var(--ink-faint); margin:-6px 0 2px; font-weight:400; text-transform:none; letter-spacing:normal }
.gd-staffnote-text{ font-size:12.5px; color:var(--ink-dim); line-height:1.4; white-space:pre-wrap }
.gd-staffnote textarea{
  width:100%; box-sizing:border-box; background:var(--bg); border:1px solid var(--line);
  border-radius:var(--r-control); padding:8px 10px; font-size:12.5px; color:var(--ink);
  font-family:inherit; resize:vertical;
}
.gd-staffnote textarea:focus{ border-color:var(--brass); outline:none }
.gd-staffnote-acts{ display:flex; gap:8px }
