/* ============================================================================
   TOOLBOX — "Contract Grade" design layer
   ----------------------------------------------------------------------------
   Loaded AFTER the accreted base stylesheet; this file owns the visual
   identity: tokens, typography, chrome (ink rail + paper workspace) and
   component surfaces. Layout mechanics (flex, sticky, z-index, drawer
   transforms) stay in the base — nothing here changes geometry the islands
   rely on. Rules are prefixed with `body`/`.app-shell` so they outrank the
   equal-specificity base + in-body blocks without !important.

   Palette (Brompton edition): ink rail #1A1916 · stone greige #F4F3F0 ·
   accent = monochrome ink (light) / paper (dark), no more brass · blueprint
   #3a3939 unchanged. The historic --amber* names are kept, value-swapped to
   ink/paper; --viz-1..6 are the fixed-order data-viz/chart slots.
   Type: Satoshi (display/numerals) + Supreme (UI), self-hosted.
   ========================================================================== */

/* ---- self-hosted type (licensed, from _SHARED/fonts) ---------------------- */
/* Satoshi replaced Excon as --display (2026-08-10, owner call). Excon was
   chosen as "display/numerals", and the numerals were the problem: this app
   asks for font-variant-numeric:tabular-nums in every KPI value, contract sum
   and coverage count, and Excon's squared figures read poorly at those sizes.
   Satoshi is a neutral grotesque with even lining figures, already licensed in
   _SHARED/fonts. Excon is gone rather than left loading unused — including out
   of the service-worker precache, whose addAll() rejects wholesale on one 404,
   so the two must move together (VERSION bumped so returning devices drop the
   old entry rather than keep serving a font nothing references). */
@font-face{
  font-family:'Satoshi';
  src:url('/fonts/Satoshi-Variable.woff2') format('woff2');
  font-weight:300 900; font-style:normal; font-display:swap;
}
@font-face{
  font-family:'Satoshi';
  src:url('/fonts/Satoshi-VariableItalic.woff2') format('woff2');
  font-weight:300 900; font-style:italic; font-display:swap;
}
@font-face{
  font-family:'Supreme';
  src:url('/fonts/Supreme-Variable.woff2') format('woff2');
  font-weight:100 800; font-style:normal; font-display:swap;
}
@font-face{
  font-family:'Supreme';
  src:url('/fonts/Supreme-VariableItalic.woff2') format('woff2');
  font-weight:100 800; font-style:italic; font-display:swap;
}

/* ---- tokens · light (default) --------------------------------------------- */
:root{
  --bg:#F4F3F0; --bg2:#ECEAE6; --panel:#FFFFFF; --panel2:#FAF9F7;
  --gunmetal:#F0EEEB; --border:#E3E1DB; --border2:#D3D0C8;
  /* --muted2 darkened #A29E95 -> #777268 (2026-08-25): it sat at 2.4:1 on --bg
     across 158 shell call sites — the exact ratio three comments in this file
     call unreadable — and no gate measured it. It now clears 4.5:1 on the
     PANEL surfaces (4.78 / 4.55), which is its contract: faint text belongs on
     cards; bg/bg2 fine print uses --muted / --muted-strong. Enforced by
     check-viz-contrast.mjs (the muted-tier block). */
  --txt:#1A1916; --muted:#6B665C; --muted2:#777268;
  /* muted-as-text on --bg2 only (AA: --muted is 4.75:1 there; this clears 5.4:1) */
  --muted-strong:#625D53;
  --ink:#1A1916;
  --rail:#1A1916; --rail2:#22201C;
  --rail-line:rgba(255,255,255,.075);
  --rail-txt:#A5A198; --rail-txt2:#8C887F;
  --amber:#1A1916; --amber2:#33312C; --amber-dim:#E6E4DE; --on-accent:#FFFFFF;
  /* accent is monochrome ink now (was brass): --amber/--amber2 are the ink fill
     + its darker gradient stop, --amber-dim a neutral warm-grey selection tint.
     --on-accent is white on ink — 14+:1, no near-ink compromise needed anymore. */
  --amber-text:#1A1916;
  /* status-as-TEXT for the 10–11px dashboard chips/flags — raw --chase / #727270 /
     --quote sit below 4.5:1 on stone; these darkened values clear 4.5:1 on
     --bg, --bg2 and --panel. Fills/dots keep the raw hues. */
  --chase-text:#9E2F23; --danger-text:#5f5e5d; --quote-text:#626160; --await-text:#5d5c5b;
  /* blueprint — the structural navy: links, info accents, selection tints.
     #3a3939 is 9.57:1 on --bg2, so fill and text share the hex in light. */
  --blueprint:#3a3939; --blueprint-text:#3a3939; --blueprint-dim:#E2E8EF;
  /* the base's info/reply blue joins the blueprint voice (tracks the dark lift too) */
  --reply:var(--blueprint);
  /* new-code aliases for the value-swapped historic names */
  --brass:var(--amber); --brass2:var(--amber2); --brass-text:var(--amber-text); --on-brass:var(--on-accent);
  /* ---- data-viz · MONOCHROME ---------------------------------------------
     Hue is gone from the data layer. Series identity comes from POSITION and
     DIRECT LABELS; grey lightness is an ordinal/emphasis channel only, never
     nominal category identity.

     The step count is MEASURED, not borrowed. Walking WCAG's 3:1 ladder away
     from this theme's worst-case surface (--bg2, the darkest light surface —
     a dark mark loses most contrast there) yields exactly TWO rungs before a
     third leaves gamut at L=-0.02. So at most two greys may carry meaning at
     the same time. Anything past that must be direct-labelled: W3C
     Understanding 1.4.11 puts a graphical object out of contrast scope when
     adjacent text conveys the same information.
     Re-derive with scratchpad/viz-ramp.mjs — never hand-pick these.

       --viz-ink    featured measure (10.37:1 worst surface)
       --viz-mid    one comparative measure beside it (3.33:1 surface, 3.12:1 vs ink)
       --viz-soft   context / de-emphasis — BELOW the floor by design; legal
                    ONLY when direct-labelled, never the sole carrier of a value
       --viz-track  unfilled remainder of a meter — decoration, exempt
       --viz-line   axis, gridline and connector ink                          */
  --viz-ink:#343433; --viz-mid:#807F7D; --viz-soft:#B8B6B2;
  --viz-track:#E2E0DC; --viz-line:#D2D0CC;
  /* legacy slot names kept so island markup keeps resolving. They deliberately
     collapse: 1-3 are the real ordinal steps, 4-6 repeat rather than inventing
     greys that cannot clear 3:1. A chart needing >3 separable series is the
     wrong form — cut series or facet it. */
  --viz-1:var(--viz-ink); --viz-2:var(--viz-mid); --viz-3:var(--viz-soft);
  --viz-4:var(--viz-ink); --viz-5:var(--viz-mid); --viz-6:var(--viz-soft);
  /* ---- status tokens · ORDINAL ATTENTION, not hue -------------------------
     ~12 status maps in index.html (RFI, QA, drawings, scope, variations, EOT,
     specs, shop drawings, timeline) resolve their pill colour through these
     six tokens, and .pill writes them INLINE as text+border+dot. Redefining
     them here converts every pill in the app at once — no map or call-site
     edits, so no status can be missed.

     Six competing hues collapse to three ATTENTION tiers. Lightness is an
     ordinal/emphasis channel (the one job the evidence supports it for), and
     the pill's own text label is what actually names the state per 1.4.1:
       exception  needs action now      -> full ink, heaviest
       in-flight  someone is working it -> mid
       settled    nothing to do         -> quiet
     Every value is TEXT-legal (>=4.5:1), because .pill paints the label with
     it. That also fixes a pre-existing bug: Draft/Void/N-A were --muted2,
     which is 2.4:1 in light — unreadable as pill text.
     These are purpose-built neutrals, NOT the --muted family: those are warm
     greige (15-16 channel spread) and would smuggle colour back in.          */
  /* --chase is THE sanctioned hue (owner decision 2026-08-16): monochrome is a
     CHART argument — adjacent marks in one figure — and it does not transfer
     to a 60-row register where the CA does pre-attentive search; no glyph
     restores pop-out at 11px. One red returns on the exception tier ONLY.
     #9E2F23 is 6.06:1 on the worst light surface (text-legal) and 64%
     saturated (the gate's chroma-separation floor is 40%). Every other status
     token stays grey, and check-viz-contrast fails any second hue. */
  --chase:#9E2F23;               /* overdue · rejected · failed · gap · disputed */
  --await:#4F4E4B;               /* pending · in-progress · submitted           */
  --reply:#4F4E4B;               /* open · in-review · informational            */
  --quote:#676560;               /* answered · approved-as-noted                */
  --awarded:#676560;             /* approved · closed · passed · covered        */
  --notissued:#676560; --internal:#676560; --grey:#676560;
  --radius:12px; --radius-sm:8px; --radius-lg:16px; --r:8px;
  --shadow:0 1px 2px rgba(25,24,20,.05);
  --shadow-sm:0 1px 2px rgba(25,24,20,.06);
  --shadow-md:0 1px 2px rgba(25,24,20,.05), 0 10px 24px -16px rgba(25,24,20,.22);
  --shadow-lg:0 1px 3px rgba(25,24,20,.06), 0 18px 40px -22px rgba(25,24,20,.28);
  --shadow-xl:0 4px 14px -8px rgba(25,24,20,.14), 0 36px 70px -32px rgba(25,24,20,.34);
  --panel-grad:#FFFFFF; --panel-grad-hover:#F9F8F5;
  --display:'Satoshi','Hanken Grotesk',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;
  --body:'Supreme','Hanken Grotesk',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;
  --mono:ui-monospace,'Cascadia Mono','JetBrains Mono','SF Mono','Roboto Mono',Menlo,Consolas,monospace;
}

/* ---- tokens · dark --------------------------------------------------------- */
:root[data-theme="dark"]{
  --bg:#151412; --bg2:#1B1A17; --panel:#1D1C19; --panel2:#242320;
  --gunmetal:#22211E; --border:#2C2B27; --border2:#3B3934;
  /* --muted2 lifted #7A776E -> #98948B (2026-08-25): same defect as light,
     other direction — 5.2:1+ on every dark surface, still a step below
     --muted. See the light block for the panel-surfaces contract. */
  --txt:#F1EFEA; --muted:#ABA69B; --muted2:#98948B;
  --muted-strong:#ABA69B; /* 7.18:1 on --bg2 — dark --muted already passes */
  --ink:#0F0E0C;
  --rail:#121110; --rail2:#171613;
  --rail-line:rgba(255,255,255,.06);
  --rail-txt:#A19D94; --rail-txt2:#8C887F; /* rail is constant ink — 6.97:1 / 5.34:1 on it */
  --amber:#F1EFEA; --amber2:#D9D6CE; --amber-dim:#2A2925; --on-accent:#1A1916;
  --amber-text:#F1EFEA; /* = --txt — paper accent read as text, already AA-guaranteed */
  /* dark chips already passed with the raw hues (6.5+:1) — keep them, except the
     hardcoded #727270 flag red which sat at 3.5–3.9:1 and needs a lift */
  --chase-text:#E36B60; --danger-text:#a2a19f; --quote-text:#b8b7b5; --await-text:#b1b0ae;
  /* blueprint lifts for dark surfaces: text 8.20:1 on --bg */
  --blueprint:#91908f; --blueprint-text:#aeadab; --blueprint-dim:#202B38;
  /* ---- data-viz · MONOCHROME, RE-DERIVED (not flipped) --------------------
     Contrast is computed against the SURFACE, so one hex set cannot pass in
     both themes — these are solved independently by the same harness, not
     inverted from light. Worst-case surface here is --panel (the LIGHTEST
     dark surface: a light mark loses most contrast against it), giving rungs
     at L=0.13 and L=0.50; a third would need L=1.61, out of gamut.
     Direction of magnitude is unchanged: --viz-ink is still "more/featured",
     it is simply the high-contrast end of THIS surface (light on dark).      */
  --viz-ink:#C7C6C3; --viz-mid:#6E6D6C; --viz-soft:#4A4844;
  --viz-track:#2C2B27; --viz-line:#3B3934;
  --viz-1:var(--viz-ink); --viz-2:var(--viz-mid); --viz-3:var(--viz-soft);
  --viz-4:var(--viz-ink); --viz-5:var(--viz-mid); --viz-6:var(--viz-soft);
  /* status tiers re-derived for dark. --muted-strong and --muted are the SAME
     value here (both #ABA69B), so "settled" needs its own step or tiers 2 and
     3 would collapse — #948F86 lands ~5:1 on --panel, keeping it text-legal
     while still reading quieter than in-flight. */
  /* the sanctioned exception hue, re-derived for dark (4.89:1 on --panel2,
     the worst dark surface) — see the light block for the decision record */
  --chase:#E36B60;
  --await:var(--muted-strong); --reply:var(--muted-strong);
  /* lifted #8B8880 -> #8E8B83 (2026-08-25): 4.44:1 on --panel2, which joined
     the gate's surface list that day — a real marginal the old three-surface
     check could not see. 4.62 now, tier separation vs in-flight stays 1.40. */
  --quote:#8E8B83; --awarded:#8E8B83;
  --notissued:#8E8B83; --internal:#8E8B83; --grey:#8E8B83;
  --shadow:0 1px 2px rgba(0,0,0,.3);
  --shadow-sm:0 1px 2px rgba(0,0,0,.35);
  --shadow-md:0 1px 2px rgba(0,0,0,.3), 0 10px 26px -16px rgba(0,0,0,.55);
  --shadow-lg:0 1px 3px rgba(0,0,0,.35), 0 18px 44px -22px rgba(0,0,0,.6);
  --shadow-xl:0 4px 14px -8px rgba(0,0,0,.42), 0 36px 74px -30px rgba(0,0,0,.66);
  --panel-grad:#1D1C19; --panel-grad-hover:#232220;
}

/* ---- base ------------------------------------------------------------------ */
body{
  font-family:var(--body);
  font-feature-settings:"ss01" 0;
  letter-spacing:.005em;
}
body ::selection{background:color-mix(in srgb,var(--amber) 24%,transparent);color:var(--txt)}
body a{color:var(--blueprint-text)}
body ::-webkit-scrollbar{width:9px;height:9px}
body ::-webkit-scrollbar-thumb{background:var(--border2);border:2px solid transparent;background-clip:content-box;border-radius:8px}
body ::-webkit-scrollbar-thumb:hover{background:var(--muted2);background-clip:content-box}

/* ============================================================================
   THE RAIL — constant ink chrome in both themes; the app's spine.
   ========================================================================== */
.app-shell .sidebar{
  background:linear-gradient(178deg,var(--rail) 0%,var(--rail2) 100%);
  border-right:1px solid color-mix(in srgb,var(--rail) 70%,var(--border));
  box-shadow:inset -1px 0 0 rgba(255,255,255,.03);
}
.app-shell .sidebar .brand{padding:var(--sp-5) var(--sp-5) var(--sp-4);border-bottom:1px solid var(--rail-line);margin-bottom:var(--sp-2)}
/* white wordmark rendered at natural aspect — no more 32px chip */
.app-shell .sidebar .brand .logo{
  width:auto;max-width:150px;height:20px;border-radius:0;background:transparent;
  object-fit:contain;object-position:left center;padding:0;
  filter:drop-shadow(0 1px 0 rgba(0,0,0,.4));
}
.app-shell .sidebar .viewtabs{padding:var(--sp-3) var(--sp-3);gap:var(--sp-0)}
.app-shell .sidebar .viewtabs::-webkit-scrollbar-thumb{background:rgba(255,255,255,.12);background-clip:content-box}
.app-shell .sidebar .viewtab{
  color:var(--rail-txt);
  font:500 13px/1.35 var(--body);
  border-radius:9px;padding:var(--sp-2) var(--sp-3) var(--sp-2) var(--sp-3);
  position:relative;letter-spacing:.01em;
}
.app-shell .sidebar .viewtab::before{
  content:"";position:absolute;left:0;top:50%;transform:translateY(-50%) scaleY(0);
  width:3px;height:17px;border-radius:3px;
  /* rail is constant ink in both themes — --amber turns ink in light theme
     and would vanish here, so this stays a literal white, not the token */
  background:#FFFFFF;
  transition:transform .2s var(--ease-out,ease);
}
.app-shell .sidebar .viewtab:hover{background:rgba(255,255,255,.055);color:#ECEBE8}
.app-shell .sidebar .viewtab.on{
  background:rgba(255,255,255,.085);color:#FFFFFF;font-weight:600;
}
.app-shell .sidebar .viewtab.on::before{transform:translateY(-50%) scaleY(1)}
.app-shell .sidebar .viewtab.on.has-active-sub{background:rgba(255,255,255,.05);color:#F1F0ED}
.app-shell .sidebar .vt-sub{color:var(--rail-txt);font:500 12.5px var(--body)}
.app-shell .sidebar .vt-sub:hover{background:rgba(255,255,255,.05);color:#ECEBE8}
.app-shell .sidebar .vt-sub.on{background:rgba(255,255,255,.07);color:#FFFFFF}
.app-shell .sidebar .vt-sub3{color:var(--rail-txt)}
.app-shell .sidebar .side-foot{border-top:1px solid var(--rail-line);padding:var(--sp-3) var(--sp-3) var(--sp-4)}
.app-shell .sidebar .side-link{color:var(--rail-txt);border-radius:9px}
.app-shell .sidebar .side-link:hover{background:rgba(255,255,255,.055);color:#ECEBE8}
.app-shell .sidebar .side-av{
  /* rail is constant ink in both themes — --amber/--on-accent flip per theme
     and would go invisible or ink-on-ink here, so this chip stays literal */
  background:linear-gradient(160deg,#FFFFFF,#DEDCD5);
  color:#1A1916;font:700 12px var(--display);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.25);
}
.app-shell .sidebar .side-user-m b{color:#ECEBE8;font-weight:600}
.app-shell .sidebar .side-user-m span{color:var(--rail-txt2)}

/* ============================================================================
   THE RAIL, COLLAPSED — Supabase-style icon rail (html.nav-rail)
   Rest = 64px; hover / .rail-hot / :has(:focus-visible) = 250px OVERLAY.
   width + margin-right always sum to 64px, so .app-main NEVER reflows —
   the expanded panel paints over content on the sidebar's z-index:50
   (topbar is z-40). Every transitioned property shares .2s + the .12s
   hover-intent delay; drive-by cursor crossings produce zero visible change.
   Scope: this media query must stay byte-identical to railMq in index.html.
   Mouse clicks never match :focus-visible, so the rail closes on mouse-out
   even with a chevron/tab focused; keyboard focus holds it open (plan A5).
   ========================================================================== */
.logo-b{display:none}
@media (min-width:901px) and (hover:hover){
  .btn.nav-toggle{display:inline-flex}
  html.nav-rail .app-shell .sidebar{
    width:64px;
    transition:width .2s var(--ease-out,ease) .12s,margin-right .2s var(--ease-out,ease) .12s,box-shadow .2s var(--ease-out,ease) .12s;
  }
  html.nav-rail .app-shell .sidebar:is(:hover,.rail-hot){
    width:250px;margin-right:-186px;
    box-shadow:14px 0 44px -18px rgba(10,10,8,.55),1px 0 0 var(--rail-line);
  }
  /* rail scrollbar off in BOTH states: theme.css styles classic 9px
     scrollbars, which steal a quarter of the rail on 768p laptops and shift
     every icon mid-transition when sub-lists change scroll height */
  html.nav-rail .app-shell .sidebar .viewtabs{scrollbar-width:none}
  html.nav-rail .app-shell .sidebar .viewtabs::-webkit-scrollbar{display:none}
  /* labels collapse, never the icons: max-width 0 keeps the 18px icon at
     x=24 (viewtabs 12 + viewtab 12) — visually centered in 64 with no
     justify-content swap (those don't animate) */
  html.nav-rail .app-shell .sidebar .vt-label,
  html.nav-rail .app-shell .sidebar .side-user-m{
    white-space:nowrap;max-width:0;opacity:0;overflow:hidden;
    transition:max-width .2s var(--ease-out,ease) .12s,opacity .2s var(--ease-out,ease) .12s;
  }
  html.nav-rail .app-shell .sidebar:is(:hover,.rail-hot) .vt-label{max-width:160px;opacity:1}
  html.nav-rail .app-shell .sidebar:is(:hover,.rail-hot) .side-user-m{max-width:150px;opacity:1}
  /* chevrons: visibility (not opacity) so the collapsed rail exposes no
     focusable, AT-visible disclosure buttons for display:none lists */
  html.nav-rail .app-shell .sidebar .vt-chev{visibility:hidden;transition:visibility 0s .12s}
  html.nav-rail .app-shell .sidebar:is(:hover,.rail-hot) .vt-chev{visibility:visible}
  /* open sub-lists: delayed SNAP (0s duration) — display:none cannot honor
     a delay, and an undelayed pop defeats the drive-by guard. .open state
     persists across collapse/expand, restoring the accordion where it was. */
  html.nav-rail .app-shell .sidebar .vt-subs,
  html.nav-rail .app-shell .sidebar .vt-subs3{
    visibility:hidden;max-height:0;overflow:hidden;margin:0;
    transition:visibility 0s .12s,max-height 0s .12s,margin 0s .12s;
  }
  html.nav-rail .app-shell .sidebar:is(:hover,.rail-hot) .vt-subs,
  html.nav-rail .app-shell .sidebar:is(:hover,.rail-hot) .vt-subs3{
    visibility:visible;max-height:none;
  }
  /* brand: the wordmark cannot survive 64px — crossfade to the square B,
     absolutely positioned so NO layout property ever changes (plan A2).
     left 16 + 32 wide = center x=32, the rail's exact centerline. */
  html.nav-rail .app-shell .sidebar .brand{position:relative}
  html.nav-rail .app-shell .sidebar .brand .logo{opacity:0;transition:opacity .2s var(--ease-out,ease) .12s}
  html.nav-rail .app-shell .sidebar .brand .logo-b{
    display:block;position:absolute;left:var(--sp-4);top:50%;
    width:32px;height:32px;transform:translateY(-50%);
    opacity:1;transition:opacity .2s var(--ease-out,ease) .12s;
    filter:drop-shadow(0 1px 0 rgba(0,0,0,.4));
  }
  html.nav-rail .app-shell .sidebar:is(:hover,.rail-hot) .brand .logo{opacity:1}
  html.nav-rail .app-shell .sidebar:is(:hover,.rail-hot) .brand .logo-b{opacity:0}
  /* side-foot avatar: nudge onto the icon centerline (12px pad puts its
     center at x=28; 16px puts it at 32) */
  html.nav-rail .app-shell .sidebar .side-user{
    padding-left:var(--sp-4);transition:padding-left .2s var(--ease-out,ease) .12s;
  }
  html.nav-rail .app-shell .sidebar:is(:hover,.rail-hot) .side-user{padding-left:var(--sp-3)}
  /* reduced motion: zero the DURATION, keep the .12s hover-intent delay —
     transition:none would make every drive-by an instant 186px snap for
     exactly the users most sensitive to it (plan A6) */
  @media (prefers-reduced-motion:reduce){
    html.nav-rail .app-shell .sidebar,
    html.nav-rail .app-shell .sidebar .vt-label,
    html.nav-rail .app-shell .sidebar .side-user-m,
    html.nav-rail .app-shell .sidebar .side-user,
    html.nav-rail .app-shell .sidebar .brand .logo,
    html.nav-rail .app-shell .sidebar .brand .logo-b{transition-duration:0s}
  }
}

/* ============================================================================
   TOPBAR — paper glass, Satoshi masthead, mono eyebrow
   ========================================================================== */
body .topbar{
  background:color-mix(in srgb,var(--bg) 84%,transparent);
  border-bottom:1px solid var(--border);
}
body .topbar-head{min-width:0}
body .topbar-head .eyebrow{
  font:600 10px/1.4 var(--mono);letter-spacing:.14em;text-transform:uppercase;
  color:var(--amber-text);
  display:block;max-width:100%;min-width:0;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
/* the stamped index dot rides along as inline-block once the eyebrow is a block */
body .topbar-head .eyebrow::before{
  display:inline-block;vertical-align:middle;margin-right:var(--sp-2);
}
body .topbar-head h1{
  font:600 23px/1.1 var(--display);letter-spacing:.005em;color:var(--txt);
}
body .topbar::after{opacity:.65}

/* job switcher — quiet squared chip with a live accent dot (the 7px .js-ic
   dot keeps its circle: it is a pulse indicator, not a status pill — accepted
   de-bubble exception; sync state is also worded in Settings → Connection) */
body .topbar .job-switcher{
  background:var(--panel);border:1px solid var(--border);
  border-radius:var(--radius-sm);padding:var(--sp-1) var(--sp-3) var(--sp-1) var(--sp-3);box-shadow:var(--shadow-sm);
}
/* …unless it is a SEGMENT of the topbar bar, which it now is: the chip, the
   refresh and the tools are one joined control, so the chip drops its own
   border, radius and shadow rather than being a card inside a card. Matched
   specificity (body .topbar .topbar-tools .job-switcher) so no !important is
   needed — the convention this file states at the top. The parent's
   align-items:stretch equalises heights, so the chip cannot outgrow the
   buttons beside it. */
body .topbar .topbar-tools .job-switcher{
  background:transparent;border:0;box-shadow:none;
  border-radius:var(--radius-sm) 0 0 var(--radius-sm);
  display:inline-flex;align-items:center;gap:var(--sp-2);
}
body .topbar .topbar-tools .job-switcher select{border:0;background:transparent;max-width:22ch;text-overflow:ellipsis}
body .topbar .job-switcher .js-ic{background:var(--amber);box-shadow:0 0 0 3px color-mix(in srgb,var(--amber) 18%,transparent)}
body .topbar .job-switcher select{font:600 12.5px/1 var(--body);color:var(--txt)}

/* ============================================================================
   CONTROLS — buttons, fields, chips
   ========================================================================== */
body .btn{
  font-family:var(--body);font-weight:600;font-size:12.5px;letter-spacing:.01em;
  background:var(--panel);border:1px solid var(--border2);color:var(--txt);
  border-radius:9px;box-shadow:var(--shadow-sm),inset 0 1px 0 rgba(255,255,255,.6);
}
:root[data-theme="dark"] body .btn{box-shadow:var(--shadow-sm),inset 0 1px 0 rgba(255,255,255,.05)}
body .btn:hover{background:var(--panel2);border-color:var(--muted2);color:var(--txt);box-shadow:var(--shadow-md)}
body .btn:active{transform:translateY(1px);box-shadow:var(--shadow-sm)}
body .btn.primary{
  background:linear-gradient(180deg,color-mix(in srgb,var(--amber) 92%,#fff),var(--amber));
  border-color:color-mix(in srgb,var(--amber) 82%,#000);
  color:var(--on-accent);font-weight:650;
  box-shadow:var(--shadow-sm),inset 0 1px 0 rgba(255,255,255,.28);
  text-shadow:none;
}
body .btn.primary:hover{
  background:linear-gradient(180deg,color-mix(in srgb,var(--amber) 80%,#fff),var(--amber));
  border-color:color-mix(in srgb,var(--amber) 82%,#000);
  color:var(--on-accent);box-shadow:var(--shadow-md),inset 0 1px 0 rgba(255,255,255,.32);
}
body .btn.ghost{background:transparent;box-shadow:none}
/* body .btn.ghost (0,2,1) outranks the base .btn:focus-visible ring (0,2,0) and
   erased keyboard focus on ghost buttons (SWMS Sign/edit/delete etc.) — restore it */
body .btn.ghost:focus-visible{box-shadow:var(--ring-focus)}
body .btn.icon-only{border-radius:9px;color:var(--muted)}
/* an icon-only PRIMARY still sits on the accent fill — muted grey there is ~2.4:1 */
body .btn.primary.icon-only{color:var(--on-accent)}
body .btn.icon-only:hover{color:var(--txt)}
body .btn.cmdk-btn{color:var(--muted);font-weight:500}
body .btn.cmdk-btn kbd{font:600 10px var(--mono);background:var(--bg2);border:1px solid var(--border2);border-bottom-width:2px;border-radius:5px;color:var(--muted-strong)}

/* fields — one label recipe everywhere (Tendering view + procModal forms use bare
   <label> wrappers, no class) */
body .field label,body .mini-label,
body #procModal .modal-card label,body #view-tender label{
  font:600 10px/1.2 var(--mono);letter-spacing:.1em;text-transform:uppercase;color:var(--muted);
}
/* those bare labels WRAP their controls — undo the inherited microlabel styling */
body #procModal .modal-card label input,
body #procModal .modal-card label select,
body #procModal .modal-card label textarea,
body #view-tender label input,
body #view-tender label select,
body #view-tender label textarea{
  font:400 12.5px/1.4 var(--body);letter-spacing:.005em;text-transform:none;color:var(--txt);
  margin-top:var(--sp-1);
}
/* .cell-mini outside table.subs had NO surface recipe and fell back to UA widget
   chrome (dark inputs on paper) — give it the standard field surface */
body .cell-mini{
  background:var(--panel);border:1px solid var(--border2);color:var(--txt);
  padding:var(--sp-1) var(--sp-2);font-size:12.5px;
}
body .field input,body .field select,body .cell-input,body textarea,body .cmdk-input{
  font-family:var(--body);background:var(--panel);border-color:var(--border2);color:var(--txt);border-radius:8px;
  transition:border-color .15s var(--ease,ease),box-shadow .15s var(--ease,ease);
}
body .field input:focus-visible,body .field select:focus-visible,body textarea:focus-visible{
  outline:none;border-color:var(--amber);
  box-shadow:0 0 0 3px color-mix(in srgb,var(--amber) 18%,transparent);
}

/* export / dropdown menus — fix the leftover dark-theme hover */
body .export-menu{background:var(--panel);border:1px solid var(--border);border-radius:12px;box-shadow:var(--shadow-lg)}
body .export-menu button{color:var(--txt);border-radius:8px}
body .export-menu button:hover{background:var(--bg2);color:var(--txt)}
body .export-menu .exp-hint{color:var(--muted)}

/* ============================================================================
   SURFACES — cards, tables, modals, sheets, palette
   ========================================================================== */
body .modal-overlay,body .sheet-overlay,body .cmdk-overlay{background:color-mix(in srgb,var(--ink) 46%,transparent)}
body .cmdk-card{border-radius:14px;border-color:var(--border);box-shadow:var(--shadow-xl)}
body .sheet{border-left:1px solid var(--border)}

/* ledger tables: mono microlabel headers, calm rows, warm hover */
body table.subs thead th,
body .fin-table th,
body .ax-table thead th{
  font:600 10px/1.3 var(--mono);letter-spacing:.09em;text-transform:uppercase;
  color:var(--muted-strong);background:var(--bg2);
}
body table.subs tbody tr:hover>td{background:color-mix(in srgb,var(--amber) 4%,var(--panel))}
body .fin-table td,body table.subs td{font-variant-numeric:tabular-nums}

/* jobs portfolio cards */
body .jobs-grid .job-card{
  border-radius:var(--radius);border-color:var(--border);
  box-shadow:var(--shadow-sm);
  transition:transform .16s var(--ease-out,ease),box-shadow .16s var(--ease-out,ease),border-color .16s var(--ease-out,ease);
}
body .jobs-grid .job-card:hover{transform:translateY(-2px);box-shadow:var(--shadow-lg);border-color:var(--border2)}
body .jobs-grid .job-card.active{border-color:color-mix(in srgb,var(--amber) 55%,var(--border));box-shadow:0 0 0 1px color-mix(in srgb,var(--amber) 45%,transparent) inset,var(--shadow-md)}
body .jobs-grid .job-card .jc-name{font:600 16px/1.25 var(--display);letter-spacing:.01em}
body .jobs-grid .job-card .jc-kv{font:600 16px/1 var(--display);letter-spacing:.01em}
body .jobs-grid .jobs-title{font:600 22px/1.15 var(--display);letter-spacing:.005em}

/* toasts — inverted ink surface with a status dot (replaces the side-tab edge) */
body .toast{
  background:var(--ink);border:1px solid rgba(255,255,255,.09);border-left-width:1px;
  color:#F1EFEA;border-radius:11px;box-shadow:var(--shadow-xl);
  font-weight:500;
}
body .toast::before{
  content:"";width:8px;height:8px;border-radius:50%;flex:0 0 auto;
  /* toast is an ink surface — an ink accent dot would disappear, so the
     default falls back to the paper accent; status callers keep --accent */
  background:var(--accent,#F1EFEA);
  box-shadow:0 0 0 3px color-mix(in srgb,var(--accent,#F1EFEA) 22%,transparent);
}
body .toast .t-act{color:#F1EFEA;font-family:var(--body)}
body .toast .t-act:hover{background:rgba(255,255,255,.08)}
:root[data-theme="dark"] body .toast{background:#262521;border-color:var(--border2)}

/* ============================================================================
   TYPE DETAILS — headings & numerals across views
   ========================================================================== */
body .trade-title,body .shop-set-title,body .prog-sec-h,body .mins-group-h,body .sheet-head h2,body .scope-name{
  font-family:var(--display);font-weight:600;letter-spacing:.01em;
}
body .eyebrow{font-family:var(--mono)}

/* KPI / stat numerals wherever the base reached for mono display */
body .jc-kv,body .kpi-v,body .stat-v{font-variant-numeric:tabular-nums}

/* ── TWO-TIER METRIC GRAMMAR (2026-07-20) ─ Tier 1 = .ledger-item (contextual
   register counts — common, quiet); Tier 2 = .kpi-v/.prog-stat (dashboard-only
   headline KPIs — rare, earned). Both speak one type grammar:
   label = 10px mono caps letter-spaced muted · value = Satoshi 600 tabular.
   .scard/.budget-panel/.let-panel are RETIRED as of this pass — every render
   call that used to build them now renders .ledger/.ledger-item instead; see
   DESIGN.md "Two-tier metrics". The base-stylesheet rules for those three
   classes are now dead and safe to prune in a follow-up pass. ── */
body .prog-stat .l,body .kpi-l,body .ledger-item .l{
  font:600 10px/1.3 var(--mono);letter-spacing:.09em;text-transform:uppercase;color:var(--muted);
}
body .prog-stat .l{margin-top:var(--sp-1)}
body .prog-stat .n,body .kpi-v,body .ledger-item .n{
  font-family:var(--display);font-weight:600;letter-spacing:.01em;
  font-variant-numeric:tabular-nums;font-feature-settings:"tnum" 1;
}

/* ── .ledger — Tier-1 "ledger strip": one connected bar with hairline
   dividers, not N separate cards. Use for every contextual/secondary count
   (register + summary headers) — never add a new isolated stat-card outside
   the dashboard. data-tone carries the semantic coloring .let-panel's
   pos/neg/pct modifiers used to express. ── */
body .ledger{
  display:flex;align-items:stretch;flex-wrap:wrap;
  border:1px solid var(--border2);border-radius:var(--radius-sm);
  background:var(--panel-grad);box-shadow:var(--shadow-sm);
}
body .ledger-item{
  display:flex;flex-direction:column;justify-content:center;gap:var(--sp-0);
  padding:var(--sp-2) var(--sp-4);border-right:1px solid var(--border);
  min-width:0;cursor:default;
  transition:background .15s var(--ease);
}
body .ledger-item[role="button"]{cursor:pointer}
body .ledger-item:last-child{border-right:0}
body .ledger-item:hover{background:var(--bg2)}
body .ledger-item:focus-visible{outline:none;box-shadow:inset var(--ring-focus)}
body .ledger-item.active{background:var(--bg2);box-shadow:inset 0 -2px 0 0 var(--accent,var(--amber))}
body .ledger-item .n{font-size:17px}
body .ledger-item .s{font-size:10px;color:var(--muted);margin-top:var(--sp-0)}
/* ── STATUS WITHOUT HUE ─────────────────────────────────────────────────────
   W3C Understanding 1.4.1 allows a lightness difference alone to DISTINGUISH
   marks, but withdraws that allowance wherever the user must perceive a
   specific value — "an additional visual indicator will be required regardless
   of the contrast ratio between those colors". good/warning/critical is
   exactly that case, so a grey step alone would be non-conforming no matter
   how much contrast it has. Every tone therefore carries a GLYPH, and the
   surrounding label always names the state in words.

   Severity now maps to ink weight instead of hue, which is the better reading
   for a dense tool: exceptions go dark and glyphed, good news recedes.
     danger  ▲ solid triangle, heaviest ink
     warn    △ hollow triangle (silhouette differs by interior fill, which is
               a large-area difference and survives at 10px)
     ok      ✓ check, quietened — nothing to action
     info    no glyph; it is not an alert state
   The glyph is supplementary: it is redundant with the adjacent text, so it
   costs nothing if a screen reader skips generated content.                  */
body .ledger-item .n::before{
  font-variant-numeric:normal;font-weight:700;
  margin-right:var(--sp-1);display:inline-block;
}
body .ledger-item[data-tone="accent"] .n{color:var(--txt)}
body .ledger-item[data-tone="warn"]   .n{color:var(--txt)}
body .ledger-item[data-tone="warn"]   .n::before{content:"\25B3"}          /* △ */
body .ledger-item[data-tone="danger"] .n{color:var(--txt);font-weight:800}
body .ledger-item[data-tone="danger"] .n::before{content:"\25B2"}          /* ▲ */
body .ledger-item[data-tone="ok"]     .n{color:var(--muted-strong)}
body .ledger-item[data-tone="ok"]     .n::before{content:"\2713"}          /* ✓ */
body .ledger-item[data-tone="info"]   .n{color:var(--muted-strong)}
@media (max-width:760px){
  body .ledger-item{flex:1 1 calc(50% - 1px)}
}

/* ── BULLET GRAPH ─ Few's formatting defaults, monochrome ────────────────────
   The base rules painted these from an inline --bl-c hue that bulletChart() no
   longer emits, so they are re-pointed at the measured viz ramp here.
     track  = the qualitative plot area: light, NO border ("Border. None")
     fill   = the featured measure: "100% black", no border, 1/3 thickness
     tgt    = the comparative measure: a short perpendicular line, clearly
              visible but less dominant than the bar (it wins on contrast, the
              bar wins on area). The old 0.6 opacity muddied it — removed.
     over   = overshoot past target, stepped to --viz-mid: a measured 3:1
              neighbour of --viz-ink, so it separates without hue.
   Square ends throughout: rounded caps displace the endpoint, and the endpoint
   IS the value under Few's "endpoint aligned with its value" rule.        ── */
body .bullet{width:100%;height:14px;display:block;overflow:visible}
body .bl-track{fill:var(--viz-track);stroke:none}
body .bl-fill{fill:var(--viz-ink);stroke:none}
body .bl-over{fill:var(--viz-mid);stroke:none}
body .bl-tgt{stroke:var(--viz-ink);stroke-width:2;opacity:1;stroke-linecap:butt}

/* ── LINEAR METERS ─ fin-bar (budget committed) · ret-bar (retention held) ────
   Both are single-ratio meters, which is the form Few's spec endorses over a
   radial dial. They inherit the bullet's figure-ground rule: light track, ink
   fill, no border. .over keeps the bullet's overshoot step so "past the limit"
   reads the same way everywhere in the app.                                ── */
body .fin-bar,body .ret-bar{background:var(--viz-track)}
body .fin-barfill,body .ret-bar>span{background:var(--viz-ink)}
body .fin-barfill.over{background:var(--viz-mid)}

/* ── SVG CHART PRIMITIVES ─ chartBars / chartStack ───────────────────────────
   Stack segments sit edge-to-edge, so the 2px surface-coloured gap the helper
   already emits is what separates them; each is direct-labelled with its value
   in the legend, which is what carries identity now that hue is gone. Legend
   swatches inherit the same ordinal steps as the segments they name.      ── */
body .chart .ch-val{fill:var(--txt)}
body .chart .ch-lbl{fill:var(--muted-strong)}
body .chart-stack{border-color:var(--viz-line)}
body .ch-leg{color:var(--muted-strong)}
body .ch-leg i{border:1px solid var(--viz-line)}

/* ── WATERFALL ─ polarity without hue ────────────────────────────────────────
   Increases and decreases were red vs green. Desaturating both by luminance
   left them a near-identical grey (#727270 vs #676560), so polarity needed a
   channel that is not lightness: FILL vs OUTLINE. That is categorical, costs
   none of the two grey steps, and is the one texture-adjacent technique the
   UK Analysis Function endorses (a bar outline keeps extent readable without
   relying on the fill).
     total     solid ink   — the anchor/summary column
     up        solid mid   — cost went UP, the adverse direction here
     down      hollow      — cost came DOWN; reads as "removed" at a glance
   Position already encodes direction; this makes it legible in isolation. ── */
body .wf-total{fill:var(--viz-ink)}
body .wf-up{fill:var(--viz-mid)}
body .wf-down{fill:var(--viz-track);stroke:var(--viz-ink);stroke-width:1.5}
body .wf-conn{stroke:var(--viz-line);stroke-width:1;opacity:1}

/* ── RADIUS NORMALISATION ─ standalone controls live in the 8–9px family
   (buttons 9 / inputs 8); cards & modals (12–16) untouched. The 999px pills
   this pass once skipped were squared to --radius-sm in the RR-1 de-bubble
   sweep — only identity/geometry circles (avatars, dots, pin) stay round ── */
body .calc-k{border-radius:9px}
body .head-field input,body .head-field select,
body .site-num,body .fin-in,body .fin-cin,body .fin-rdue,
body .cell-mini,body table.subs td .cell-mini{border-radius:8px}
body .set-segb,body .row-details,body .fin-jump,body .scope-chip,body .fin-colink{border-radius:8px}

/* ── HIT AREAS ─ boot-view controls under 24px grow via token padding ── */
body .topbar .job-switcher{padding:0 var(--sp-3)}
body .topbar .job-switcher select{padding:var(--sp-2) 0}
body .link-btn.fin-hclink{height:auto;padding:var(--sp-1) var(--sp-2)}

/* ── SAFETY STATUS CHIPS ─ safBadge() output joins the .pill vocabulary.
   Text is the status hue pulled toward --txt so it clears 4.5:1 on the tinted
   fill in BOTH themes (light: darkened; dark: lifted) — --c arrives inline. ── */
body .saf-pill{
  background:color-mix(in srgb,var(--c,var(--muted)) 16%,transparent);
  border-color:color-mix(in srgb,var(--c,var(--muted)) 45%,transparent);
  color:color-mix(in srgb,var(--c,var(--muted)) 55%,var(--txt));
}
body .saf-pill .pdot{background:var(--c,var(--muted))}
body .saf-cyc:hover .saf-pill{border-color:var(--c,var(--muted))}
body .saf-cyc:focus-visible{outline:none;border-radius:6px;box-shadow:var(--ring-focus)}

/* ── COMMAND PALETTE ─ group headers were --muted2 (2.7:1) ── */
body .cmdk-group{color:var(--muted-strong)}

/* ── IMPORTER HEADER ─ base hardcodes Tailwind blues; join the blueprint voice ── */
body .imp-long .hd{color:var(--blueprint-text);box-shadow:inset 3px 0 0 var(--blueprint)}

/* ── LIGHT-THEME CONTRAST ─ RFI chip counts, filter-dropdown labels and
   detail-sheet fact labels were --muted2 (2.4–2.7:1); active chip's
   .rfi-chip.on b (0,2,1) still outranks this and lifts to --txt ── */
body .rfi-chip b{color:var(--muted-strong)}
body .filter-sel>label{color:var(--muted-strong)}
body .d-fact-l{color:var(--muted-strong)}

/* ── DASHBOARD RISK/STATUS TEXT ─ the money-status chips ("−$286k over") and the
   forecast-strip flags ("12 over" / "3 need action") are 10–11px/700 status text
   painted with raw accents (--chase 2.89:1 on --bg2, #727270 4.24:1 on --bg in
   light). Now achromatic: same glyph vocabulary as the ledger tones (▲ danger
   / △ watch / ✓ ok), severity carried by ink weight instead of hue. Each chip
   sits beside its own wording ("12 over", "3 need action"), so the glyph is
   redundant reinforcement and never the only signal. ── */
body .kpi-note{color:var(--muted-strong)}
body .kpi-note.warn{color:var(--txt)}
body .kpi-note.ok{color:var(--muted-strong)}
body .kpi-note.warn::before{content:"\25B3";margin-right:var(--sp-1)}
body .kpi-note.ok::before{content:"\2713";margin-right:var(--sp-1)}

body .dsb-flag{color:var(--txt);font-weight:800}
body .dsb-flag::before{content:"\25B2";margin-right:var(--sp-1)}
body .dsb-flag.ok{color:var(--muted-strong);font-weight:700}
body .dsb-flag.ok::before{content:"\2713"}

/* health annotation: the state word is already inside the annotation, so the
   glyph pairs with it and the border weight escalates instead of the hue */
body .dsb-health{color:var(--muted-strong)}
/* no margin: the annotation is inline-flex with gap:--sp-2, one spacing mechanism */
body .dsb-health::before{font-weight:700}
body .dsb-health.is-ok{color:var(--muted-strong)}
body .dsb-health.is-ok::before{content:"\2713"}
body .dsb-health.is-watch{color:var(--txt);border-color:var(--muted2)}
body .dsb-health.is-watch::before{content:"\25B3"}
body .dsb-health.is-warn{color:var(--txt);border-color:var(--txt);font-weight:800}
body .dsb-health.is-warn::before{content:"\25B2"}

/* ── REGISTER ROW AFFORDANCE ─ package trade-heads toggle open on click/Enter
   like the RFI & Drawings rows; show the same pointer affordance row-wide ── */
body .trade-head[data-toggle]{cursor:pointer}

/* ── HIT AREAS ─ sheet/modal close stays 30px visually but hits ≥42px via an
   inset pseudo overlay; the sheet-nav variant needs a positioning context ── */
body .rfi-sheet-nav .modal-close{position:relative}
body .modal-close::after{content:"";position:absolute;inset:calc(-1*(var(--sp-1) + var(--sp-0)))}

/* ── DEMO PILL CLEARANCE ─ fixed bottom-right badge must not sit on the last
   register rows at scroll end ── */
body:has(> .demo-badge) .app-main .wrap{padding-bottom:calc(var(--sp-9) + var(--sp-7))}

/* ── STAT-TILE STRAGGLERS (historical) ─ .scard's two markup dialects used to
   need separate overrides here; both call sites now render .ledger-item (see
   the TWO-TIER METRIC GRAMMAR block above), so these rules are dead. Left
   harmless in case an untouched .scard survives somewhere the 2026-07-20
   sweep missed — safe to delete once confirmed unused. */
body .scard .n{font-family:var(--display);font-size:20px;font-feature-settings:"tnum" 1}
body .scard>b{
  font-family:var(--display);font-weight:600;font-size:15px;letter-spacing:.01em;
  color:var(--txt);font-variant-numeric:tabular-nums;font-feature-settings:"tnum" 1;
}
body .scard>span{font:600 10px/1.3 var(--mono);letter-spacing:.09em;text-transform:uppercase;color:var(--muted)}

/* ── SETTINGS TABS ─ prog-modes segmented bar groups the 2800px stack ── */
body .set-section.settab-off{display:none}
body .settings-card .set-tabs{position:sticky;top:0;z-index:2;background:var(--panel);box-shadow:0 var(--sp-2) var(--sp-3) calc(-1*var(--sp-1)) var(--panel)}

/* ── REGISTER COLUMN SORT ─ affordance + caret on aria-sort headers ── */
body table thead th[data-sortable]{cursor:pointer;user-select:none}
body table thead th[data-sortable]:focus-visible{outline:2px solid var(--amber);outline-offset:-2px}
body table thead th[aria-sort]{color:var(--txt)}
body table thead th[aria-sort]::after{content:"▲";font-size:8px;margin-left:var(--sp-1);color:var(--amber-text);vertical-align:1px}
body table thead th[aria-sort="descending"]::after{content:"▼"}

/* ── HIT AREAS 2 ─ vertical-only target expansion on sub-30px controls
   (visual size unchanged; horizontal untouched so adjacent controls don't
   steal each other's clicks; smaller inset inside dense table rows) ── */
body .set-segb,body .rfi-sheet-foot .btn,body .rfi-nav-btn,
body .hs-sig-btn,body .saf-cyc{position:relative}
body .set-segb::after,body .rfi-sheet-foot .btn::after,body .rfi-nav-btn::after{
  content:"";position:absolute;inset:calc(-1*var(--sp-2)) 0;
}
body .hs-sig-btn::after,body .saf-cyc::after{
  content:"";position:absolute;inset:calc(-1*var(--sp-1)) 0;
}
body .key-close,body .calc-close{min-width:var(--sp-6);min-height:var(--sp-6)}

/* ── SAVED VIEWS ─ per-register filter presets (sv-wrap mounts in filter bars) ── */
body .sv-wrap{position:relative;margin-left:auto}
body .sv-btn b{font-weight:600;color:var(--muted-strong)}
body .sv-on{display:inline-block;width:var(--sp-2);height:var(--sp-2);border-radius:50%;background:var(--amber);margin-right:var(--sp-1);vertical-align:1px}
body .sv-menu{left:auto;right:0;min-width:248px}
body .sv-row{display:flex;align-items:stretch;gap:var(--sp-0)}
body .sv-row>button:first-child{flex:1;min-width:0}
body .sv-row.on>button:first-child{color:var(--amber-text)}
body .sv-del{appearance:none;background:transparent;border:0;color:var(--muted);cursor:pointer;padding:0 var(--sp-2);border-radius:8px;font-size:14px;line-height:1}
body .sv-del:hover{color:var(--danger-text,var(--chase))}
body .sv-del:focus-visible{outline:2px solid var(--amber);outline-offset:-2px}
body .sv-none{padding:var(--sp-2) var(--sp-3);font-size:12px;color:var(--muted)}
body .sv-save{display:flex;gap:var(--sp-1);padding:var(--sp-1);border-top:1px solid var(--border2);margin-top:var(--sp-1)}
body .sv-save .sv-name{flex:1;min-width:0}

/* ── RFI BULK SELECT ─ checkbox column + selection action bar ── */
body .col-rsel{width:28px;text-align:center}
body .rfi-selbox,body #rfiSelAll{accent-color:var(--amber);width:var(--sp-4);height:var(--sp-4);cursor:pointer;vertical-align:middle}
body .rfi-bulkbar{display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-3);padding:var(--sp-2) var(--sp-3);margin:0 0 var(--sp-3);border:1px solid var(--border2);border-radius:10px;background:var(--panel);font-size:12.5px;color:var(--muted-strong)}
body .rfi-bulkbar b{color:var(--txt)}
body .rfi-bulkbar[hidden]{display:none}

/* ---- reduced motion -------------------------------------------------------- */
@media (prefers-reduced-motion:reduce){
  .app-shell .sidebar .viewtab::before{transition:none}
  body .jobs-grid .job-card,body .btn{transition:none}
}

/* ============================================================================
   MOTION — alive layer (2026-07-19)
   State-conveying only: 150–250ms transitions on the --ease/--ease-out curves,
   one slow (~3s) accent liveness pulse on live dots, a ≤150ms fade-rise when a
   view enters. No layout property is ever animated (opacity / transform /
   paint only) and every loop or entrance pauses under prefers-reduced-motion.
   ========================================================================== */

/* view switch — setView() flips [hidden] on the #view-* containers; un-hiding
   restarts this entrance. Fill mode stays the default (none) so the resting
   state is fully visible even when the animation never runs. */
@media (prefers-reduced-motion:no-preference){
  body .app-main .wrap>[id^="view-"]{animation:bmViewIn .15s var(--ease-out)}
  @keyframes bmViewIn{
    from{opacity:0;transform:translateY(4px)}
    to{opacity:1;transform:none}
  }
}

/* live indicators — the job-switcher dot breathes an accent ring (~3s): box-shadow
   only, never scale. The topbar src-chip live/scan dots already run the base
   liveDot loop (index.html), which is gated the same way. */
@media (prefers-reduced-motion:no-preference){
  body .topbar .job-switcher .js-ic{animation:bmLivePulse 3.2s var(--ease) infinite}
  @keyframes bmLivePulse{
    0%,100%{box-shadow:0 0 0 3px color-mix(in srgb,var(--amber) 18%,transparent)}
    50%{box-shadow:0 0 0 6px color-mix(in srgb,var(--amber) 6%,transparent)}
  }
}

/* micro-states — gap-fills over the base vocabulary (buttons, rows, cards and
   KPI tiles already transition there): filter chips + segmented controls ease
   coherently instead of snapping, viewtabs pick up the brand curve, chips get
   the same 1px pressed drop as buttons. */
body .rfi-chip,body .scope-chip,body .let-chip,body .fin-jump,
body .set-segb,body .sv-btn,body .status-pick{
  transition:background .15s var(--ease),border-color .15s var(--ease),
             color .15s var(--ease),box-shadow .15s var(--ease);
}
body .viewtab{transition:color .15s var(--ease),background .15s var(--ease),box-shadow .15s var(--ease)}
body .rfi-chip:active,body .scope-chip:active,body .set-segb:active{transform:translateY(1px)}
body .export-menu button,body .sv-row>button{transition:background .12s var(--ease),color .12s var(--ease)}

/* reduced motion — stop the loop and the entrance; state changes land
   instantly (the base zeroes its own transitions separately). */
@media (prefers-reduced-motion:reduce){
  body .app-main .wrap>[id^="view-"]{animation:none}
  body .topbar .job-switcher .js-ic{animation:none}
  body .rfi-chip,body .scope-chip,body .let-chip,body .fin-jump,
  body .set-segb,body .sv-btn,body .status-pick,body .viewtab,
  body .export-menu button,body .sv-row>button{transition:none}
  body .rfi-chip:active,body .scope-chip:active,body .set-segb:active{transform:none}
}
