/* ================================================================
   UI KIT — tokens.css
   The standard component library. Pairs with ~/Documents/UIUX-PLAYBOOK.md
   (this file implements it) and kit.js (behaviors).
   Root element: add class "tc" (theme container) to <body> or app root.
   Themes: default = Slate. <root data-theme="paper"> = warm variant.
   Customize per product: --accent trio + the --viz-* meaning-contract
   palette (validate per playbook §6). Touch nothing else lightly.
   ================================================================ */
.tc {
  --page:#fafafa; --surface:#ffffff; --surface-2:#f4f4f5; --sidebar:#f7f7f8;
  --ink:#17171a; --ink-2:#55555e; --ink-3:#72727e; /* ink-3 ≥4.5:1 on page & surface (AA) */
  --hairline:#e6e6ea; --hairline-2:#f0f0f2; --ring:rgba(23,23,26,.08); --shadow-c:23,23,26;
  --accent:#2770ce; --accent-ink:#1c5cab; --accent-bg:rgba(39,112,206,.08); /* accent 4.89:1 on surface (AA) */
  /* ---------- meaning-contract slots (playbook §6) ----------
     Every value below is CHOSEN by tools/palette-propose.js and CHECKED by
     tools/palette-check.js, whose own maths is proved by
     tools/palette-selftest.js against the 19 published CIEDE2000 pairs and
     the WCAG contrast boundaries. Nothing here was picked by eye. Re-run all
     three before changing a digit; the numbers are in
     checks/PALETTE-VALIDATION.md.

     What each slot MEANS — a contract, not a swatch set. Use a slot for its
     meaning or do not use it:
       --viz-1   the evidenced subject  — confirmed, counted, signed for
       --viz-2   the same subject, PARTIALLY evidenced — provisional, or
                 seen by discovery only. The middle step of the ordered run
                 viz-1 → viz-2 → viz-mute, and it reads weaker on purpose.
       --viz-3   the counterparty       — external, outbound, a vendor, the
                 other side of a movement
       --viz-4   attention, NOT danger  — the .st-warn dot. Amber.
       --viz-mute   never assessed / never asked. Always hatched or stamped.
       --viz-track  an empty track. Never a data mark.
       --viz-seq-1..5  ORDERED intensity: one hue, monotonic in L*, so it
                 survives greyscale and all three dichromacies. Matrix and
                 heat cells only, always with its legend.

     Measured, at the WORST of {normal, protanopia, deuteranopia, tritanopia}:
       every categorical pair  ΔE ≥ 14.3   (gate is 12)
       every slot              ≥ 3.15:1 on slate AND paper   (gate is 3:1)
     ⚠ --viz-4 sits ΔE 9.7 from --lc-missing. No amber in sRGB clears 12 from
     that red at 3:1, so the two are separated by GEOMETRY instead: missing
     and broken-chain draw as an open gap with a dashed edge and a text
     plate, never as a filled amber segment. Red stays reserved. */
  --viz-1:#2770ce; --viz-2:#797f97; --viz-3:#53a06a; --viz-4:#d76a00;
  --viz-mute:#b3b3bb; --viz-track:#ececef;
  --viz-seq-1:#dee5fd; --viz-seq-2:#b8c5f4; --viz-seq-3:#8ca4e7;
  --viz-seq-4:#5380d4; --viz-seq-5:#0759ab;
  --s-good:#0ca30c; --s-good-ink:#04790a; --warn:#eda100; --warn-ink:#b0500f; --danger:#d03b3b;
  --ease-out:cubic-bezier(.22,1,.36,1); --ease-spring:cubic-bezier(.34,1.4,.44,1);
  --t-fast:150ms; --t-med:260ms; --t-slow:480ms;
  /* the entrance fade. Its own token because it is the ONE duration a
     person sits through before they can read the page: at --t-slow, with
     kit.js's stagger in front of it, a Dashboard filter change took
     1,173ms to come back. See the reveal block in js/kit.js. */
  --t-rv:240ms;
  --sans:system-ui,-apple-system,"Segoe UI",sans-serif;
  --mono:ui-monospace,"SF Mono",SFMono-Regular,Menlo,monospace;
  --r-card:12px; --r-ctl:8px;
  --topbar-h:54px; /* anything sticking under the topbar offsets by this, never a hardcoded 54 */
  font-family:var(--sans); color:var(--ink); background:var(--page);
  -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility;
  font-size:14px; line-height:1.45; min-height:100vh;
  transition:background .35s ease,color .35s ease;
}
.tc[data-theme="paper"]{
  --page:#f7f7f4; --surface:#fcfcfb; --surface-2:#f3f2ee; --sidebar:#f2f1ec;
  --ink:#141311; --ink-2:#52514e; --ink-3:#737166; /* AA on paper surface */
  --hairline:#e7e6df; --hairline-2:#efeee8; --ring:rgba(20,19,17,.08); --shadow-c:20,19,17;
  --viz-mute:#b7b5ab; --viz-track:#edece6;
}
.tc *,.tc *::before,.tc *::after{box-sizing:border-box;margin:0;padding:0}
.tc ::selection{background:rgba(39,112,206,.16)}
/* ── ONE FOCUS INDICATOR PER CONTROL ─────────────────────────────────────
   THE RULE. Exactly one visible focus indicator per focused control, and
   this is it unless the control already has one of its own. Two kinds of
   control already do: one sitting in a container that lights up on
   :focus-within (.ak-in, .search, .at-search, .pop-flt …), and one with a
   :focus rule of its own (.inp, .sel, .field input …). Both used to draw
   their soft halo AND this hard ring inside it, which is the double ring
   the product owner photographed on the Ask box.

   HOW A CONTROL YIELDS. It declares `--focus-ring:none` on its OWN base
   rule — on the control, never on the wrapper, because the property
   inherits and a wrapper would silently strip the ring off every button
   inside it too (.ak-in holds the send button; .at-search holds Clear).
   `outline:0` is NOT how a control yields and never was: this rule sits at
   (0,2,0) and beat every one of those declarations, which is precisely how
   the two rings came to be drawn at once.

   WHERE NOTHING ELSE DRAWS ONE, THIS STAYS. It is the keyboard indicator.
   Deleting it would leave a keyboard user unable to see where they are, so
   a control that yields must be certain something else lights up — and a
   control whose own ring is switched off again (.kf-in.locked .inp) takes
   this one back explicitly. frontend/tools/verify-one-focus-ring.js is the
   guard that stops a future container drawing a second ring.

   border-radius:4px was here and has gone: it rounded the CONTROL, not the
   ring, so a focused pill (.ak-chip, 99px) squared off to 4px on focus. An
   outline follows the element's own radius in every browser this ships to.
   ──────────────────────────────────────────────────────────────────────── */
.tc :focus-visible{outline:var(--focus-ring,2px solid var(--accent));outline-offset:2px}
/* forced-colors drops box-shadow, so every yielded control's halo disappears
   there. The outline survives, so in that mode it is drawn unconditionally. */
@media (forced-colors:active){.tc :focus-visible{outline:2px solid Highlight;outline-offset:2px}}
:where(.tc) button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
:where(.tc) a{color:inherit;text-decoration:none}
:where(.tc) svg{display:block}
:where(.tc) input,:where(.tc) select,:where(.tc) textarea{font:inherit;color:inherit}

/* ---------- app shell ---------- */
.shell{display:flex;min-height:100vh}
.sb{position:sticky;top:0;align-self:flex-start;width:236px;height:100vh;flex:none;background:var(--sidebar);border-right:1px solid var(--hairline);display:flex;flex-direction:column;padding:14px 10px 12px;transition:width var(--t-med) var(--ease-out),background .35s ease;overflow:hidden}
.shell.slim .sb{width:64px}
.sb-brand{display:flex;align-items:center;gap:9px;padding:2px 8px 14px}
.sb-logo{width:21px;height:26px;object-fit:contain;flex:none;transition:transform var(--t-med) var(--ease-spring)}
.sb-brand:hover .sb-logo{transform:translateY(-1px) rotate(-4deg)}
.sb-word{font-size:14.5px;font-weight:650;letter-spacing:-.015em;white-space:nowrap}
.sb-word small{display:block;font-size:10px;font-weight:500;letter-spacing:.06em;color:var(--ink-3);margin-top:-1px}
.sb-nav{flex:1;display:flex;flex-direction:column;gap:1px;overflow-y:auto;overflow-x:hidden;scrollbar-width:none}
.sb-nav::-webkit-scrollbar{display:none}
.nav-it{display:flex;align-items:center;gap:10px;height:32px;padding:0 8px;border-radius:7px;color:var(--ink-2);font-size:13px;font-weight:500;white-space:nowrap;position:relative;transition:background var(--t-fast) ease,color var(--t-fast) ease}
.nav-it:hover{background:var(--surface-2);color:var(--ink)}
.nav-it .nv-ic{flex:none;width:17px;height:17px;color:var(--ink-3);transition:color var(--t-fast) ease,transform var(--t-med) var(--ease-spring)}
.nav-it:hover .nv-ic{color:var(--ink)}
/* ⚠ A FACE IS PART OF SOMETHING — DRAW IT THAT WAY.
   The Store rail read Today · Goods-in · Stock · Staging · Defectives ·
   Movements: six peers, when Staging and Defectives are faces OF Stock.
   Procurement had the same shape under Purchasing. Read at a glance it
   said the product has six desks, which is not true and is exactly the
   "too many sections" the rail was supposed to stop.
   A face now sits indented under whatever heads it — a parent item where
   there is one, or the section label where the seat opens straight onto a
   face (Finance, Compliance). One idea, one mechanism. */
.nav-it.sub{height:29px;padding-left:30px;font-size:12.5px}
.nav-it.sub .nv-ic{width:15px;height:15px;opacity:.8}
/* the hairline that ties a run of faces to the item above them */
.nav-it.sub::before{content:"";position:absolute;left:16px;top:0;bottom:0;width:1.5px;background:var(--hairline)}
.nav-it.sub:first-of-type::before{top:4px}
.shell.slim .nav-it.sub{padding-left:0;height:32px}
.shell.slim .nav-it.sub::before{display:none}
.nav-it .lbl{overflow:hidden;transition:opacity var(--t-fast) ease}
.shell.slim .nav-it .lbl,.shell.slim .badge,.shell.slim .sb-word,.shell.slim .nav-sec{opacity:0;pointer-events:none}
.shell.slim .nav-it{justify-content:center;padding:0}
.shell.slim .sb-brand{justify-content:center;padding-left:0;padding-right:0}
.nav-it.active{background:var(--surface);color:var(--ink);font-weight:600;border:1px solid var(--hairline);box-shadow:0 1px 2px rgba(var(--shadow-c),.05)}
.nav-it.active .nv-ic{color:var(--accent)}
.badge{margin-left:auto;font-size:10.5px;font-weight:600;font-variant-numeric:tabular-nums;color:var(--accent-ink);background:var(--accent-bg);border-radius:99px;padding:1px 7px;transition:opacity var(--t-fast) ease}
.badge.warn{color:var(--warn-ink);background:rgba(237,161,0,.12)}
.nav-sec{padding:14px 8px 5px;font-size:10px;font-weight:650;letter-spacing:.09em;text-transform:uppercase;color:var(--ink-3);white-space:nowrap;transition:opacity var(--t-fast) ease}
.sb-foot{border-top:1px solid var(--hairline);padding-top:10px;margin-top:8px;display:flex;flex-direction:column;gap:2px}
.user-card{display:flex;align-items:center;gap:9px;padding:6px 8px;border-radius:8px;transition:background var(--t-fast) ease}
.user-card:hover{background:var(--surface-2)}
.u-av{width:28px;height:28px;border-radius:50%;flex:none;background:linear-gradient(135deg,var(--accent),#7a3ff2);color:#fff;font-size:10.5px;font-weight:650;display:grid;place-items:center}
.u-meta{min-width:0;transition:opacity var(--t-fast) ease}
.shell.slim .u-meta,.shell.slim .u-gear{opacity:0;width:0}
.u-name{font-size:12.5px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.u-role{font-size:11px;color:var(--ink-3)}
.u-gear{margin-left:auto;color:var(--ink-3);transition:transform .6s var(--ease-spring),color var(--t-fast) ease,opacity var(--t-fast) ease}
.user-card:hover .u-gear{transform:rotate(90deg);color:var(--ink)}

.main{flex:1;min-width:0}
.topbar{position:sticky;top:0;z-index:50;background:color-mix(in srgb,var(--page) 82%,transparent);-webkit-backdrop-filter:blur(14px) saturate(1.4);backdrop-filter:blur(14px) saturate(1.4);border-bottom:1px solid var(--hairline)}
.topbar-in{max-width:1156px;margin:0 auto;padding:0 26px;height:var(--topbar-h);display:flex;align-items:center;gap:12px}
.crumbs{display:flex;align-items:center;gap:7px;font-size:13px;color:var(--ink-3)}
.crumbs b{color:var(--ink);font-weight:600}
.crumbs svg{color:var(--hairline)}
.sb-toggle{width:28px;height:28px;border-radius:7px;display:grid;place-items:center;color:var(--ink-3);transition:background var(--t-fast) ease,color var(--t-fast) ease;margin-left:-8px}
.sb-toggle:hover{background:var(--surface-2);color:var(--ink)}
.topbar-right{margin-left:auto;display:flex;align-items:center;gap:10px}
.wrap{max-width:1156px;margin:0 auto;padding:0 26px 56px}
/* Work surfaces — queues, worklists, wide tables, split doc+data, dashboards — run
   full width to the rail. Reading surfaces keep the capped .wrap above.
   See playbook §1 "Content width is a function of surface type". */
.wrap.wide{max-width:none;padding:0 32px 56px}
.topbar-in.wide{max-width:none;padding:0 32px}
/* Cap the measure on prose inside a wide surface, never the page itself. */
.wrap.wide .measure{max-width:72ch}

/* ---------- controls ---------- */
.seg{position:relative;display:flex;background:var(--surface-2);border:1px solid var(--hairline);border-radius:var(--r-ctl);padding:2px}
.seg-thumb{position:absolute;top:2px;bottom:2px;left:0;width:60px;background:var(--surface);border:1px solid var(--hairline);border-radius:6px;box-shadow:0 1px 2px rgba(var(--shadow-c),.06);transition:transform var(--t-med) var(--ease-spring),width var(--t-med) var(--ease-spring)}
.seg button{position:relative;z-index:1;padding:3.5px 13px;font-size:12.5px;font-weight:500;color:var(--ink-3);border-radius:6px;transition:color var(--t-fast) ease}
.seg button.on{color:var(--ink)}
.seg button:hover:not(.on){color:var(--ink-2)}
.search{display:flex;align-items:center;gap:8px;width:170px;height:31px;padding:0 10px;background:var(--surface);border:1px solid var(--hairline);border-radius:var(--r-ctl);color:var(--ink-3);font-size:12.5px;transition:width var(--t-med) var(--ease-out),border-color var(--t-fast) ease,box-shadow var(--t-fast) ease;cursor:pointer}
.search:hover{border-color:color-mix(in srgb,var(--ink) 18%,var(--hairline))}
.search:focus-within{width:230px;border-color:color-mix(in srgb,var(--accent) 45%,var(--hairline));box-shadow:0 0 0 3px var(--accent-bg)}
/* .search is two things. On Messages and Reports it WRAPS an input, and the
   input yields the global ring below. In the top bar it IS the control — a
   button that opens the palette — so :focus-within above lights up the very
   element that is focused, and that element has to yield instead. Written as
   button.search so the wrapper form is untouched: putting the token on bare
   .search would strip the ring off anything focusable inside the wrapper. */
button.search{--focus-ring:none}
.search input{--focus-ring:none;border:0;outline:0;background:none;width:100%;font:inherit;color:var(--ink)}
.search input::placeholder{color:var(--ink-3)}
.kbd{margin-left:auto;flex:none;font-family:var(--mono);font-size:10px;color:var(--ink-3);border:1px solid var(--hairline);border-bottom-width:2px;border-radius:4px;padding:1px 5px;background:var(--surface-2)}
.icon-btn{position:relative;width:31px;height:31px;border-radius:var(--r-ctl);display:grid;place-items:center;color:var(--ink-2);border:1px solid transparent;transition:background var(--t-fast) ease,border-color var(--t-fast) ease,color var(--t-fast) ease}
.icon-btn:hover{background:var(--surface);border-color:var(--hairline);color:var(--ink)}
.icon-btn .dot{position:absolute;top:7px;right:7px;width:6px;height:6px;border-radius:50%;background:var(--accent);border:1.5px solid var(--page)}
@keyframes ring{0%{rotate:0deg}22%{rotate:12deg}45%{rotate:-9deg}65%{rotate:5deg}82%{rotate:-2deg}100%{rotate:0deg}}
.icon-btn:hover .bell{animation:ring .7s var(--ease-spring);transform-origin:50% 15%}

.btn{display:inline-flex;align-items:center;gap:6px;height:31px;padding:0 13px;border-radius:var(--r-ctl);font-size:12.5px;font-weight:550;border:1px solid var(--hairline);background:var(--surface);color:var(--ink-2);transition:all var(--t-fast) ease;cursor:pointer}
.btn:hover{color:var(--ink);border-color:color-mix(in srgb,var(--ink) 20%,var(--hairline));box-shadow:0 1px 2px rgba(var(--shadow-c),.06)}
.btn:active{transform:scale(.97);transition-duration:60ms}
.btn.primary{background:var(--ink);border-color:var(--ink);color:var(--surface)}
.btn.primary:hover{opacity:.88;color:var(--surface)}
.btn.accent{background:var(--accent);border-color:var(--accent);color:#fff}
.btn.accent:hover{background:var(--accent-ink);color:#fff}
.btn.danger:hover{color:var(--danger);border-color:var(--danger)}
.btn.sm{height:26px;padding:0 10px;font-size:11.5px}
.btn.ghost{border-color:transparent;background:transparent}
.btn.ghost:hover{background:var(--surface-2);box-shadow:none}

.fld{display:flex;flex-direction:column;gap:5px}
/* Focus is a conversation: the label wakes with its field, the ring blooms
   out on a spring, the placeholder steps back for the typed answer. */
.fld>label{font-size:11.5px;font-weight:600;color:var(--ink-2);transition:color var(--t-fast) ease}
.fld:focus-within>label{color:var(--accent-ink)}
.fld .hint{font-size:11px;color:var(--ink-3);font-weight:400}
.inp,.sel{--focus-ring:none;height:32px;padding:0 10px;border:1px solid var(--hairline);border-radius:var(--r-ctl);background:var(--surface);font-size:12.5px;color:var(--ink);outline:0;transition:border-color var(--t-fast) ease,box-shadow var(--t-med) var(--ease-spring),background-position var(--t-fast) var(--ease-out);-webkit-appearance:none;appearance:none}
.inp:hover:not(:focus),.sel:hover:not(:focus){border-color:color-mix(in srgb,var(--ink) 18%,var(--hairline))}
.inp:focus,.sel:focus{border-color:color-mix(in srgb,var(--accent) 55%,var(--hairline));box-shadow:0 0 0 3.5px var(--accent-bg),0 1px 2px rgba(var(--shadow-c),.05)}
.inp::placeholder{color:var(--ink-3);opacity:1;transition:opacity var(--t-fast) ease}
.inp:focus::placeholder{opacity:.4}
.sel{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24' fill='none' stroke='%238e8e98' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 9px top 50%;padding-right:26px}
.sel:focus{background-position:right 9px top 60%} /* the chevron dips: "about to drop open" */

/* ---------- every native dropdown, not only the ones that remembered ----------
   43 hand-authored <select>s and not one of them named .sel, which made .sel
   above dead CSS and left the product's dropdowns looking like two different
   applications. 27 of them named .inp instead — and .inp shares the
   `appearance:none` in the .inp,.sel rule while the chevron lives only on
   .sel, so those selects had their native arrow taken away and nothing put
   back: a dropdown that reads as a text box which mysteriously will not take
   typing, which is worse than the browser default it replaced.

   ⚠ This comment used to end "Those call sites now say .sel." They do not.
   Ten still say .inp — setup.js ×5, custody-rounds.js ×3, stock.js ×1 and
   today-asset-manager.js ×1 — 40 say .sel, and the remaining 17 carry no
   class at all. The claim was measured and it was false, which is worse than
   the slip it described: the next person reads it and trusts a repair that
   never happened. What actually dresses all 67 today is the net below, not the
   call sites, and tools/select-styling-check.js measures every one of them
   in a real browser rather than asserting it here in prose.

   The rules below are the net under the next one, and the reason a <select>
   written with no class at all still arrives dressed.

   These are `.tc select`, not `:where(.tc) select`. A zero-specificity net was
   tried first and rendered wrong: the reset at the top of this file is
   `.tc *{box-sizing:border-box;margin:0;padding:0}` at (0,1,0), which beats
   anything weaker, so every control came out with padding:0 and the chevron
   sat on top of the option text. (0,1,1) clears the reset and still loses a
   tie to any surface rule, which is injected into <head> after this file.

   [multiple] is excluded throughout. A list box is not a dropdown: it has no
   arrow to replace, and a 32px box with a single-line padding would clip the
   rows that are the whole point of it. --------------------------------------- */
.tc select:where(:not([multiple])){--focus-ring:none;height:32px;padding:0 26px 0 10px;font-weight:400;border:1px solid var(--hairline);border-radius:var(--r-ctl);background-color:var(--surface);background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24' fill='none' stroke='%238e8e98' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 9px top 50%;font-size:12.5px;color:var(--ink);outline:0;-webkit-appearance:none;appearance:none;transition:border-color var(--t-fast) ease,box-shadow var(--t-med) var(--ease-spring),background-position var(--t-fast) var(--ease-out)}
.tc select:where(:not([multiple])):hover:not(:focus){border-color:color-mix(in srgb,var(--ink) 18%,var(--hairline))}
.tc select:where(:not([multiple])):focus{border-color:color-mix(in srgb,var(--accent) 55%,var(--hairline));box-shadow:0 0 0 3.5px var(--accent-bg),0 1px 2px rgba(var(--shadow-c),.05);background-position:right 9px top 60%}
/* the standing net: .inp's `background` shorthand wipes the chevron out, so
   any select that names .inp — and any call site that makes the same slip
   tomorrow — has it put back here rather than losing its only affordance in
   silence. */
select.inp:not([multiple]){background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24' fill='none' stroke='%238e8e98' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 9px top 50%;padding-right:26px}
select.inp:not([multiple]):focus{background-position:right 9px top 60%}
/* a list box keeps its rows and its own native scroll */
.tc select[multiple]{--focus-ring:none;height:auto;min-height:76px;padding:5px 6px;font-weight:400;border:1px solid var(--hairline);border-radius:var(--r-ctl);background:var(--surface);font-size:12.5px;color:var(--ink);outline:0}
.tc select[multiple]:focus{border-color:color-mix(in srgb,var(--accent) 55%,var(--hairline));box-shadow:0 0 0 3.5px var(--accent-bg)}

/* ---------- forms: the layout the markup was already written for ----------
   .form-grid (ten forms across purchasing.js and compliance.js), .field
   (sixteen call sites across messages.js, import.js and estate-history.js),
   .field-lbl, .fld-l and .err were all written against rules this stylesheet
   never carried. With no rule to read, `<label>Text<input></label>` is inline
   flow, so the NEXT label's text wraps onto the end of the PREVIOUS input's
   line: "Raise an order" rendered as "Vendor [select] Site / [Bangalore]
   Equipment / [Laptop] Quantity". The intent was in the markup the whole
   time — several of those labels carry style="grid-column:1/-1", which means
   nothing at all outside a grid.

   Nothing below invents a second field language. Every metric is .fld/.inp/
   .sel above, reused, so a control that names a class and a control that
   does not are the same object on screen.

   The collapse to one column is a CONTAINER query — the technique
   kit-viz.css's .rb already uses — not a viewport one. These forms open both
   inside the 420px .drawer and on a full-width page, and a media query
   cannot tell those two apart: on a wide screen it would leave the drawer
   two columns wide, which is precisely the case that is unreadable.
   -------------------------------------------------------------------------- */
.form-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:13px 14px;align-items:end;container-type:inline-size}
/* a field takes one column; everything else a form carries — the error line,
   the hint, the submit button — is a row of its own. An inline
   style="grid-column:1/-1" on a label still outranks both of these. */
.form-grid>*{grid-column:1/-1}
.form-grid>label{grid-column:auto}
.form-grid>button{justify-self:start}
/* A container query styles the container's CHILDREN, never the container
   itself, so "one column" is expressed as "every field spans the whole row"
   rather than as a template change on .form-grid. Same result, and it is the
   only form of it that actually applies. */
@container (max-width:430px){.form-grid>label{grid-column:1/-1}}

/* The label stack — .fld's 5px gap and 11.5px/600 label, including the label
   waking with its field on focus.
   .field is the same object with a <span> carrying the text instead of a bare
   text node; .fld-l and .field-lbl are that span. Both were written as spans
   on purpose: the wrapper is itself a <label>, and a <label> inside a <label>
   is invalid HTML, so `.fld>label` could never have matched them. The
   selector was the wrong shape for correct markup — fixed here rather than in
   the nine call sites, which are right as they stand. */
.form-grid>label,.field{display:flex;flex-direction:column;gap:5px;min-width:0;font-size:11.5px;font-weight:600;color:var(--ink-2);transition:color var(--t-fast) ease}
.form-grid>label:focus-within,.field:focus-within{color:var(--accent-ink)}
.fld-l,.field-lbl,.field>span{font-size:11.5px;font-weight:600;color:var(--ink-2);transition:color var(--t-fast) ease}
.field>span,.field-lbl{color:inherit}
.fld:focus-within>.fld-l{color:var(--accent-ink)}
.form-grid .hint,.field .hint{font-size:11px;color:var(--ink-3);font-weight:400}
/* the line a failed save writes into, in every one of these forms */
.err{font-size:11.5px;font-weight:500;color:var(--danger);line-height:1.5}

/* Bare controls inside a form are reached by DESCENT rather than by class.
   These forms are plain HTML inside template literals across a dozen call
   sites, and asking every one of them to remember .inp is exactly how they
   came to be missing in the first place. The inner :where() keeps the element
   list from inflating the weight, so the whole fallback sits at (0,1,0) —
   enough to clear the `.tc *{padding:0}` reset, and low enough that any
   surface rule for its own control still wins. Selects are not listed here:
   they are handled for the whole app above.

   font-weight:400 is not decoration. The reset says
   `:where(.tc) input,select,textarea{font:inherit}`, and in THIS shape the
   control is a child of the label rather than its sibling as it is under
   .fld — so it inherits the label's 600 and every typed value renders bold.
   Caught by rendering the form, not by reading it. */
.form-grid :where(input:not([type=checkbox],[type=radio],[type=file],[type=hidden]),textarea),
.field :where(input:not([type=checkbox],[type=radio],[type=file],[type=hidden]),textarea){--focus-ring:none;width:100%;min-width:0;height:32px;padding:0 10px;font-weight:400;border:1px solid var(--hairline);border-radius:var(--r-ctl);background:var(--surface);font-size:12.5px;color:var(--ink);outline:0;transition:border-color var(--t-fast) ease,box-shadow var(--t-med) var(--ease-spring)}
.form-grid :where(input,textarea):hover:not(:focus),
.field :where(input,textarea):hover:not(:focus){border-color:color-mix(in srgb,var(--ink) 18%,var(--hairline))}
.form-grid :where(input,textarea):focus,
.field :where(input,textarea):focus{border-color:color-mix(in srgb,var(--accent) 55%,var(--hairline));box-shadow:0 0 0 3.5px var(--accent-bg),0 1px 2px rgba(var(--shadow-c),.05)}
.form-grid :where(input,textarea)::placeholder,
.field :where(input,textarea)::placeholder{color:var(--ink-3);opacity:1;transition:opacity var(--t-fast) ease}
.form-grid :where(input,textarea):focus::placeholder,
.field :where(input,textarea):focus::placeholder{opacity:.4}
.form-grid :where(select),.field :where(select){width:100%;min-width:0}
.form-grid :where(textarea),.field :where(textarea){height:auto;min-height:64px;padding:8px 10px;line-height:1.5;resize:vertical}
/* date, month, week, time and number are deliberately NOT told
   appearance:none. What we replace is the BOX; the widget inside it is the
   entire reason the call site chose that input type, and Firefox drops the
   number spinner outright when the control is told appearance:none. The
   webkit picker indicator is toned down to sit with the field instead, and
   keeps its own hit area and cursor; the spin buttons are asked back by name
   so nothing else on the page can take them away. */
.form-grid :where(input[type=date],input[type=month],input[type=week],input[type=time],input[type=datetime-local]),
.field :where(input[type=date],input[type=month],input[type=week],input[type=time],input[type=datetime-local]){padding-right:8px}
.form-grid input::-webkit-calendar-picker-indicator,
.field input::-webkit-calendar-picker-indicator{opacity:.5;cursor:pointer;transition:opacity var(--t-fast) ease}
.form-grid input:hover::-webkit-calendar-picker-indicator,.form-grid input:focus::-webkit-calendar-picker-indicator,
.field input:hover::-webkit-calendar-picker-indicator,.field input:focus::-webkit-calendar-picker-indicator{opacity:.85}
.form-grid input[type=number]::-webkit-inner-spin-button,.form-grid input[type=number]::-webkit-outer-spin-button,
.field input[type=number]::-webkit-inner-spin-button,.field input[type=number]::-webkit-outer-spin-button{-webkit-appearance:auto;appearance:auto;opacity:1}
/* a field that is not yours to fill reads as designed, not as the browser's
   own grey (messages.js disables the template ID once one exists)

   `.tc select:disabled` is here for the same reason the chevron net above is:
   measured, a <select disabled> written with no class and standing outside a
   .form-grid or a .field came out pixel-identical to an enabled one — same
   white ground, same ink, same cursor — so the one thing the attribute is for
   was invisible. No shipped select is in that position today; three of the
   four hosts a select is written into were covered and the fourth was not,
   which is how the chevron went missing the last time. */
.form-grid :where(input,select,textarea):disabled,
.field :where(input,select,textarea):disabled,.inp:disabled,.sel:disabled,
.tc select:disabled{background-color:var(--surface-2);color:var(--ink-3);cursor:not-allowed}
/* The toggle knob is a liquid pill with mass: press and it stretches toward
   where it will go, release and it springs across; the checkmark pops in
   only after the knob lands. Same 34×20 box. */
.toggle{position:relative;width:34px;height:20px;border-radius:99px;background:var(--hairline);transition:background var(--t-med) ease;cursor:pointer;flex:none;border:0}
.toggle:hover{background:color-mix(in srgb,var(--ink) 10%,var(--hairline))}
.toggle::after{content:"";position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:99px;background:#fff;box-shadow:0 1px 2px rgba(0,0,0,.2);transition:transform var(--t-med) var(--ease-spring),width var(--t-fast) var(--ease-out),box-shadow var(--t-fast) ease}
.toggle:active::after{width:20px;box-shadow:0 2px 5px rgba(0,0,0,.25)}
.toggle.on{background:var(--accent)}
.toggle.on:hover{background:var(--accent-ink)}
.toggle.on::after{transform:translateX(14px)}
.toggle.on:active::after{transform:translateX(10px)} /* right edge stays anchored while stretched */
.toggle::before{content:"";position:absolute;z-index:1;left:20.5px;top:7.5px;width:7px;height:4px;border-left:1.6px solid var(--accent);border-bottom:1.6px solid var(--accent);border-radius:.5px;transform:rotate(-45deg) scale(.3);opacity:0;transition:opacity 120ms ease,transform 200ms var(--ease-spring)}
.toggle.on::before{opacity:1;transform:rotate(-45deg) scale(1);transition-delay:150ms}

/* ---------- cards & layout ---------- */
.card{background:var(--surface);border:1px solid var(--hairline);border-radius:var(--r-card);box-shadow:0 1px 2px rgba(var(--shadow-c),.03);transition:border-color var(--t-med) ease,box-shadow var(--t-med) ease,background .35s ease}
.card:hover{border-color:color-mix(in srgb,var(--ink) 14%,var(--hairline));box-shadow:0 1px 2px rgba(var(--shadow-c),.03),0 6px 20px -8px rgba(var(--shadow-c),.1)}
.card.flat:hover{border-color:var(--hairline);box-shadow:0 1px 2px rgba(var(--shadow-c),.03)}
.card-head{display:flex;align-items:baseline;gap:10px}
.card-title{font-size:14px;font-weight:600;letter-spacing:-.008em}
.card-sub{font-size:12px;color:var(--ink-3)}
.page-head{display:flex;align-items:flex-end;justify-content:space-between;padding:28px 2px 20px;gap:16px;flex-wrap:wrap}
.page-head h1{font-size:24px;font-weight:600;letter-spacing:-.022em}
.page-head .sub{margin-top:5px;font-size:13px;color:var(--ink-2);display:flex;align-items:center;gap:8px}
.page-actions{display:flex;align-items:center;gap:8px}
.grid{display:grid;gap:12px}
.mt{margin-top:12px}
.rv{opacity:0;transform:translateY(8px)}
.rv.in{opacity:1;transform:none;transition:opacity var(--t-rv) var(--ease-out),transform var(--t-rv) var(--ease-out)}
@media (prefers-reduced-motion:reduce){.rv{opacity:1;transform:none}.tc *,.tc *::before,.tc *::after{animation-duration:.01ms!important;transition-duration:.01ms!important;transition-delay:0s!important}}

/* KPI tiles — every KPI is a door to its breakdown (playbook §3: click =
   descend), so the tile is interactive BY DEFAULT: pointer, lift on hover,
   a ↗ that surfaces top-right, focus ring + Enter/Space via kit.js.
   Opt out of all of it with .kpi.static for a genuinely inert tile. */
.kpi{padding:15px 16px 13px;position:relative;overflow:hidden;cursor:pointer;transition:border-color var(--t-med) ease,box-shadow var(--t-med) ease,background .35s ease,transform var(--t-med) var(--ease-spring)}
.kpi:hover,.kpi:focus-visible{transform:translateY(-1px);border-color:color-mix(in srgb,var(--ink) 18%,var(--hairline));box-shadow:0 1px 2px rgba(var(--shadow-c),.04),0 8px 24px -10px rgba(var(--shadow-c),.16)}
.kpi:active{transform:translateY(0) scale(.99);transition-duration:60ms}
.kpi::after{content:"";position:absolute;top:14px;right:15px;width:13px;height:13px;background:var(--accent-ink);-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7 17 17 7M9 7h8v8'/%3E%3C/svg%3E") center/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7 17 17 7M9 7h8v8'/%3E%3C/svg%3E") center/contain no-repeat;opacity:0;transform:translate(-4px,4px);transition:opacity var(--t-fast) ease,transform var(--t-med) var(--ease-out)}
.kpi:hover::after,.kpi:focus-visible::after{opacity:1;transform:none}
.kpi-top{display:flex;align-items:center;justify-content:space-between;color:var(--ink-3)}
.kpi-label{font-size:10.5px;font-weight:650;letter-spacing:.07em;text-transform:uppercase}
.kpi-ic{color:var(--ink-3);transition:color var(--t-med) ease,opacity var(--t-fast) ease}
.kpi:hover .kpi-ic,.kpi:focus-visible .kpi-ic{opacity:0} /* the icon yields its corner to the ↗ */
.kpi.static{cursor:default}
.kpi.static:hover,.kpi.static:active{transform:none;border-color:color-mix(in srgb,var(--ink) 14%,var(--hairline));box-shadow:0 1px 2px rgba(var(--shadow-c),.03),0 6px 20px -8px rgba(var(--shadow-c),.1)}
.kpi.static::after{content:none}
.kpi.static:hover .kpi-ic{opacity:1;color:var(--ink-2)}
.kpi-value{margin-top:9px;font-size:26px;font-weight:350;letter-spacing:-.02em;font-variant-numeric:tabular-nums;line-height:1.05;display:flex;align-items:baseline;gap:6px}
.kpi-value .unit{font-size:13.5px;color:var(--ink-3);font-weight:450;letter-spacing:0}
.kpi-foot{margin-top:8px;display:flex;align-items:center;gap:7px;font-size:11.5px;color:var(--ink-3);min-height:17px}
.delta{display:inline-flex;align-items:center;gap:3px;font-weight:550;font-variant-numeric:tabular-nums}
.delta.up{color:var(--s-good-ink)}
.delta.down{color:var(--warn-ink)}
.spark{position:absolute;right:12px;bottom:12px;opacity:.95}
.kpi-link{display:inline-flex;align-items:center;gap:4px;color:var(--accent-ink);font-weight:550;font-size:11.5px}
.kpi-link svg{transition:transform var(--t-med) var(--ease-out)}
.kpi:hover .kpi-link svg{transform:translateX(2.5px)}

/* status + avatars */
.live{display:inline-flex;align-items:center;gap:6px;color:var(--s-good-ink);font-weight:500}
.live i{width:6px;height:6px;border-radius:50%;background:var(--s-good);position:relative}
/* No pulsing halo. A dot that throbs forever reads as decoration, not status —
   it is the single most "generated" tell in a UI. The dot alone says live. */
.live i::after{content:"";position:absolute;inset:-2px;border-radius:50%;border:1px solid var(--s-good);opacity:.28}
.p-av{width:25px;height:25px;border-radius:50%;flex:none;display:grid;place-items:center;font-size:9.5px;font-weight:600;background:var(--surface-2);color:var(--ink-2);border:1px solid var(--hairline);position:relative;transition:transform var(--t-med) var(--ease-spring)}
.p-st{position:absolute;right:-1px;bottom:-1px;width:8px;height:8px;border-radius:50%;border:1.5px solid var(--surface)}
.st-good{background:var(--s-good)}.st-warn{background:var(--viz-4)}.st-mute{background:var(--viz-mute)}.st-off{background:var(--viz-track);border-color:var(--hairline)}
/* Chips are enamel pins: a pressed token with a top-lit sheen and a crisp
   edge — depth from finish, not from motion. Same box as before. */
.chip{display:inline-flex;align-items:center;gap:5px;font-size:10.5px;font-weight:600;color:var(--ink-2);background:linear-gradient(180deg,color-mix(in srgb,var(--surface) 60%,var(--surface-2)),var(--surface-2));border:1px solid var(--hairline);border-radius:99px;padding:1.5px 9px;white-space:nowrap;box-shadow:inset 0 1px 0 rgba(255,255,255,.6),0 1px 1px rgba(var(--shadow-c),.04)}
.chip.good{color:var(--s-good-ink);background:linear-gradient(180deg,rgba(12,163,12,.05),rgba(12,163,12,.12));border-color:rgba(12,163,12,.26);box-shadow:inset 0 1px 0 rgba(255,255,255,.5),0 1px 1px rgba(12,163,12,.05)}
.chip.warn{color:var(--warn-ink);background:linear-gradient(180deg,rgba(237,161,0,.06),rgba(237,161,0,.15));border-color:rgba(237,161,0,.3);box-shadow:inset 0 1px 0 rgba(255,255,255,.5),0 1px 1px rgba(176,80,15,.05)}
.chip.bad{color:#a03030;background:linear-gradient(180deg,rgba(208,59,59,.05),rgba(208,59,59,.11));border-color:rgba(208,59,59,.26);box-shadow:inset 0 1px 0 rgba(255,255,255,.5),0 1px 1px rgba(208,59,59,.05)}
.chip.info{color:var(--accent-ink);background:linear-gradient(180deg,rgba(39,112,206,.05),rgba(39,112,206,.11));border-color:rgba(39,112,206,.24);box-shadow:inset 0 1px 0 rgba(255,255,255,.5),0 1px 1px rgba(39,112,206,.05)}
/* a status ARRIVING or CHANGING stamps in once — never an idle loop */
@keyframes chip-stamp{0%{transform:scale(.8);opacity:0}62%{transform:scale(1.05)}100%{transform:scale(1);opacity:1}}
.chip.stamp{animation:chip-stamp .32s var(--ease-spring)}
/* standalone status dot: solid core + static tinted halo (no pulse — the dot
   alone says the state; if the state changes, re-add .stamp on the chip) */
.dot{display:inline-block;width:8px;height:8px;border-radius:50%;flex:none;background:var(--viz-mute);box-shadow:0 0 0 2.5px color-mix(in srgb,var(--viz-mute) 20%,transparent)}
.dot.good{background:var(--s-good);box-shadow:0 0 0 2.5px rgba(12,163,12,.16)}
.dot.warn{background:var(--warn);box-shadow:0 0 0 2.5px rgba(237,161,0,.2)}
.dot.bad{background:var(--danger);box-shadow:0 0 0 2.5px rgba(208,59,59,.16)}
.chip .dot{width:5px;height:5px;box-shadow:none;margin-right:-1px} /* opt-in colour-blind cue inside a chip */
.icon-btn .dot{box-shadow:none} /* the notification dot keeps its old look */
/* badge count change pops once — call UI.setBadge(el, value) */
@keyframes badge-pop{0%{transform:scale(1)}45%{transform:scale(1.22)}100%{transform:scale(1)}}
.badge.pop{animation:badge-pop .4s var(--ease-spring)}

/* rows / lists */
.rows{margin-top:7px}
.row{display:flex;align-items:center;gap:12px;padding:8px 8px;margin:0 -8px;border-radius:8px;transition:background var(--t-fast) ease;cursor:default;border-bottom:1px solid var(--hairline-2)}
.rows .row:last-child{border-bottom:0}
.row:hover{background:var(--surface-2)}
.row.click{cursor:pointer}
.row-name{font-size:12.5px;font-weight:500}
.row-sub{font-size:11px;color:var(--ink-3);margin-top:1px}
.row-right{margin-left:auto;text-align:right}
.row-val{font-size:12.5px;font-weight:550;font-variant-numeric:tabular-nums}
.row .go{opacity:0;transform:translateX(-4px);transition:opacity var(--t-fast) ease,transform var(--t-fast) ease;color:var(--ink-3)}
.row:hover .go{opacity:1;transform:none}
.rk{font-family:var(--mono);font-size:10.5px;color:var(--ink-3);width:16px;text-align:right}
.see-all{display:inline-flex;align-items:center;gap:5px;margin:7px 0 6px;font-size:12px;font-weight:500;color:var(--ink-3);transition:color var(--t-fast) ease;cursor:pointer}
.see-all:hover{color:var(--accent-ink)}
.see-all svg{transition:transform var(--t-med) var(--ease-out)}
.see-all:hover svg{transform:translateX(2.5px)}

/* item rows with logo + meter (app/product/category rows) */
.app{display:grid;grid-template-columns:28px 1fr 74px;align-items:center;gap:12px;padding:6px 8px;border-radius:8px;position:relative;overflow:hidden;cursor:default}
.app::before{content:"";position:absolute;inset:0;background:var(--surface-2);transform:scaleX(0);transform-origin:left;transition:transform var(--t-med) var(--ease-out);border-radius:8px}
.app:hover::before{transform:scaleX(1)}
.app>*{position:relative}
.app-ic{width:28px;height:28px;border-radius:7px;display:grid;place-items:center;background:var(--surface);border:1px solid var(--hairline);transition:transform var(--t-med) var(--ease-spring),box-shadow var(--t-med) ease}
.app:hover .app-ic{transform:scale(1.12) rotate(-4deg);box-shadow:0 2px 8px -2px rgba(var(--shadow-c),.18)}
.app-name{font-size:12.5px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.app-bar{display:block;margin-top:5px;height:4px;border-radius:2px;background:var(--hairline-2);overflow:hidden}
.app-fill{display:block;height:100%;border-radius:2px;width:0;transition:width .9s var(--ease-out)}
.app-val{text-align:right;font-variant-numeric:tabular-nums}
.app-val .t{font-size:12.5px;font-weight:550;display:block;transition:opacity var(--t-fast) ease,transform var(--t-fast) ease}
.app-val .p{font-size:11px;color:var(--ink-3);display:block;margin-top:-16px;opacity:0;transform:translateY(4px);transition:opacity var(--t-fast) ease,transform var(--t-fast) ease}
.app:hover .app-val .t{opacity:0;transform:translateY(-4px)}
.app:hover .app-val .p{opacity:1;transform:none}

/* tables */
.tbl{width:100%;border-collapse:collapse;font-size:12.5px}
.tbl th{text-align:left;font-size:10.5px;font-weight:650;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-3);padding:8px 10px;border-bottom:1px solid var(--hairline);white-space:nowrap}
.tbl td{padding:9px 10px;border-bottom:1px solid var(--hairline-2);vertical-align:middle;transition:box-shadow var(--t-fast) ease}
.tbl tr:last-child td{border-bottom:0}
/* Hovering a row is picking up one record from a stack: the row washes, an
   accent tick marks it, and the rest of the stack recedes. Reading focus,
   not decoration — and the resting table is untouched. */
.tbl tbody tr{transition:background var(--t-fast) ease,opacity var(--t-fast) ease}
.tbl tbody tr:hover{background:var(--surface-2)}
.tbl tbody tr:hover td:first-child{box-shadow:inset 2px 0 0 var(--accent)}
.tbl tbody:hover tr:not(:hover){opacity:.6}
.tbl .num{text-align:right;font-variant-numeric:tabular-nums}
.tbl-wrap{overflow-x:auto}

/* tabs */
.tabs{display:flex;gap:2px;border-bottom:1px solid var(--hairline);margin-top:4px}
.tab{padding:8px 13px;font-size:12.5px;font-weight:550;color:var(--ink-3);border-bottom:2px solid transparent;margin-bottom:-1px;transition:color var(--t-fast) ease,border-color var(--t-fast) ease;cursor:pointer}
.tab:hover{color:var(--ink-2)}
.tab.on{color:var(--ink);border-bottom-color:var(--ink)}

/* right drawer (house rule: overlays anchor right, full height) */
.drawer-veil{position:fixed;inset:0;background:rgba(17,17,19,.32);opacity:0;pointer-events:none;transition:opacity var(--t-med) ease;z-index:90}
.drawer-veil.on{opacity:1;pointer-events:auto}
.drawer{position:fixed;top:0;right:0;bottom:0;width:420px;max-width:92vw;background:var(--surface);border-left:1px solid var(--hairline);box-shadow:-12px 0 40px -12px rgba(var(--shadow-c),.25);transform:translateX(102%);transition:transform var(--t-med) var(--ease-out);z-index:91;display:flex;flex-direction:column}
.drawer.on{transform:none}
.drawer-head{display:flex;align-items:center;gap:10px;padding:16px 18px;border-bottom:1px solid var(--hairline)}
.drawer-title{font-size:14px;font-weight:600}
.drawer-x{margin-left:auto;width:26px;height:26px;border-radius:6px;display:grid;place-items:center;color:var(--ink-3);transition:background var(--t-fast) ease,color var(--t-fast) ease}
.drawer-x:hover{background:var(--surface-2);color:var(--ink)}
.drawer-body{flex:1;overflow-y:auto;padding:16px 18px}
.drawer-foot{padding:12px 18px;border-top:1px solid var(--hairline);display:flex;gap:8px;justify-content:flex-end}

/* empty state */
.emp{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:42px 20px;color:var(--ink-3)}
.emp .emp-ic{width:38px;height:38px;border-radius:10px;background:var(--surface-2);border:1px solid var(--hairline);display:grid;place-items:center;margin-bottom:12px;color:var(--ink-3)}
.emp b{color:var(--ink-2);font-size:13px;font-weight:600}
.emp p{font-size:12px;margin-top:3px;max-width:300px}
.emp .btn{margin-top:12px}

/* timeline (generic time-axis rows; segment classes map to --viz-*) */
.axis{position:relative;height:21px;margin-left:var(--who-w,168px)}
.tick{position:absolute;top:0;font-family:var(--mono);font-size:10px;color:var(--ink-3);transform:translateX(-50%)}
.tick.first{transform:none}.tick.last{transform:translateX(-100%)}
.p-rows{position:relative}
.gridlines{position:absolute;inset:0 0 0 var(--who-w,168px);pointer-events:none}
.gl{position:absolute;top:0;bottom:0;width:1px;background:var(--hairline-2)}
.now{position:absolute;top:-20px;bottom:0;left:var(--now-x);width:1px;background:color-mix(in srgb,var(--accent) 55%,transparent);z-index:3;pointer-events:none}
.now::before{content:"";position:absolute;top:0;left:-2.5px;width:6px;height:6px;border-radius:50%;background:var(--accent)}
.now-lbl{position:absolute;top:-3px;left:5px;font-family:var(--mono);font-size:10px;font-weight:600;color:var(--accent-ink);background:var(--surface);padding:0 4px;border-radius:3px;white-space:nowrap;line-height:14px}
.p-row{display:flex;align-items:center;height:38px;border-radius:8px;transition:opacity var(--t-med) ease,background var(--t-fast) ease;position:relative}
.p-rows:hover .p-row:not(:hover){opacity:.45}
.p-row:hover{background:var(--surface-2)}
.p-who{flex:0 0 var(--who-w,168px);display:flex;align-items:center;gap:10px;padding-left:6px}
.p-row:hover .p-av{transform:scale(1.1)}
.p-name{font-size:12.5px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:transform var(--t-med) var(--ease-out)}
.p-row:hover .p-name{transform:translateX(2px)}
.p-meta{font-family:var(--mono);font-size:10px;color:var(--ink-3)}
.p-track{position:relative;flex:1;height:13px;border-radius:5px;background:var(--viz-track)}
.seg-b{position:absolute;top:0;bottom:0;border-radius:4px;transform:scaleX(0);transform-origin:left center;transition:transform .7s var(--ease-out),filter var(--t-fast) ease,box-shadow var(--t-fast) ease}
.tl-on .seg-b{transform:scaleX(1)}
.seg-b:hover{filter:brightness(.92) saturate(1.1);box-shadow:0 0 0 2px var(--surface);z-index:2}
.seg-1{background:var(--viz-1)}.seg-2{background:var(--viz-2)}
.seg-3{background:var(--viz-3)}.seg-4{background:var(--viz-4)}
.seg-hatch{background:repeating-linear-gradient(45deg,var(--viz-mute) 0 3px,color-mix(in srgb,var(--viz-mute) 34%,var(--surface)) 3px 6px)}
.p-chip{position:absolute;right:8px;top:50%;transform:translateY(-50%);z-index:2;font-size:10.5px;font-weight:550;color:var(--ink-3);background:var(--surface);border:1px solid var(--hairline);border-radius:99px;padding:1px 8px;white-space:nowrap}
.legend{margin-left:auto;display:flex;gap:13px}
.lg{display:inline-flex;align-items:center;gap:6px;font-size:11.5px;color:var(--ink-2)}
.sw{width:9px;height:9px;border-radius:3px}
.sw.hatch{background:repeating-linear-gradient(45deg,var(--viz-mute) 0 2px,transparent 2px 4px);border:1px solid var(--viz-mute)}

/* donut */
.donut-wrap{position:relative;flex:none}
.donut-c{position:absolute;inset:0;display:grid;place-items:center;text-align:center;pointer-events:none}
.donut-v{font-size:24px;font-weight:350;letter-spacing:-.02em;font-variant-numeric:tabular-nums;transition:opacity var(--t-fast) ease}
.donut-l{font-size:10px;letter-spacing:.06em;text-transform:uppercase;font-weight:650;color:var(--ink-3);margin-top:2px;transition:opacity var(--t-fast) ease}
.donut-c.swap .donut-v,.donut-c.swap .donut-l{opacity:0}
.donut circle.seg{transition:stroke-width var(--t-med) var(--ease-spring),opacity var(--t-med) ease;cursor:pointer}
.donut.dim circle.seg:not(.hot){opacity:.28}
.dl{display:flex;flex-direction:column;gap:8px;flex:1}
.dl-row{display:flex;align-items:center;gap:9px;font-size:12.5px;color:var(--ink-2);border-radius:6px;padding:2px 6px;margin:-2px -6px;transition:background var(--t-fast) ease;cursor:default}
.dl-row:hover{background:var(--surface-2)}
.dl-row b{margin-left:auto;font-weight:550;color:var(--ink);font-variant-numeric:tabular-nums}
.dl-row .pct{width:38px;text-align:right;color:var(--ink-3);font-variant-numeric:tabular-nums;font-size:11.5px}

/* line chart chrome */
.lc .grid-l{stroke:var(--hairline-2)}
.lc .exp{stroke:var(--ink-3);stroke-width:1.2;stroke-dasharray:3 4;fill:none;opacity:.6}
.lc .act{stroke:var(--accent);stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
.lc .area{fill:var(--accent);opacity:.07}
.lc .axl{font-family:var(--mono);font-size:9.5px;fill:var(--ink-3)}
.lc .pt{fill:var(--accent);stroke:var(--surface);stroke-width:1.5;opacity:0;transition:opacity var(--t-fast) ease}
.lc .pt.end{opacity:1}
.lc .hover-l{stroke:var(--hairline);stroke-width:1;opacity:0}

/* tooltip */
.tip{position:fixed;z-index:100;pointer-events:none;background:rgba(23,22,20,.93);color:#f2f1ee;font-size:11.5px;line-height:1.5;border-radius:8px;padding:7px 10px;box-shadow:0 8px 24px -6px rgba(20,19,17,.35);opacity:0;transform:translateY(4px) scale(.98);transition:opacity var(--t-fast) ease,transform var(--t-fast) var(--ease-out);max-width:260px}
.tip.on{opacity:1;transform:none}
.tip .t-title{font-weight:600;display:flex;align-items:center;gap:6px}
.tip .t-sw{width:8px;height:8px;border-radius:2.5px}
.tip .t-meta{font-family:var(--mono);font-size:10.5px;color:#b9b7b0;margin-top:2px}

/* command palette */
.pal-veil{position:fixed;inset:0;background:rgba(17,17,19,.36);opacity:0;pointer-events:none;transition:opacity var(--t-fast) ease;z-index:95;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}
.pal-veil.on{opacity:1;pointer-events:auto}
.pal{position:fixed;left:50%;top:16vh;transform:translateX(-50%) scale(.98);width:560px;max-width:92vw;background:var(--surface);border:1px solid var(--hairline);border-radius:14px;box-shadow:0 24px 70px -12px rgba(var(--shadow-c),.35);z-index:96;opacity:0;pointer-events:none;transition:opacity var(--t-fast) ease,transform var(--t-fast) var(--ease-out);overflow:hidden}
.pal.on{opacity:1;pointer-events:auto;transform:translateX(-50%) scale(1)}
.pal-in{display:flex;align-items:center;gap:10px;padding:13px 16px;border-bottom:1px solid var(--hairline)}
.pal-in input{flex:1;border:0;outline:0;background:none;font-size:14px;color:var(--ink)}
.pal-list{max-height:320px;overflow-y:auto;padding:6px}
.pal-it{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:8px;font-size:13px;cursor:pointer;color:var(--ink-2)}
.pal-it .nv-ic{width:16px;height:16px;color:var(--ink-3)}
.pal-it.sel{background:var(--surface-2);color:var(--ink)}
.pal-it .pal-k{margin-left:auto;font-size:10.5px;color:var(--ink-3);font-family:var(--mono)}
.pal-sec{font-size:10px;font-weight:650;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-3);padding:8px 10px 3px}

.foot-note{margin-top:24px;text-align:center;font-size:11px;color:var(--ink-3)}
