/* ===========================================================================
   Chemtex ERP - theme.css
   Design system v2. Loads AFTER app.css / chat.css / field.css and overrides
   them by cascade order, never by !important.

   Why a layer and not a rewrite
   -----------------------------
   app.css is 1,180 lines carrying 653 selectors of hard-won layout across
   three apps. Rewriting it to change how things LOOK would put every one of
   those layouts at risk for a cosmetic gain. So app.css keeps owning
   structure - grids, widths, what sits where - and this file owns the visual
   language: colour, elevation, radius, motion, state.

   Rolling back is removing one <link>. Nothing else has to be undone.

   Contents
     1  tokens - light            8  buttons
     2  tokens - dark             9  inputs
     3  base + motion            10  tables
     4  view transitions         11  pills, chips, badges
     5  surfaces + elevation     12  modal
     6  sidebar + nav            13  toasts
     7  topbar                   14  skeleton + progress
                                 15  command palette
                                 16  charts
                                 17  density, motion opt-out, print
                                18  busy buttons
   =========================================================================== */

/* ---------------------------------------------------------------- 1. LIGHT */
:root {
  color-scheme: light;

  /* ink */
  --ink:      #10192a;
  --ink-2:    #465469;
  --ink-3:    #6d7c90;
  --ink-4:    #96a3b4;

  /* ground */
  --bg:        #eef1f5;
  --bg-2:      #e6ebf1;
  --surface:   #ffffff;
  --surface-2: #f7f9fb;
  --surface-3: #eef2f7;

  /* strokes */
  --line:   #dde4ec;
  --line-2: #eaeff4;
  --line-3: #c8d3e0;

  /* brand - steel blue.  --primary is the ACCENT (text, borders, icons);
     --primary-solid is the FILL that carries white text. They diverge in
     dark mode, where a fill dark enough for white text is too dark to read
     as a label. */
  --primary:       #1f5f8b;
  --primary-600:   #184c70;
  --primary-700:   #123f5e;
  --primary-solid: #1f5f8b;
  --primary-50:    #e8f1f7;
  --primary-100:   #d3e5f2;
  --primary-line:  #bcd7ea;

  /* brand - copper */
  --copper:       #b4632a;
  --copper-600:   #93501f;
  --copper-solid: #b4632a;
  --copper-50:    #fbf1e8;
  --copper-line:  #ebd0b7;

  /* semantic */
  --green:       #24704a;  --green-solid: #2f7a4f;
  --green-50:    #e8f5ee;  --green-line:  #bfe0cd;
  --red:         #b3261e;  --red-solid:   #b3261e;
  --red-600:     #93201a;  --red-50:      #fceceb;
  --red-line:    #f0c4c1;
  --amber:       #9a6a12;  --amber-ink:   #8a5a12;
  --amber-solid: #c08a1e;  --amber-50:    #fdf4e3;
  --amber-line:  #edd6a6;
  --violet:      #6a4c93;  --violet-50:   #f0ebf7;
  --violet-line: #d9cbe9;
  --teal:        #0e7c73;  --teal-50:     #e4f3f1;

  /* app.css legacy aliases */
  --hover:   #f2f7fb;
  --tint-ok: var(--green-50);

  /* field.css shipped its own short token names before this system existed.
     Aliasing them here - rather than renaming 55 usages in field.css - means
     the field portal inherits dark mode and every future palette change for
     free, and field.css keeps working untouched if this file is removed. */
  --p:   var(--primary);
  --p6:  var(--primary-600);
  --acc: var(--copper);
  --mut: var(--ink-3);
  --ok:  var(--green);
  --err: var(--red);

  /* sidebar */
  --sidebar:      #0e1725;
  --sidebar-2:    #17243a;
  --sidebar-3:    #1e2f49;
  --sidebar-line: #22334c;
  --sidebar-ink:  #cbd8e8;
  --sidebar-ink-2:#7f92ab;

  /* geometry */
  --r:    9px;
  --r-sm: 6px;
  --r-lg: 14px;
  --r-xl: 20px;
  --r-pill: 999px;

  /* elevation - two-part shadows: a tight contact shadow plus a wide
     ambient one. A single blurred shadow reads as fog; two read as a
     physical object sitting on a surface. */
  --e1: 0 1px 2px rgba(16,24,40,.05), 0 1px 3px rgba(16,24,40,.06);
  --e2: 0 1px 2px rgba(16,24,40,.05), 0 4px 12px rgba(16,24,40,.07);
  --e3: 0 2px 4px rgba(16,24,40,.05), 0 12px 28px rgba(16,24,40,.10);
  --e4: 0 4px 8px rgba(16,24,40,.06), 0 28px 60px rgba(16,24,40,.16);
  --shadow: var(--e2);
  --ring: 0 0 0 3px rgba(31,95,139,.22);
  --glow: 0 0 0 1px rgba(31,95,139,.10);

  /* motion */
  --dur-1: 110ms;
  --dur-2: 190ms;
  --dur-3: 320ms;
  --dur-4: 520ms;
  --ease:      cubic-bezier(.32,.72,0,1);
  --ease-out:  cubic-bezier(.16,1,.3,1);
  --ease-bump: cubic-bezier(.34,1.56,.64,1);

  --skeleton-1: #e9edf2;
  --skeleton-2: #f4f7fa;
  --backdrop: rgba(13,20,32,.42);
}

/* ----------------------------------------------------------------- 2. DARK */
/* Not an inversion. A dark UI needs its own decisions: surfaces lift by
   getting LIGHTER (there is no shadow to see against black), accents move up
   in lightness to stay legible, and tint washes become low-alpha overlays
   rather than pale pastels. */
:root[data-theme="dark"] {
  color-scheme: dark;

  --ink:   #e9eff7;
  --ink-2: #a9b8ca;
  --ink-3: #7d8da1;
  --ink-4: #5e6c7e;

  --bg:        #0b111b;
  --bg-2:      #080d15;
  --surface:   #131c29;
  --surface-2: #182231;
  --surface-3: #1f2b3c;

  --line:   #263349;
  --line-2: #1c2635;
  --line-3: #33435c;

  --primary:       #74b8e8;
  --primary-600:   #8ec8f2;
  --primary-700:   #a8d6f7;
  --primary-solid: #2b7cb8;
  --primary-50:    rgba(116,184,232,.13);
  --primary-100:   rgba(116,184,232,.20);
  --primary-line:  rgba(116,184,232,.30);

  --copper:       #e0965c;
  --copper-600:   #ecae7c;
  --copper-solid: #a85c26;
  --copper-50:    rgba(224,150,92,.13);
  --copper-line:  rgba(224,150,92,.30);

  --green:       #5cc48c;  --green-solid: #2c7a52;
  --green-50:    rgba(92,196,140,.13);
  --green-line:  rgba(92,196,140,.28);
  --red:         #f27a72;  --red-solid:   #a8322a;
  --red-600:     #f79a94;  --red-50:      rgba(242,122,114,.13);
  --red-line:    rgba(242,122,114,.30);
  --amber:       #e6b45c;  --amber-ink:   #ecc27c;
  --amber-solid: #97701c;  --amber-50:    rgba(230,180,92,.13);
  --amber-line:  rgba(230,180,92,.28);
  --violet:      #b191dd;  --violet-50:   rgba(177,145,221,.13);
  --violet-line: rgba(177,145,221,.28);
  --teal:        #4cc0b4;  --teal-50:     rgba(76,192,180,.13);

  --hover:   #1c2736;
  --tint-ok: var(--green-50);

  --sidebar:      #080d15;
  --sidebar-2:    #101a28;
  --sidebar-3:    #17243a;
  --sidebar-line: #1c2939;
  --sidebar-ink:  #c4d2e3;
  --sidebar-ink-2:#6e7f95;

  /* Depth in dark comes from a light top edge, not a drop shadow. */
  --e1: 0 1px 2px rgba(0,0,0,.40), inset 0 1px 0 rgba(255,255,255,.03);
  --e2: 0 2px 6px rgba(0,0,0,.44), inset 0 1px 0 rgba(255,255,255,.04);
  --e3: 0 8px 24px rgba(0,0,0,.52), inset 0 1px 0 rgba(255,255,255,.05);
  --e4: 0 24px 60px rgba(0,0,0,.62), inset 0 1px 0 rgba(255,255,255,.06);
  --shadow: var(--e2);
  --ring: 0 0 0 3px rgba(116,184,232,.30);
  --glow: 0 0 0 1px rgba(116,184,232,.14);

  --skeleton-1: #1b2534;
  --skeleton-2: #23304260;
  --backdrop: rgba(2,6,12,.68);
}

/* Auto mode: follow the OS unless the user has chosen explicitly. The
   attribute selector above always wins because it is more specific. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { color-scheme: dark; }
}

/* ------------------------------------------------------ 3. BASE  +  MOTION */
html { background: var(--bg); }
body {
  background: var(--bg);
  color: var(--ink);
  transition: background-color var(--dur-3) var(--ease),
              color var(--dur-3) var(--ease);
  text-rendering: optimizeLegibility;
}

/* One theme-swap animation instead of forty. Elements opt in by naming the
   properties that actually change, so nothing else is put on the compositor. */
.app, .sidebar, .main, .topbar, .utilbar, .card, .stat, .modal, .view,
.tbl-wrap, .home-card, .hub-header, .filters, .pager, .empty,
input, select, textarea, .btn, .pill, .chip, .toast {
  transition: background-color var(--dur-2) var(--ease),
              border-color var(--dur-2) var(--ease),
              color var(--dur-2) var(--ease),
              box-shadow var(--dur-2) var(--ease);
}

::selection { background: var(--primary-100); color: var(--ink); }

/* Scrollbars that belong to the theme rather than to the OS. */
* { scrollbar-width: thin; scrollbar-color: var(--line-3) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--line-3); border-radius: var(--r-pill);
  border: 3px solid transparent; background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover { background: var(--ink-4); background-clip: content-box; }

/* A visible ring for keyboard users, nothing for mouse users. */
:focus { outline: none; }
:focus-visible {
  outline: none;
  box-shadow: var(--ring);
  border-radius: var(--r-sm);
}

/* --------------------------------------------------- 4. VIEW  TRANSITIONS  */
/* go() wraps the #view swap in document.startViewTransition(), so the browser
   cross-fades the old page into the new one instead of blanking. Everything
   below is progressive: unsupported browsers simply do not animate. */
@view-transition { navigation: auto; }

::view-transition-old(root) {
  animation: cx-vt-out var(--dur-2) var(--ease) both;
}
::view-transition-new(root) {
  animation: cx-vt-in var(--dur-3) var(--ease-out) both;
}
@keyframes cx-vt-out { to { opacity: 0; transform: translateY(-6px) scale(.995); } }
@keyframes cx-vt-in  { from { opacity: 0; transform: translateY(10px) scale(.995); } }

/* The sidebar and topbar must NOT cross-fade with the page - they persist. */
.sidebar, .topbar, .utilbar { view-transition-name: none; }

/* ---------------------------------------------- 5. SURFACES  +  ELEVATION  */
.card, .hub-header, .stat, .home-card, .rem-panel, .task-list, .ap-list,
.fu-list, .trk-card, .inq-card, .cardlist, .qa-wrap {
  border-radius: var(--r-lg);
  box-shadow: var(--e1);
}
.card { border-color: var(--line); overflow: hidden; }
.card-head {
  background: linear-gradient(180deg, var(--surface-2), var(--surface));
  border-bottom: 1px solid var(--line-2);
}
.card-title { letter-spacing: -.01em; }

/* Lift on hover: the shadow grows and the object rises 2px. Kept to
   transform + shadow so it stays on the compositor and never reflows a
   12,000-row list. */
.home-card, .inq-card, .trk-card, .portal-banner, .acc-link {
  transition: transform var(--dur-2) var(--ease-out),
              box-shadow var(--dur-2) var(--ease-out),
              border-color var(--dur-2) var(--ease);
  will-change: transform;
}
.home-card:hover, .inq-card:hover, .trk-card:hover,
.portal-banner:hover, .acc-link:hover {
  transform: translateY(-2px);
  box-shadow: var(--e3);
  border-color: var(--primary-line);
}
.home-card:active, .portal-banner:active { transform: translateY(0); }

/* Stat tiles - the numbers are the point, so give them the display face and
   a coloured spine that says which family they belong to. */
.stat {
  position: relative; overflow: hidden;
  background: linear-gradient(160deg, var(--surface), var(--surface-2));
  border-radius: var(--r-lg);
  border-left-width: 0;          /* app.css draws a flat 3px rule here; the
                                    ::before below replaces it with a gradient */
  padding-left: 19px;
}
.stat::before {
  content: ""; position: absolute; inset: 0 auto 0 0; width: 3px;
  background: linear-gradient(180deg, var(--primary), var(--primary-600));
  opacity: .85;
}
.stat.alt::before  { background: linear-gradient(180deg, var(--copper), var(--copper-600)); }
.stat.pos::before  { background: linear-gradient(180deg, var(--green), var(--green)); }
.stat:hover { box-shadow: var(--e2); }
.stat-value {
  font-family: 'Space Grotesk', Inter, sans-serif;
  font-variant-numeric: tabular-nums;
  letter-spacing: -.02em;
}
.stat-label {
  text-transform: uppercase; letter-spacing: .09em;
  font-size: 10.5px; font-weight: 600; color: var(--ink-3);
}

/* -------------------------------------------------------- 6. SIDEBAR + NAV */
.sidebar {
  background:
    radial-gradient(120% 60% at 0% 0%, var(--sidebar-3) 0%, transparent 55%),
    linear-gradient(180deg, var(--sidebar-2) 0%, var(--sidebar) 60%);
  border-right: 1px solid var(--sidebar-line);
}
.side-brand { border-bottom: 1px solid var(--sidebar-line); }
.brand-c {
  border-radius: 10px;
  box-shadow: 0 2px 10px rgba(31,95,139,.45), inset 0 1px 0 rgba(255,255,255,.18);
}

.nav a {
  position: relative;
  border-radius: var(--r-sm);
  margin: 1px 8px;
  color: var(--sidebar-ink-2);
  transition: background-color var(--dur-1) var(--ease),
              color var(--dur-1) var(--ease),
              padding-left var(--dur-2) var(--ease-out);
}
.nav a:hover {
  background: rgba(255,255,255,.055);
  color: var(--sidebar-ink);
  padding-left: 15px;
}
/* The active item is a tinted slab with a copper rail, not a solid blue
   block - the blue block fought the brand mark for attention. */
.nav a.active {
  background: linear-gradient(90deg, rgba(31,95,139,.34), rgba(31,95,139,.10));
  color: #fff;
}
.nav a.active::before {
  content: ""; position: absolute; left: -8px; top: 6px; bottom: 6px;
  width: 3px; border-radius: 0 3px 3px 0;
  background: var(--copper);
  animation: cx-rail var(--dur-3) var(--ease-bump) both;
}
@keyframes cx-rail { from { transform: scaleY(0); } to { transform: scaleY(1); } }
.nav-group {
  text-transform: uppercase; letter-spacing: .12em;
  font-size: 9.5px; font-weight: 700; color: var(--sidebar-ink-2); opacity: .75;
}
.nav-ico { transition: transform var(--dur-2) var(--ease-bump); }
.nav a:hover .nav-ico { transform: scale(1.12); }
.nav-badge {
  background: var(--red-solid); color: #fff;
  box-shadow: 0 0 0 2px rgba(179,38,30,.22);
}

/* ------------------------------------------------------------- 7. TOP BARS */
/* Sticky, translucent, and it grows a hairline + shadow only once the page
   has actually scrolled - cxScrolled is set by ui.js. */
.utilbar, .topbar {
  position: sticky; top: 0;
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  backdrop-filter: saturate(180%) blur(14px);
}
/* The omnibox results panel lives INSIDE .utilbar. backdrop-filter makes
   .utilbar a stacking context, so the panel's own z-index:60 is scoped to it
   and cannot lift the panel above a sibling - and .topbar is a later sibling
   at the same z-index, translucent, ~70px tall. It painted straight over the
   top of the dropdown, hiding the first group header and its first two or
   three rows behind a frosted blank rectangle.
   Ranking the two bars fixes it for good: the utility bar (and everything
   that drops out of it) sits above the page header. */
.utilbar { top: 0; z-index: 40; }
.topbar  { z-index: 30; }
.topbar {
  top: var(--utilbar-h, 0px);
  /* z-index set above, deliberately below .utilbar */
  border-bottom: 1px solid transparent;
  transition: border-color var(--dur-2) var(--ease),
              box-shadow var(--dur-2) var(--ease);
}
body.cx-scrolled .topbar {
  border-bottom-color: var(--line);
  box-shadow: 0 6px 18px -14px rgba(16,24,40,.5);
}
.page-title { letter-spacing: -.02em; }
.crumb {
  text-transform: uppercase; letter-spacing: .1em;
  font-size: 10px; font-weight: 600; color: var(--ink-3);
}

/* Theme + density controls injected by ui.js into the utility bar. */
.cx-util {
  display: inline-flex; align-items: center; gap: 2px;
  padding: 2px; border-radius: var(--r-pill);
  background: var(--surface-2); border: 1px solid var(--line);
}
.cx-util button {
  display: grid; place-items: center;
  width: 26px; height: 26px; padding: 0;
  border: 0; background: transparent; cursor: pointer;
  border-radius: var(--r-pill); color: var(--ink-3);
  transition: background-color var(--dur-1) var(--ease),
              color var(--dur-1) var(--ease),
              transform var(--dur-2) var(--ease-bump);
}
.cx-util button:hover { background: var(--surface-3); color: var(--ink); }
.cx-util button:active { transform: scale(.9); }
.cx-util button.on { background: var(--primary-50); color: var(--primary); }
.cx-util svg { width: 15px; height: 15px; fill: none; stroke: currentColor;
  stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.cx-util .cx-kbd {
  width: auto; padding: 0 9px; gap: 6px; font-size: 11px;
  font-family: 'IBM Plex Mono', monospace; color: var(--ink-3);
  white-space: nowrap;            /* the "Ctrl K" label was wrapping under the
                                     glyph and pushing the cluster off-baseline */
  grid-auto-flow: column;
}

/* ------------------------------------------------------------- 8. BUTTONS */
.btn {
  border-radius: var(--r-sm);
  font-weight: 550;
  box-shadow: var(--e1);
  transition: background-color var(--dur-1) var(--ease),
              border-color var(--dur-1) var(--ease),
              box-shadow var(--dur-1) var(--ease),
              transform var(--dur-1) var(--ease);
}
.btn:hover { box-shadow: var(--e2); }
.btn:active { transform: translateY(1px) scale(.985); box-shadow: var(--e1); }
.btn:disabled { box-shadow: none; opacity: .5; }

.btn-primary {
  background: linear-gradient(180deg,
              color-mix(in srgb, var(--primary-solid) 88%, #fff),
              var(--primary-solid));
  border-color: var(--primary-solid);
  color: #fff;
  box-shadow: var(--e1), inset 0 1px 0 rgba(255,255,255,.16);
}
.btn-primary:hover {
  background: linear-gradient(180deg, var(--primary-solid), var(--primary-600));
  border-color: var(--primary-600);
  box-shadow: var(--e2), inset 0 1px 0 rgba(255,255,255,.16);
}
.btn-danger {
  background: linear-gradient(180deg,
              color-mix(in srgb, var(--red-solid) 88%, #fff), var(--red-solid));
  border-color: var(--red-solid); color: #fff;
}
.btn-ghost { box-shadow: none; }
.btn-ghost:hover { background: var(--hover); box-shadow: none; }
.btn-icon, .icon-btn {
  border-radius: var(--r-sm);
  transition: background-color var(--dur-1) var(--ease),
              color var(--dur-1) var(--ease),
              transform var(--dur-2) var(--ease-bump);
}
.btn-icon:hover, .icon-btn:hover { transform: scale(1.08); }

/* ---------------------------------------------------------- 9. FORM FIELDS */
input, select, textarea {
  border-radius: var(--r-sm);
  transition: border-color var(--dur-1) var(--ease),
              box-shadow var(--dur-1) var(--ease),
              background-color var(--dur-1) var(--ease);
}
input:hover, select:hover, textarea:hover { border-color: var(--line-3); }
input:focus, select:focus, textarea:focus,
.filters input:focus, .filters select:focus,
.cl-grid select:focus, .cl-grid input:focus, .cl-full textarea:focus,
.coa-grid input:focus, .qed-spec:focus {
  border-color: var(--primary);
  box-shadow: var(--ring);
  outline: none;
}
.fld > span, .lbl {
  text-transform: uppercase; letter-spacing: .07em;
  font-size: 10.5px; font-weight: 600; color: var(--ink-3);
}
.filters {
  background: var(--surface-2);
  border: 1px solid var(--line-2);
  border-radius: var(--r-lg);
}

/* ------------------------------------------------------------- 10. TABLES */
.tbl-wrap {
  border-radius: var(--r-lg);
  border: 1px solid var(--line);
  box-shadow: var(--e1);
  overflow: auto;
  /* app.css sets overflow-x:auto, which already makes this the scrollport for
     BOTH axes (per spec, a non-visible value on one axis computes the other to
     auto). Without a height it never scrolls vertically, so position:sticky on
     the header would resolve against a box that never moves. Capping the
     height turns it into a real scroller: the column names stay put and the
     filters and pager stay on screen with them. */
  max-height: min(74vh, 860px);
}
/* Modal bodies and embedded panels do their own scrolling - a second nested
   scroller there traps the wheel. */
.modal-body .tbl-wrap, .cardlist-scroll .tbl-wrap,
#inqScroll .tbl-wrap, #inqlScroll .tbl-wrap { max-height: none; }
/* Sticky header: on a 100-row page the column names used to scroll away and
   the operator had to guess which column they were reading. */
.tbl-wrap thead th {
  position: sticky; top: 0; z-index: 2;
  background: var(--surface-2);
  border-bottom: 1px solid var(--line);
  text-transform: uppercase; letter-spacing: .06em;
  font-size: 10.5px; font-weight: 650; color: var(--ink-3);
  white-space: nowrap;
}
.tbl-wrap tbody tr {
  transition: background-color var(--dur-1) linear;
}
.tbl-wrap tbody tr:hover { background: var(--hover); }
.tbl-wrap tbody tr[data-id] { cursor: pointer; }
/* Left accent on the hovered row - cheaper to read than a full-row tint at
   speed, and it survives the zebra. */
.tbl-wrap tbody tr td:first-child { position: relative; }
.tbl-wrap tbody tr:hover td:first-child::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 2px;
  background: var(--primary);
}
.tbl-wrap td { border-bottom: 1px solid var(--line-2); }
.num, .mono { font-variant-numeric: tabular-nums; }

/* A row that was just written flashes green, so a save is visible without
   hunting for the toast. */
@keyframes cx-flash {
  0%   { background: var(--green-50); box-shadow: inset 2px 0 0 var(--green); }
  100% { background: transparent;     box-shadow: inset 2px 0 0 transparent; }
}
tr.cx-saved, .cx-saved { animation: cx-flash 1.6s var(--ease-out) both; }

.pager {
  border-radius: var(--r-lg);
  background: var(--surface-2);
  border: 1px solid var(--line-2);
}
.empty {
  border-radius: var(--r-lg);
  border: 1px dashed var(--line-3);
  background: var(--surface-2);
}

/* --------------------------------------------------- 11. PILLS  +  CHIPS  */
.pill, .stage-pill, .fu-pill, .hub-badge, .inq-chip, .chip, .oc-chip {
  border-radius: var(--r-pill);
  font-weight: 600;
  letter-spacing: .02em;
}
.pill {
  padding: 2px 9px; font-size: 10.5px;
  text-transform: uppercase; letter-spacing: .05em;
  border: 1px solid transparent;
}
.pill-grey  { background: var(--surface-3);  color: var(--ink-2); border-color: var(--line); }
.pill-blue  { background: var(--primary-50); color: var(--primary); border-color: var(--primary-line); }
.pill-green { background: var(--green-50);   color: var(--green);   border-color: var(--green-line); }
.pill-amber { background: var(--amber-50);   color: var(--amber);   border-color: var(--amber-line); }
.pill-red   { background: var(--red-50);     color: var(--red);     border-color: var(--red-line); }

.chip, .oc-chip, .inq-chip, .tab, .hub-tab, .tabs button {
  transition: background-color var(--dur-1) var(--ease),
              color var(--dur-1) var(--ease),
              border-color var(--dur-1) var(--ease),
              transform var(--dur-1) var(--ease);
}
.chip:active, .oc-chip:active { transform: scale(.96); }
.chip.on, .oc-chip.on, .inq-chip.is-on,
.rem-type.active, .rem-chip.active {
  background: var(--primary-solid); border-color: var(--primary-solid); color: #fff;
  box-shadow: var(--e1);
}

/* Tabs get a sliding underline rather than a hard swap. */
.hub-tab, .tab, .tabs button { position: relative; }
.hub-tab::after, .tab::after, .tabs button::after {
  content: ""; position: absolute; left: 10px; right: 10px; bottom: -1px;
  height: 2px; border-radius: 2px; background: var(--primary);
  transform: scaleX(0); transform-origin: center;
  transition: transform var(--dur-2) var(--ease-out);
}
.hub-tab.active::after, .tab.is-on::after,
.tabs button.active::after, .tabs button.on::after { transform: scaleX(1); }

/* --------------------------------------------------------------- 12. MODAL */
.modal-back {
  background: var(--backdrop);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  opacity: 0;
  transition: opacity var(--dur-2) var(--ease);
}
.modal-back.cx-in { opacity: 1; }
/* This file loads after app.css, so this block - not app.css - is what
   actually animates the panel. It was still running the centred-card
   entrance: translateY + scale, rounded corners and a border. That silently
   overrode the drawer's slide-in and put the card look back.
   Now it animates the drawer: in from the right edge, square, no border. */
.modal {
  border-radius: 0;
  box-shadow: var(--e4);
  border: 0;
  opacity: 1;
  transform: translateX(100%);
  transition: transform var(--dur-3) var(--ease-out);
}
.modal-back.cx-in .modal { opacity: 1; transform: none; }
.modal-head {
  background: var(--surface);
  border-bottom: 1px solid var(--line-2);
}
.modal-head h2 { letter-spacing: -.015em; }
.modal-foot {
  background: var(--surface-2);
  border-top: 1px solid var(--line-2);
}

/* -------------------------------------------------------------- 13. TOASTS */
.toasts { z-index: 90; }
.toast {
  position: relative; overflow: hidden;
  display: flex; align-items: flex-start; gap: 10px;
  border-radius: var(--r-lg);
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--e3);
  animation: cx-toast-in var(--dur-3) var(--ease-bump) both;
}
.toast.cx-out { animation: cx-toast-out var(--dur-2) var(--ease) both; }
@keyframes cx-toast-in  { from { opacity:0; transform: translateX(18px) scale(.96); } }
@keyframes cx-toast-out { to   { opacity:0; transform: translateX(18px) scale(.96); } }
.toast::before {
  content: ""; position: absolute; inset: 0 auto 0 0; width: 3px;
  background: var(--primary);
}
.toast.ok::before  { background: var(--green); }
.toast.err::before { background: var(--red); }
.toast.warn::before{ background: var(--amber-solid); }
.cx-toast-ico { flex: none; margin-top: 1px; width: 16px; height: 16px;
  fill: none; stroke: currentColor; stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round; color: var(--primary); }
.toast.ok   .cx-toast-ico { color: var(--green); }
.toast.err  .cx-toast-ico { color: var(--red); }
.toast.warn .cx-toast-ico { color: var(--amber-solid); }
.cx-toast-msg { flex: 1; min-width: 0; }
.cx-toast-undo {
  flex: none; border: 0; background: transparent; cursor: pointer;
  font: inherit; font-weight: 650; font-size: 12px;
  color: var(--primary); padding: 0 2px; border-radius: var(--r-sm);
}
.cx-toast-undo:hover { text-decoration: underline; }
.cx-toast-bar {
  position: absolute; left: 0; bottom: 0; height: 2px;
  background: var(--primary); opacity: .45;
  animation: cx-toast-bar linear both;
}
@keyframes cx-toast-bar { from { width: 100%; } to { width: 0; } }

/* ------------------------------------------- 14. SKELETONS  +  PROGRESS  */
/* A page that goes blank feels broken; a page that shows the SHAPE of what
   is coming feels fast even at the same latency. */
.cx-skel {
  border-radius: var(--r-sm);
  background: linear-gradient(90deg,
              var(--skeleton-1) 25%, var(--skeleton-2) 37%, var(--skeleton-1) 63%);
  background-size: 400% 100%;
  animation: cx-shimmer 1.4s ease-in-out infinite;
}
@keyframes cx-shimmer { from { background-position: 100% 0; } to { background-position: 0 0; } }
.cx-skel-table { border: 1px solid var(--line); border-radius: var(--r-lg);
  overflow: hidden; background: var(--surface); box-shadow: var(--e1); }
.cx-skel-row { display: flex; gap: 14px; align-items: center;
  padding: 12px 14px; border-bottom: 1px solid var(--line-2); }
.cx-skel-row:last-child { border-bottom: 0; }
.cx-skel-row .cx-skel { height: 10px; }
.cx-skel-head { background: var(--surface-2); }
.cx-skel-head .cx-skel { height: 8px; opacity: .7; }
.cx-skel-cards { display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }
.cx-skel-card { height: 92px; border-radius: var(--r-lg); }

/* Route progress bar - the 3px answer to "did my click register". */
#cxProgress {
  position: fixed; top: 0; left: 0; height: 2.5px; width: 0;
  z-index: 200; pointer-events: none; opacity: 0;
  background: linear-gradient(90deg, var(--primary), var(--copper));
  box-shadow: 0 0 10px var(--primary);
  transition: width var(--dur-3) var(--ease-out), opacity var(--dur-2) var(--ease);
}
#cxProgress.on { opacity: 1; }

/* The original spinner, restyled to match. */
.spinner { border-color: var(--line); border-top-color: var(--primary); }
.loading { color: var(--ink-3); }

/* ------------------------------------------------------ 15. COMMAND PALETTE */
#cxPalette {
  position: fixed; inset: 0; z-index: 120;
  display: grid; place-items: start center;
  padding-top: 12vh;
  background: var(--backdrop);
  -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
  opacity: 0; transition: opacity var(--dur-2) var(--ease);
}
#cxPalette.cx-in { opacity: 1; }
#cxPalette.hidden { display: none; }
.cx-pal-card {
  width: min(620px, calc(100vw - 32px));
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
  box-shadow: var(--e4);
  overflow: hidden;
  transform: translateY(-12px) scale(.98);
  transition: transform var(--dur-3) var(--ease-out);
}
#cxPalette.cx-in .cx-pal-card { transform: none; }
.cx-pal-in {
  display: flex; align-items: center; gap: 10px;
  padding: 14px 16px; border-bottom: 1px solid var(--line-2);
}
.cx-pal-in svg { width: 17px; height: 17px; flex: none; fill: none;
  stroke: var(--ink-3); stroke-width: 2; stroke-linecap: round; }
.cx-pal-in input {
  flex: 1; border: 0; background: transparent; outline: none;
  font: inherit; font-size: 15px; color: var(--ink);
}
.cx-pal-in input::placeholder { color: var(--ink-4); }
.cx-pal-esc {
  font-family: 'IBM Plex Mono', monospace; font-size: 10px;
  color: var(--ink-3); border: 1px solid var(--line);
  border-radius: 5px; padding: 2px 6px; background: var(--surface-2);
}
.cx-pal-list { max-height: min(52vh, 420px); overflow: auto; padding: 6px; }
.cx-pal-grp {
  padding: 8px 12px 4px; font-size: 9.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .12em; color: var(--ink-4);
}
.cx-pal-row {
  display: flex; align-items: center; gap: 11px;
  padding: 9px 12px; border-radius: var(--r-sm); cursor: pointer;
  color: var(--ink-2);
}
.cx-pal-row .cx-pal-ico {
  width: 26px; height: 26px; flex: none; display: grid; place-items: center;
  border-radius: 7px; background: var(--surface-3); color: var(--primary);
}
.cx-pal-row .cx-pal-ico svg { width: 14px; height: 14px; fill: none;
  stroke: currentColor; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
.cx-pal-row strong { font-weight: 600; color: var(--ink); }
.cx-pal-row small { color: var(--ink-4); margin-left: auto; font-size: 11px; }
.cx-pal-row.sel { background: var(--primary-50); color: var(--ink); }
.cx-pal-row.sel .cx-pal-ico { background: var(--primary); color: #fff; }
.cx-pal-empty { padding: 26px 16px; text-align: center; color: var(--ink-3); font-size: 13px; }

/* -------------------------------------------------------------- 16. CHARTS */
/* Rendered by charts.js as inline SVG, so everything below inherits the theme
   tokens automatically - no chart library to re-skin on a theme swap. */
.cx-chart { display: block; width: 100%; overflow: visible; }
.cx-chart .cx-grid  { stroke: var(--line-2); stroke-width: 1; }
.cx-chart .cx-axis  { fill: var(--ink-4); font-size: 10px;
  font-family: 'IBM Plex Mono', monospace; }
.cx-chart .cx-bar   { transition: opacity var(--dur-1) var(--ease); }
.cx-chart .cx-bar:hover { opacity: .78; }
.cx-chart .cx-line  { fill: none; stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round; }
.cx-chart .cx-dot   { stroke: var(--surface); stroke-width: 2; }
.cx-chart .cx-lbl   { fill: var(--ink-2); font-size: 11px; font-weight: 600; }
/* Draw-in: the line traces itself, the bars grow from the baseline. */
.cx-chart .cx-anim-line {
  stroke-dasharray: var(--len); stroke-dashoffset: var(--len);
  animation: cx-draw 900ms var(--ease-out) forwards;
}
@keyframes cx-draw { to { stroke-dashoffset: 0; } }
.cx-chart .cx-anim-bar { transform-origin: bottom;
  animation: cx-grow 520ms var(--ease-out) both; }
@keyframes cx-grow { from { transform: scaleY(0); } }
.cx-chart .cx-anim-arc { animation: cx-arc 900ms var(--ease-out) forwards; }
@keyframes cx-arc { from { stroke-dashoffset: var(--circ); } }
.cx-spark { display: inline-block; vertical-align: middle; }
.cx-legend { display: flex; flex-wrap: wrap; gap: 6px 16px; margin-top: 10px; }
.cx-legend span { display: inline-flex; align-items: center; gap: 6px;
  font-size: 11.5px; color: var(--ink-2); }
.cx-legend i { width: 9px; height: 9px; border-radius: 3px; flex: none; }

/* --------------------------------------- 17. DENSITY / MOTION / PRINT ---- */
/* Compact drops row height ~22% for the operators who live in the invoice
   list all day; cozy is the default for everyone else. */
:root[data-density="compact"] { font-size: 12.5px; }
:root[data-density="compact"] .tbl-wrap td { padding-top: 5px; padding-bottom: 5px; }
:root[data-density="compact"] .tbl-wrap thead th { padding-top: 6px; padding-bottom: 6px; }
:root[data-density="compact"] .card-body { padding: 12px 14px; }
:root[data-density="compact"] .stat { padding: 10px 13px; }
:root[data-density="compact"] .nav a { padding-top: 6px; padding-bottom: 6px; }

/* Honour the OS setting AND the in-app switch. Every animation above is
   decoration; none of it carries information that is lost when removed. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important; scroll-behavior: auto !important;
  }
}
:root[data-motion="off"] *, :root[data-motion="off"] *::before, :root[data-motion="off"] *::after {
  animation-duration: .01ms !important; animation-iteration-count: 1 !important;
  transition-duration: .01ms !important;
}
:root[data-motion="off"] .home-card:hover, :root[data-motion="off"] .inq-card:hover,
:root[data-motion="off"] .portal-banner:hover { transform: none; }

/* ---------------------------------------------------------------------------
   PRINT
   ---------------------------------------------------------------------------
   The app is a fixed-height grid: html/body are 100%, .app is a grid at 100%,
   .main hides its overflow and .view is the scroller. A printer honours all of
   that literally, so the page box came out one screen tall and everything
   below the fold was discarded - a tour expense claim printed as "1 of 1" with
   two thirds of its rows missing, and the approval chain lost its last step to
   .rail's horizontal scroll.

   So the first job here is not styling, it is UNWINDING that shell. Every
   height and every overflow between <html> and the record has to be released
   or the browser has nothing to paginate.

   The second job is that Chrome prints with "Background graphics" OFF by
   default, which erases every filled pill - approved, pending and rejected all
   come out as white boxes. State is re-expressed with borders so the sheet
   still reads in monochrome.
   --------------------------------------------------------------------------- */
.print-only { display: none; }

@media print {
  @page { size: A4 portrait; margin: 12mm 10mm; }

  /* 1. unwind the fixed-height shell - the actual cause */
  html, body { height: auto !important; overflow: visible !important; }
  .app       { display: block !important; height: auto !important; }
  .main      { display: block !important; overflow: visible !important; margin: 0 !important; }
  .view      { flex: none !important; overflow: visible !important;
               height: auto !important; padding: 0 !important; }

  /* 2. unwind every inner scroller */
  .card, .tbl-wrap, .rail, .hist, .cardlist-scroll,
  .rem-panel-b, .pp-list, .modal-body,
  #inqScroll, #inqlScroll { overflow: visible !important; max-height: none !important; }

  /* 3. chrome off */
  .sidebar, .utilbar, .nav-backdrop, .page-actions, .pager, .filters,
  #cxProgress, #cxPalette, .toasts, .modal-back { display: none !important; }
  .topbar { padding: 0 0 10px !important; border-bottom: 1px solid #999 !important; }

  /* 4. controls are noise on a voucher */
  table .btn, table button, .btn-icon { display: none !important; }

  /* 5. mono-legible with background graphics off */
  :root { --surface: #fff; --surface-2: #fff; --bg: #fff; --ink: #000; }
  .card, .tbl-wrap, .stat { box-shadow: none !important; border-color: #bbb !important; }
  .pill { border: 1px solid #666 !important; background: none !important; color: #000 !important; }
  .rail { display: flex !important; }

  /* 6. paginate sanely.
     `.card { break-inside: avoid-page }` was wrong: a card taller than the
     space left on the sheet gets pushed whole to the next one, so every card
     started its own page. Only rows and stat tiles are small enough to keep
     intact; a card is allowed to split, with its heading kept with the first
     rows so a title never strands at the foot of a sheet. */
  thead     { display: table-header-group; }
  tr, .stat { break-inside: avoid; }
  .card     { break-inside: auto; }
  .card-head, .stats { break-after: avoid; }

  /* 7. tour voucher: the plan and the trip write-up are not what Accounts
     are approving - they are approving the money. */
  #tourItinCard, #tvsHost { display: none !important; }

  .print-only { display: block; }
  .sign-strip { display: flex !important; gap: 14px; margin-top: 26px;
                break-inside: avoid; }
  .sign-box   { flex: 1; font-size: 10.5px; }
  .sign-box span   { display: block; text-transform: uppercase; letter-spacing: .08em;
                     color: #444; }
  .sign-box strong { display: block; font-size: 12px; margin-top: 2px; }
  .sign-box i      { display: block; border-bottom: 1px solid #666; margin-top: 30px; }
}

/* ===========================================================================
   18. LOGIN  -  the one screen with a fixed identity
   ---------------------------------------------------------------------------
   The brand panel (black ground, corporate red, the che/te/x lockup) is a
   piece of corporate identity, not a themeable surface, so it is excluded
   from tokenisation and stays exactly as it is in both modes. Only the FORM
   side follows the theme, and it is given the motion the rest of the app now
   has - a staggered entrance instead of a hard paint.
   =========================================================================== */
.login-panel { background: var(--surface-2); }
:root[data-theme="dark"] .login-panel { background: var(--bg); }
:root[data-theme="dark"] .login-wordmark,
:root[data-theme="dark"] .login-h1 { color: var(--ink); }
:root[data-theme="dark"] .login-lead { color: var(--ink-2); }
:root[data-theme="dark"] .login-card input[type=email],
:root[data-theme="dark"] .login-card input[type=password] {
  background: var(--surface); color: var(--ink); border-color: var(--line);
}
:root[data-theme="dark"] .login-foot { color: var(--ink-3); }

.login-card > * { animation: cx-rise 620ms var(--ease-out) both; }
.login-card > *:nth-child(1) { animation-delay: 40ms; }
.login-card > *:nth-child(2) { animation-delay: 90ms; }
.login-card > *:nth-child(3) { animation-delay: 130ms; }
.login-card > *:nth-child(4) { animation-delay: 170ms; }
.login-card > *:nth-child(5) { animation-delay: 210ms; }
.login-card > *:nth-child(6) { animation-delay: 250ms; }
@keyframes cx-rise { from { opacity: 0; transform: translateY(14px); } }

.login-brand-inner { animation: cx-fade 900ms var(--ease-out) both; }
@keyframes cx-fade { from { opacity: 0; } }

.login-card input[type=email], .login-card input[type=password] {
  border-radius: var(--r-sm);
  transition: border-color var(--dur-1) var(--ease), box-shadow var(--dur-1) var(--ease);
}
.login-card .btn-primary {
  border-radius: var(--r-sm);
  transition: transform var(--dur-1) var(--ease), box-shadow var(--dur-2) var(--ease),
              filter var(--dur-1) var(--ease);
}
.login-card .btn-primary:active { transform: translateY(1px) scale(.99); }

/* ===========================================================================
   19. FIELD PORTAL  -  phone-first surfaces
   ---------------------------------------------------------------------------
   field.html is used one-handed, outdoors, on a phone. Bigger tap targets,
   a sheet that slides from the bottom edge rather than appearing in the
   middle, and safe-area padding so the tab bar clears a notch.
   =========================================================================== */
.tabbar { padding-bottom: max(6px, env(safe-area-inset-bottom)); }
.bottom-dock {
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
  border-top: 1px solid var(--line);
}
.tab-btn {
  transition: color var(--dur-1) var(--ease), transform var(--dur-2) var(--ease-bump);
  border-radius: var(--r);
}
.tab-btn:active { transform: scale(.93); }
.tab-btn.on { color: var(--primary); }
.sheet { background: var(--backdrop); -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px); }
.sheet-card {
  border-radius: var(--r-xl) var(--r-xl) 0 0;
  box-shadow: var(--e4);
  padding-bottom: max(14px, env(safe-area-inset-bottom));
  animation: cx-sheet var(--dur-3) var(--ease-out) both;
}
@keyframes cx-sheet { from { transform: translateY(100%); } }
.sheet-card::before {
  content: ""; display: block; width: 38px; height: 4px; margin: 0 auto 10px;
  border-radius: var(--r-pill); background: var(--line-3);
}

/* ===========================================================================
   20. BRAND  -  AiBLE
   ---------------------------------------------------------------------------
   AiBLE is the platform, CRM is this frontend, Chemtex Speciality Limited is
   the company. Three different things, so they get three different weights:
   the corporate lockup keeps the login's left panel, AiBLE takes the wordmark
   above the form, and the company name appears only where it is legally
   required (COA certificates, quotation and proforma PDFs) - which is why
   none of those documents were touched by the rename.

   .login-wordmark was letter-spaced for the all-lowercase "chemtex". "AiBLE"
   is mixed case with a narrow i, so it needs its own tracking or the A and i
   collide.
   =========================================================================== */
.login-wordmark {
  letter-spacing: -.015em;
  font-weight: 700;
}
:root[data-theme="dark"] .login-wordmark { color: var(--ink); }

/* Slot for the AiBLE mark when it arrives. Sized so a square or a wide
   lockup both sit correctly without touching the markup. */
.brand-cx, #brandLogo {
  object-fit: contain;
  max-height: 34px;
  transition: opacity var(--dur-2) var(--ease);
}
.side-brand #brandName { letter-spacing: -.01em; }
.side-brand #brandApp {
  color: var(--copper);
  text-transform: uppercase;
  letter-spacing: .16em;
}

/* The utility bar must never push the signed-in user off the edge: the
   controls hold their size, the identity block truncates instead. */
/* The utility bar gained three controls, so it needs a wrap opportunity on a
   narrow viewport and a ceiling on the one item that can genuinely run away
   with the row - a 40-character work email. Everything else keeps its size. */
.utilbar  { min-width: 0; flex-wrap: wrap; row-gap: 6px; }
.top-meta { min-width: 0; flex-wrap: wrap; justify-content: flex-end; row-gap: 6px; }
#cxUtil   { flex: none; }
.tm-av    { flex: none; }
.tm-clock { white-space: nowrap; flex: none; }
.tm-em    { display: block; max-width: 240px; overflow: hidden;
            text-overflow: ellipsis; white-space: nowrap; }

/* HOTFIX 22 Aug 2026, caught on the live login screen.
   .login-card .fld>span, .login-remember and .login-row are set to --cx-ink
   (#0d0d0f) inside the protected login block, which is correct on the light
   form panel and invisible on the dark one. The protected block is not
   tokenised by design, so the dark case has to be stated explicitly here. */
:root[data-theme="dark"] .login-card .fld > span { color: var(--ink-2); }
:root[data-theme="dark"] .login-remember,
:root[data-theme="dark"] .login-remember span,
:root[data-theme="dark"] .login-row { color: var(--ink-2); }
:root[data-theme="dark"] .login-card input[type=email]::placeholder,
:root[data-theme="dark"] .login-card input[type=password]::placeholder { color: var(--ink-4); }
:root[data-theme="dark"] .login-remember input { accent-color: var(--cx-red); }
:root[data-theme="dark"] .login-foot,
:root[data-theme="dark"] .login-foot span { color: var(--ink-3); }
:root[data-theme="dark"] .form-msg { color: var(--ink-2); }


/* ============================================================
   18  BUSY BUTTONS
   ------------------------------------------------------------
   The progress bar answers "is the app working". It does not
   answer "which of these three buttons did I press", which is
   the live question on a modal footer carrying Save, Save & New
   and Convert. ui.js adds .cx-busy on click and takes it off
   when the modal closes or #view repaints.

   The width is pinned inline before the class lands, because a
   button whose label goes transparent would otherwise collapse
   to the size of its spinner and shuffle its neighbours.
   ============================================================ */
.btn.cx-busy {
  position: relative;
  color: transparent !important;
  pointer-events: none;
  opacity: .85;
}
.btn.cx-busy::after {
  content: "";
  position: absolute; inset: 0; margin: auto;
  width: 14px; height: 14px;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  color: #fff;
  animation: cx-spin .6s linear infinite;
}
/* Ghost and light buttons sit on a pale surface, so a white
   spinner would be invisible on them. */
.btn-ghost.cx-busy::after,
.btn-light.cx-busy::after,
.btn:not([class*="btn-primary"]):not([class*="btn-danger"]).cx-busy::after {
  color: var(--ink-2);
}
@keyframes cx-spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  .btn.cx-busy::after { animation: none; border-top-color: currentColor; opacity: .5; }
}
[data-motion="off"] .btn.cx-busy::after {
  animation: none; border-top-color: currentColor; opacity: .5;
}

/* ============================================================
   19  USERS & ACCESS FILTER BAR
   ------------------------------------------------------------
   69 seats rendered as one flat list. The filters answer the two
   questions actually asked of this page - "who is on this
   profile" and "who reports into this desk" - without a round
   trip, since the arrays are already loaded.
   ============================================================ */
.ua-filters {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--line-2);
}
.ua-filters input[type="search"] { min-width: 220px; flex: 1 1 220px; }
.ua-filters select { flex: 0 1 auto; max-width: 260px; }
.ua-filters #uaCount { margin-left: auto; white-space: nowrap; }
@media (max-width: 640px) {
  .ua-filters select, .ua-filters input[type="search"] { flex: 1 1 100%; max-width: none; }
  .ua-filters #uaCount { margin-left: 0; }
}

/* ============================================================
   20  COA ITEM PICKER
   ------------------------------------------------------------
   The order-first wizard lists a sales order's product lines so
   the issuer ticks which ones need a certificate. A line with no
   specification in the product master cannot be certified, so it
   is dimmed and its checkbox disabled rather than hidden - the
   issuer needs to SEE that the machine and the filter media were
   considered and skipped, not silently dropped.
   ============================================================ */
.coa-items { width: 100%; margin-top: 4px; }
.coa-items th { font-size: 11px; text-transform: uppercase; letter-spacing: .05em; }
.coa-items td, .coa-items th { padding: 6px 8px; }
.coa-items td:first-child, .coa-items th:first-child { width: 28px; text-align: center; }
.coa-items tr.coa-nospec { opacity: .55; }
.coa-items tr.coa-nospec td { font-style: italic; }
.coa-items small.mono { color: var(--ink-3); margin-left: 6px; }
.coa-items a { color: var(--primary); text-decoration: none; }
.coa-items a:hover { text-decoration: underline; }

/* ============================================================
   21  MULTI-ROW PLAN VISITS (desktop)
   ------------------------------------------------------------
   A tour is six or eight calls. Tour, agent, city and state are
   entered once at the top; each card below carries only what
   differs between stops.
   ============================================================ */
.pv-row {
  border: 1px solid var(--line); border-radius: 10px;
  padding: 10px 12px 4px; margin: 10px 0; background: var(--surface);
}
.pv-row-h { display: flex; align-items: center; justify-content: space-between; margin-bottom: 4px; }
.pv-n { font-size: 10.5px; font-weight: 700; letter-spacing: .08em;
        text-transform: uppercase; color: var(--ink-3); }
.pv-rm { border: 0; background: transparent; font-size: 20px; line-height: 1;
         color: var(--ink-3); padding: 0 4px; cursor: pointer; }
.pv-rm:hover { color: var(--red); }
.pv-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 12px; }
.pv-f { display: flex; flex-direction: column; gap: 3px; position: relative; }
.pv-f > span { font-size: 11px; color: var(--ink-2); }
.pv-wide { grid-column: 1 / -1; }
.pv-row .picker-list { max-height: 190px; overflow: auto; }
#pvAdd { width: 100%; margin-top: 2px; }
@media (max-width: 640px) { .pv-grid { grid-template-columns: 1fr; } }

/* ============================================================
   22  CALL RECORDING PANEL
   ------------------------------------------------------------
   Two layers on a recorded call. activities.notes carries the
   summary a rep reads; the diarized conversation lives on
   core.call_recordings and is fetched only when the call is
   opened, then collapsed. A 19,000-character exchange must not
   bury the form it is attached to.
   ============================================================ */
.rec-panel { margin-top: 14px; padding: 12px 14px; border: 1px solid var(--line);
  border-radius: 10px; background: var(--surface-2, var(--surface)); }
.rec-head { display: flex; justify-content: space-between; align-items: baseline;
  gap: 12px; flex-wrap: wrap; margin-bottom: 6px; }
.rec-next { margin: 6px 0 10px; font-size: 13px; }
.rec-more > summary { cursor: pointer; font-size: 12.5px; color: var(--primary);
  user-select: none; padding: 4px 0; }
.rec-turns { max-height: 340px; overflow: auto; margin-top: 8px;
  border-top: 1px solid var(--line-2); padding-top: 8px; }
.rec-turn { display: grid; grid-template-columns: 74px 1fr; gap: 10px;
  padding: 3px 0; font-size: 13px; line-height: 1.45; }
.rec-who { font-size: 11px; text-transform: uppercase; letter-spacing: .04em;
  color: var(--ink-3); padding-top: 2px; }
.rec-turn.rec-us .rec-who { color: var(--primary); font-weight: 600; }
.rec-turn.rec-us .rec-txt { color: var(--ink-2); }
.rec-flat { max-height: 340px; overflow: auto; margin-top: 8px; font-size: 13px;
  line-height: 1.6; white-space: pre-wrap; }
.rec-dot { color: var(--primary); font-size: 10px; }

/* ============================================================
   23  RECORDED CALLS - TAGGING SHEET
   ------------------------------------------------------------
   The queue is mostly NEW business: calls to numbers nobody has
   ever saved. The sheet puts the transcript next to the pickers
   so the tagger can read who it was before deciding.
   ============================================================ */
.rec-tagwrap { display: flex; flex-direction: column; gap: 12px; }
.rec-tagbox { padding: 12px 14px; border: 1px solid var(--line);
  border-radius: 10px; background: var(--surface-2, var(--surface)); }
.rec-tagbox .f-lab { display: block; font-size: 11px; text-transform: uppercase;
  letter-spacing: .05em; color: var(--ink-3); margin-bottom: 8px; }
.rec-all { display: flex; align-items: flex-start; gap: 8px; margin-top: 10px;
  font-size: 12.5px; color: var(--ink-2); }
.rec-all input { margin-top: 2px; }
.rec-tagbox .picker-list { max-height: 180px; overflow: auto; }

/* Filed-against block on a recorded call. A rep may see the NAME of a
   customer outside their division so they can tell us the auto-tag is wrong,
   without gaining any access to the record itself. */
.rec-filedbox { padding: 10px 14px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--surface-2, var(--surface)); display: flex; align-items: center;
  justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.rec-filed-lab { font-size: 11px; text-transform: uppercase; letter-spacing: .05em;
  color: var(--ink-3); }
.rec-oos { font-size: 10.5px; padding: 1px 6px; border-radius: 999px;
  background: var(--warn-bg, #fdf1dc); color: var(--warn, #8a5a00); white-space: nowrap; }
.rec-flagged { margin: 0; font-size: 12.5px; color: var(--warn, #8a5a00); }

/* A call tagged to an inquiry rather than a customer - worth marking, because
   the follow-up belongs to a different part of the funnel. */
.rec-kind { font-size: 10.5px; padding: 1px 6px; border-radius: 999px;
  background: rgba(31,95,139,.10); color: var(--primary); white-space: nowrap; }

/* ── Transcribed calls: two facts per cell, on their own lines ──
   "Tui Bon Natural Pvt Ltd, Aizawl6909100959" and "Sreya Mondalalso the
   owner" - the <small> ran straight on from the <strong> because nothing made
   it a block. The name and the number, and the caller and the owner, are
   separate facts and read as one string otherwise.

   Scoped to #recBody rather than a table class: tableHtml() emits a bare
   <table> and takes no class argument, so the container is the hook. */
#recBody td > strong { display: block; line-height: 1.35; }
#recBody td small { display: block; margin-top: 2px; font-size: 11.5px;
  color: var(--ink-3); line-height: 1.3; }
#recBody td .rec-kind,
#recBody td .rec-oos { display: inline-block; margin-left: 6px; margin-top: 0; }
#recBody td { padding-top: 9px; padding-bottom: 9px; vertical-align: top; }

/* ── Call signals ──────────────────────────────────────────────
   What a conversation CONTAINED, not how it felt. Each chip is a
   fact checkable by opening the call, which is why they read as
   plain words rather than scores. */
.sig { display: inline-block; font-size: 10.5px; line-height: 1.5;
  padding: 1px 7px; margin: 1px 4px 1px 0; border-radius: 999px;
  background: rgba(31,95,139,.09); color: var(--primary); white-space: nowrap; }
.sig-no { background: rgba(158,27,19,.10); color: #9e1b13; }
.sig-more { background: transparent; color: var(--ink-3); }
.sig-hot { display: inline-block; font-size: 10.5px; font-weight: 700;
  padding: 1px 8px; margin: 1px 5px 1px 0; border-radius: 999px;
  background: #2f7d5c; color: #fff; white-space: nowrap; }
.rec-brief { margin: 12px 0; padding: 12px 14px; border: 1px solid var(--line);
  border-radius: 10px; background: var(--surface-2, var(--surface)); }
.rec-sigs { margin-bottom: 8px; }
.rec-summary { margin: 0; font-size: 13.5px; line-height: 1.55; }
.rec-next-box { margin-top: 10px; padding: 8px 11px; border-radius: 8px;
  background: rgba(47,125,92,.08); font-size: 13px; }
.rec-next-lab { display: block; font-size: 10.5px; text-transform: uppercase;
  letter-spacing: .05em; color: #2f7d5c; font-weight: 700; margin-bottom: 2px; }
.rec-next-box em { color: var(--ink-3); font-style: normal; }

/* ── Call follow-ups ───────────────────────────────────────────
   The 367 next_action lines written during summarising. Colour on
   the left border carries urgency and nothing else. */
.ca-group { margin-bottom: 18px; }
.ca-h { margin: 0 0 8px; font-size: 12px; text-transform: uppercase;
  letter-spacing: .06em; color: var(--ink-3); font-weight: 700; }
.ca-n { display: inline-block; margin-left: 6px; padding: 0 7px; border-radius: 999px;
  background: var(--line-2, #eee); color: var(--ink-2); font-size: 11px; }
.ca-card { border: 1px solid var(--line); border-left: 3px solid var(--line);
  border-radius: 10px; padding: 11px 14px; margin-bottom: 8px; background: var(--surface); }
.ca-red   { border-left-color: #9e1b13; }
.ca-amber { border-left-color: #b4632a; }
.ca-blue  { border-left-color: #1f5f8b; }
.ca-grey  { border-left-color: var(--line-2, #ddd); }
.ca-top { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.ca-top strong { font-size: 14px; }
.ca-top small { color: var(--ink-3); font-size: 11.5px; }
.ca-due { margin-left: auto; font-size: 11.5px; font-variant-numeric: tabular-nums;
  color: var(--ink-3); white-space: nowrap; }
.ca-late { color: #9e1b13; font-weight: 700; }
.ca-now  { color: #b4632a; font-weight: 700; }
.ca-tags { margin-top: 6px; }
.ca-theme { display: inline-block; font-size: 10.5px; padding: 1px 7px; margin: 1px 4px 1px 0;
  border-radius: 999px; background: rgba(180,99,42,.10); color: #b4632a; }
.ca-act { margin: 8px 0 0; font-size: 13.5px; line-height: 1.5; }
.ca-more > summary { cursor: pointer; font-size: 11.5px; color: var(--primary);
  margin-top: 6px; user-select: none; }
.ca-sum { margin: 6px 0 0; font-size: 12.5px; line-height: 1.5; color: var(--ink-2); }
.ca-btns { display: flex; gap: 6px; margin-top: 10px; flex-wrap: wrap; }
@media (prefers-reduced-motion: reduce) { .ca-card { transition: none; } }

/* Merged calls surface. The bar reuses .inq-toolbar from the Inquiries page -
   one flex row that already fits - so these only add what is specific:
   the view select reads as the primary control, and the Mine/Team pair is a
   segmented toggle rather than two more selects. */
.rec-bar .rec-view { font-weight: 600; min-width: 150px; max-width: 200px; }
.rec-toggle { display: inline-flex; border: 1px solid var(--line); border-radius: 7px;
  overflow: hidden; height: 32px; flex: 0 0 auto; }
.rec-toggle .rec-scope { border: 0; background: transparent; padding: 0 12px;
  font: inherit; font-size: 12px; cursor: pointer; color: var(--ink-2); }
.rec-toggle .rec-scope.active { background: var(--primary); color: #fff; font-weight: 600; }
.rec-link { color: var(--primary); font-weight: 600; text-decoration: none; }
.rec-link:hover { text-decoration: underline; }
.sig-type { background: rgba(47,125,92,.12); color: #2f7d5c; font-weight: 600; }
.rec-act { font-size: 12.5px; line-height: 1.45; max-width: 320px; }
.rec-act .ca-due { display: block; margin: 0 0 3px; }
.rec-ev { margin-top: 4px; font-size: 11px; color: #2f7d5c; }
.rec-done { margin-top: 6px; }
@media (max-width: 900px) { .rec-bar .inp { min-width: 104px; } }

/* Contacts: the kind is only worth marking when it is NOT a customer contact -
   labelling the 18,561 normal ones would be noise. */
.ct-kind { display: inline-block; font-size: 10.5px; padding: 1px 7px; margin-left: 6px;
  border-radius: 999px; white-space: nowrap; }
.ct-inquiry    { background: rgba(31,95,139,.10);  color: #1f5f8b; }
.ct-prospect   { background: rgba(180,99,42,.12);  color: #b4632a; }
.ct-retailer   { background: rgba(47,125,92,.12);  color: #2f7d5c; }
.ct-unattached { background: rgba(0,0,0,.06);      color: #6b6259; }
.ic-teal { background: #2f7d5c; }

/* Undo toast, and the closed-action flag it pairs with. */
.toast-undo { display: flex; align-items: center; gap: 14px; }
.toast-undo button { border: 0; background: transparent; color: inherit;
  font: inherit; font-weight: 700; text-decoration: underline; cursor: pointer;
  padding: 0; opacity: .9; }
.toast-undo button:hover { opacity: 1; }
.rec-doneflag { display: block; margin-top: 4px; font-size: 11.5px; color: #2f7d5c; }
.rec-doneflag .lnk { margin-left: 6px; }
.ct-noname { color: var(--ink-3); font-weight: 500; font-style: italic; }

/* Three ways to resolve an untagged call: an existing record, a bare contact,
   or a new inquiry. Tabs rather than three stacked forms, because only one
   applies and stacking them made the sheet twice as tall. */
.rec-tabs { display: flex; gap: 4px; margin-bottom: 10px;
  border-bottom: 1px solid var(--line); }
.rec-tab { border: 0; background: transparent; padding: 6px 12px; cursor: pointer;
  font: inherit; font-size: 12.5px; color: var(--ink-3);
  border-bottom: 2px solid transparent; margin-bottom: -1px; }
.rec-tab.active { color: var(--primary); font-weight: 600; border-bottom-color: var(--primary); }
.rec-pane.hidden { display: none; }

/* ── Visit reports ─────────────────────────────────────────────
   Field write-ups, not prose: headings and lists carried in plain
   line breaks, so whitespace is structure and must survive. */
.vr-report { white-space: pre-wrap; font-size: 13.5px; line-height: 1.6;
  max-height: 420px; overflow: auto; padding: 12px 14px; margin: 12px 0;
  border: 1px solid var(--line); border-radius: 10px;
  background: var(--surface-2, var(--surface)); }
.vr-badge { display: inline-block; font-size: 11px; padding: 2px 8px; border-radius: 999px;
  background: rgba(0,0,0,.06); color: var(--ink-3); margin: 6px 0 0; }
.vr-rated { font-size: 11.5px; font-weight: 700; color: #b4632a; }
.vr-signals { margin: 12px 0; padding: 10px 14px; border-radius: 10px;
  background: rgba(31,95,139,.05); }
.vr-signals > div { margin-bottom: 6px; }
.vr-op { color: #2f7d5c; font-size: 13px; margin: 6px 0 0; }
.vr-risk { color: #9e1b13; font-size: 13px; margin: 6px 0 0; }
.vr-thread { margin-top: 14px; border-top: 1px solid var(--line); padding-top: 12px; }
.vr-thread h4 { margin: 0 0 8px; font-size: 12px; text-transform: uppercase;
  letter-spacing: .05em; color: var(--ink-3); }
.vr-cmt { padding: 8px 0; border-bottom: 1px solid var(--line-2, #f0ece6); }
.vr-cmt p { margin: 4px 0 0; font-size: 13px; line-height: 1.5; }
.vr-cmt-h { display: flex; gap: 8px; align-items: baseline; font-size: 12px; }
.vr-role { color: var(--ink-3); font-size: 11px; }
.vr-when { margin-left: auto; color: var(--ink-3); font-size: 11px; }
.vr-post { margin-top: 12px; }
.vr-post textarea { width: 100%; margin-top: 6px; }
.vr-actions { display: flex; gap: 6px; margin-top: 8px; flex-wrap: wrap; }
.vr-stars { display: inline-flex; align-items: center; gap: 2px; margin: 4px 0; }
.vr-star { border: 0; background: transparent; cursor: pointer; font-size: 20px;
  line-height: 1; padding: 0 1px; color: #d8d2ca; }
.vr-star.on { color: #e0a83c; }
.vr-star-lab { margin-left: 8px; font-size: 12px; color: var(--ink-3); }
.vr-pin, .fv-pin { display: flex; gap: 8px; align-items: flex-start; margin: 10px 0;
  font-size: 12.5px; }
.vr-pin small, .fv-pin small { display: block; color: var(--ink-3); font-size: 11px; }

/* Tour visit summary. A stop never visited matters as much as one that was,
   so it is shown rather than omitted - dimmed, not hidden. */
.tvs-row { border-bottom: 1px solid var(--line-2, #f0ece6); }
.tvs-h { display: flex; gap: 10px; align-items: center; flex-wrap: wrap;
  padding: 10px 14px; cursor: pointer; }
.tvs-h:hover { background: rgba(31,95,139,.04); }
.tvs-date { font-size: 12px; color: var(--ink-3); min-width: 92px; }
.tvs-c { font-size: 11.5px; color: var(--ink-3); }
.tvs-missed { opacity: .62; }
.tvs-body { padding: 0 14px 12px; }
.tvs-body.hidden { display: none; }
.tvs-foot { padding: 14px; border-top: 1px solid var(--line); }
.tvs-foot h4 { margin: 0 0 2px; font-size: 13px; }
.tvs-foot textarea { width: 100%; margin-top: 8px; }

/* ── Commission wizard ─────────────────────────────────────────
   Customer, then invoices, then the products already priced. The
   rep supplies only the percent or the rate. */
.cw-inv { max-height: 210px; overflow: auto; border: 1px solid var(--line);
  border-radius: 9px; margin-top: 4px; }
.cw-inv-row { display: grid; grid-template-columns: 22px 1.4fr .9fr .9fr .8fr auto;
  gap: 8px; align-items: center; padding: 7px 10px; font-size: 12.5px; cursor: pointer;
  border-bottom: 1px solid var(--line-2, #f0ece6); }
.cw-inv-row:last-child { border-bottom: 0; }
.cw-inv-row:hover { background: rgba(31,95,139,.05); }
.cw-claimed { background: rgba(180,99,42,.05); }
.cw-lines { width: 100%; margin-top: 10px; font-size: 12.5px; }
.cw-lines th { font-size: 11px; text-transform: uppercase; letter-spacing: .04em;
  color: var(--ink-3); }
.cw-lines input[type=number] { width: 88px; text-align: right; }
.cw-off { opacity: .45; }
.cw-total { margin: 10px 0 0; font-size: 13.5px; text-align: right; }
