/* ============================================================================
   SBT UI — the platform's own theme. Plain committed CSS, no build step, no
   third-party framework.

   FIVE themes. prime-light and prime-dark are this suite's own (#318): cool
   blue-grey neutrals, a sky accent, and a user-pickable accent hue driven by
   accent.js. prime-light is the DEFAULT for every user and every company.
   light / dark / aura are the original three, mirroring the SBT D-TAP app —
   underline top-nav links in light/dark, glass pill nav in aura, gray surfaces,
   indigo accent, emerald/amber/rose states; aura = the dark tokens plus the
   aura visual layer, mirroring dtap's dual `dark`+`aura` classes.

   Theme switch: html[data-sbt-theme="prime-light|prime-dark|light|dark|aura"].
   An unset attribute no longer follows the OS — the server resolves the theme
   (user setting -> company setting -> prime-light) and renders it onto <html>
   before first paint. The prefers-color-scheme block below is therefore only
   for the legacy `dark`/`aura` pair, and every theme carrying its own palette
   must be named in its :not() guards.
   ========================================================================== */

/* ---- font: Figtree (self-hosted, SIL OFL 1.1 — see fonts/OFL.txt) ---- */
@font-face {
  font-family: 'Figtree';
  font-style: normal;
  font-weight: 300 900;
  font-display: swap;
  src: url('fonts/figtree-var.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Figtree';
  font-style: normal;
  font-weight: 300 900;
  font-display: swap;
  src: url('fonts/figtree-var-latinext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Figtree';
  font-style: italic;
  font-weight: 300 900;
  font-display: swap;
  src: url('fonts/figtree-var-italic.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Figtree';
  font-style: italic;
  font-weight: 300 900;
  font-display: swap;
  src: url('fonts/figtree-var-italic-latinext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ---- tokens ----

   ⚠ EVERY token block below is also reachable through `.sbt-theme-scope`, which
   re-scopes a whole theme onto ONE ELEMENT and its subtree instead of the page.
   It exists for the theme picker's preview swatches: those carry a
   `data-sbt-theme` of their own, but every block here was keyed on `html[...]`,
   so the attribute matched nothing and all six swatches quietly rendered the
   theme already on screen — a picker that previewed only what you were already
   looking at. A declaration made ON an element beats one INHERITED from `html`
   whatever its specificity, which is what makes the re-scope work from inside
   any theme, `light` included, whose palette is this `:root`.
   ⛔ The scope class is opt-in on purpose: `[data-sbt-theme]` alone would hand
   a full palette to any element that ever carries the attribute. */
:root,
.sbt-theme-scope[data-sbt-theme="light"] {
  --sbt-bg: #f3f4f6;
  --sbt-surface: #ffffff;
  --sbt-surface-2: #f9fafb;
  --sbt-surface-3: #f3f4f6;
  --sbt-text: #111827;
  --sbt-text-2: #374151;
  --sbt-muted: #6b7280;
  --sbt-border: #e5e7eb;
  --sbt-border-2: #d1d5db;
  --sbt-accent: #4f46e5;
  --sbt-accent-strong: #4338ca;
  --sbt-accent-weak: #eef2ff;
  --sbt-accent-text: #4338ca;
  --sbt-accent-vivid: #4f46e5;   /* defined, NOT consumed outside prime (WO5-7) */
  --sbt-nav-active-border: #818cf8; /* indigo-400 (dtap light active underline) */
  /* Text that sits ON a solid fill. Defined in EVERY theme block, not only the
     prime ones: `var(--sbt-on-accent)` with no fallback would otherwise be
     invalid at computed-value time here, and `color` being an inherited
     property that means the label quietly inherits the surrounding text colour
     instead of failing loudly. Measured: white on #4f46e5 is 6.29. */
  --sbt-on-accent: #ffffff;
  /* White on the warning fill is only 3.19 here and 1.67 on the dark themes --
     the "internal note" badge was effectively unreadable. Black is 6.59. */
  --sbt-on-warning: #000000;
  --sbt-success: #059669;
  --sbt-success-weak: #ecfdf5;
  --sbt-warning: #d97706;
  --sbt-warning-weak: #fffbeb;
  --sbt-danger: #e11d48;
  --sbt-danger-weak: #fff1f2;
  --sbt-info: #0891b2;
  --sbt-info-weak: #ecfeff;
  /* chart series — CVD-validated categorical order; keep the order, never cycle */
  --sbt-series-1: #2a78d6;
  --sbt-series-2: #eb6834;
  --sbt-series-3: #1baf7a;
  --sbt-series-4: #eda100;
  --sbt-series-5: #e87ba4;
  --sbt-series-6: #008300;
  --sbt-series-7: #4a3aa7;
  --sbt-series-8: #e34948;
  --sbt-shadow: 0 1px 3px rgba(17, 24, 39, .08), 0 1px 2px rgba(17, 24, 39, .04);
  --sbt-shadow-lg: 0 10px 30px rgba(17, 24, 39, .18);
  --sbt-radius: 10px;
  --sbt-radius-sm: 8px;
  --sbt-font: 'Figtree', ui-sans-serif, system-ui, -apple-system, "Segoe UI",
    Roboto, "Helvetica Neue", Arial, sans-serif;
  --sbt-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
html[data-sbt-theme="dark"],
html[data-sbt-theme="aura"] { color-scheme: dark; }
html[data-sbt-theme="dark"],
html[data-sbt-theme="dark"] body { background: #030712; }
html[data-sbt-theme="dark"],
html[data-sbt-theme="aura"],
.sbt-theme-scope[data-sbt-theme="dark"],
.sbt-theme-scope[data-sbt-theme="aura"] {
  --sbt-bg: #030712;
  --sbt-surface: #111827;
  --sbt-surface-2: #1f2937;
  --sbt-surface-3: #273449;
  --sbt-text: #e5e7eb;
  --sbt-text-2: #d1d5db;
  --sbt-muted: #9ca3af;
  --sbt-border: #374151;
  --sbt-border-2: #4b5563;
  --sbt-accent: #6366f1;
  --sbt-accent-strong: #818cf8;
  --sbt-accent-weak: rgba(99, 102, 241, .16);
  --sbt-accent-text: #a5b4fc;
  --sbt-accent-vivid: #6366f1;   /* defined, NOT consumed outside prime (WO5-7) */
  --sbt-nav-active-border: #6366f1; /* indigo-500 (dtap dark active underline) */
  /* #372 -- the label on an accent fill is BLACK on these two themes. White on
     #6366f1 measures 4.47, a hair under AA, and WO5-1 leg 4 deliberately left
     it alone because moving it changes what a shipped theme looks like; that
     is now the ruled change. Black on the same fill measures 4.70 and passes,
     and it is the only step available without moving the accent itself, which
     feeds far more than labels. Black on #fbbf24 is 12.58. */
  --sbt-on-accent: #000000;
  --sbt-on-warning: #000000;
  --sbt-success: #34d399;
  --sbt-success-weak: rgba(16, 185, 129, .14);
  --sbt-warning: #fbbf24;
  --sbt-warning-weak: rgba(245, 158, 11, .14);
  --sbt-danger: #fb7185;
  --sbt-danger-weak: rgba(225, 29, 72, .16);
  --sbt-info: #22d3ee;
  --sbt-info-weak: rgba(8, 145, 178, .16);
  /* chart series — same hues re-stepped for the dark surface (validated) */
  --sbt-series-1: #3987e5;
  --sbt-series-2: #d95926;
  --sbt-series-3: #199e70;
  --sbt-series-4: #c98500;
  --sbt-series-5: #d55181;
  --sbt-series-6: #008300;
  --sbt-series-7: #9085e9;
  --sbt-series-8: #e66767;
  --sbt-shadow: 0 1px 3px rgba(0, 0, 0, .5);
  --sbt-shadow-lg: 0 10px 30px rgba(0, 0, 0, .6);
}
/* ⚠ These guards list every theme that must NOT inherit the dark tokens when the
   OS asks for dark. A theme missing from the :not() chain looks perfect on a
   light machine and silently turns dark on someone else's — the tokens below
   win purely by source order. `aura` was special-cased here for that reason and
   both prime themes now are too: prime-light because it is a light theme, and
   prime-dark because it carries its own dark palette rather than borrowing
   this one. Add any future theme here in the same breath as its token block. */
@media (prefers-color-scheme: dark) {
  html:not([data-sbt-theme="light"]):not([data-sbt-theme="prime-light"]):not([data-sbt-theme="prime-dark"]):not([data-sbt-theme="bluebell"]):not([data-sbt-theme="bluebell-dark"]):not([data-sbt-theme="fruits-light"]):not([data-sbt-theme="fruits-dark"]) { color-scheme: dark; }
  html:not([data-sbt-theme="light"]):not([data-sbt-theme="aura"]):not([data-sbt-theme="prime-light"]):not([data-sbt-theme="prime-dark"]):not([data-sbt-theme="bluebell"]):not([data-sbt-theme="bluebell-dark"]):not([data-sbt-theme="fruits-light"]):not([data-sbt-theme="fruits-dark"]),
  html:not([data-sbt-theme="light"]):not([data-sbt-theme="aura"]):not([data-sbt-theme="prime-light"]):not([data-sbt-theme="prime-dark"]):not([data-sbt-theme="bluebell"]):not([data-sbt-theme="bluebell-dark"]):not([data-sbt-theme="fruits-light"]):not([data-sbt-theme="fruits-dark"]) body { background: #030712; }
  html:not([data-sbt-theme="light"]):not([data-sbt-theme="prime-light"]):not([data-sbt-theme="prime-dark"]):not([data-sbt-theme="bluebell"]):not([data-sbt-theme="bluebell-dark"]):not([data-sbt-theme="fruits-light"]):not([data-sbt-theme="fruits-dark"]) {
    --sbt-bg: #030712;
    --sbt-surface: #111827;
    --sbt-surface-2: #1f2937;
    --sbt-surface-3: #273449;
    --sbt-text: #e5e7eb;
    --sbt-text-2: #d1d5db;
    --sbt-muted: #9ca3af;
    --sbt-border: #374151;
    --sbt-border-2: #4b5563;
    --sbt-accent: #6366f1;
    --sbt-accent-strong: #818cf8;
    --sbt-accent-weak: rgba(99, 102, 241, .16);
    --sbt-accent-text: #a5b4fc;
    --sbt-accent-vivid: #6366f1; /* defined, NOT consumed outside prime (WO5-7) */
    --sbt-nav-active-border: #6366f1;
    /* #372 -- same ruling as the attribute-stamped block above: black reads
       4.70 on #6366f1 where white read 4.47. Kept in step so a viewer whose OS
       is dark and who has never picked a theme sees what "dark" sees. */
    --sbt-on-accent: #000000;
    --sbt-on-warning: #000000;
    --sbt-success: #34d399;
    --sbt-success-weak: rgba(16, 185, 129, .14);
    --sbt-warning: #fbbf24;
    --sbt-warning-weak: rgba(245, 158, 11, .14);
    --sbt-danger: #fb7185;
    --sbt-danger-weak: rgba(225, 29, 72, .16);
    --sbt-info: #22d3ee;
    --sbt-info-weak: rgba(8, 145, 178, .16);
    --sbt-series-1: #3987e5;
    --sbt-series-2: #d95926;
    --sbt-series-3: #199e70;
    --sbt-series-4: #c98500;
    --sbt-series-5: #d55181;
    --sbt-series-6: #008300;
    --sbt-series-7: #9085e9;
    --sbt-series-8: #e66767;
    --sbt-shadow: 0 1px 3px rgba(0, 0, 0, .5);
    --sbt-shadow-lg: 0 10px 30px rgba(0, 0, 0, .6);
  }
}


/* ==========================================================================
   prime-light / prime-dark — the default themes (#318).

   Derived, not eyeballed: every value below was computed in OKLCH and then
   proved against WCAG 2.1 relative luminance on the surfaces it actually sits
   on, not just on white. The generator is ~/claude/kotak_handoff/s138_palette.py
   and public/sbt-ui/accent.js is its port — the user's colour picker runs the
   same maths, so a picked hue lands where these did.

   Two findings shaped the whole palette and are worth keeping written down:

   1. No blue in the sky family carries white text at its 600 step (measured
      3.87-4.01 across six seeds), so the white-text-safe solid is the 700 step.
      The sky identity lives in the tints, the bright non-text accents and the
      surfaces — never in the button fill.
   2. A muted grey validated only against white silently fails on tinted
      surfaces. slate-500 #64748b is 4.76 on white but 4.27 on --sbt-surface-2,
      which is exactly where muted text is most used. --sbt-muted is #5b6b80,
      and every pair below was measured on surface, bg AND surface-2.
   ========================================================================== */
html[data-sbt-theme="prime-light"] { color-scheme: light; }
html[data-sbt-theme="prime-light"],
html[data-sbt-theme="prime-light"] body { background: #f4f7fb; }
html[data-sbt-theme="prime-light"],
.sbt-theme-scope[data-sbt-theme="prime-light"] {
  /* neutrals — cool, faintly blue, to sit under a sky accent.
     bg -> surface is a 1.075 lift, so white cards genuinely rise off the
     ground instead of dissolving into a flat grey wash. */
  --sbt-bg: #f4f7fb;
  --sbt-surface: #ffffff;
  --sbt-surface-2: #eef3f9;
  --sbt-surface-3: #e2eaf4;
  /* text ramp. text 17.50/16.29/15.69 · text-2 10.06/9.36 · muted 5.44/5.06/4.88
     on surface/bg/surface-2. ⚠ muted on --sbt-surface-3 is 4.48, a hair under —
     surface-3 is an avatar/inset fill and must never carry muted body text; use
     --sbt-text-2 there. */
  --sbt-text: #0d1a2b;
  --sbt-text-2: #31435c;
  --sbt-muted: #5b6b80;
  /* the fourth step of the ramp. ⚠ NON-TEXT ONLY — icon strokes, dividers,
     disabled glyphs (3.56 on surface, clears SC 1.4.11's 3:1). Nothing in this
     app is "large text": 14pt-bold is 18.66px and 18pt is 24px, so timestamps
     (12px), previews (14px) and sender names (15px) all need the full 4.5:1 and
     must use --sbt-muted. There is no room for a lighter text step. */
  --sbt-faint: #7c899a;
  --sbt-border: #dde5f0;
  --sbt-border-2: #c3d0e0;
  /* sky ramp, seed #0ea5e9. 700 is the solid because 600 cannot carry white. */
  --sbt-accent: #006fa0;         /* 700 · white on it 5.55 */
  --sbt-accent-strong: #005981;  /* 800 · white on it 7.65 (hover) */
  --sbt-accent-weak: #f0f9ff;    /* 50  · tint fills · accent text on it 5.21 */
  --sbt-accent-tint: #dbf0ff;    /* 100 · stronger tint · accent text on it 4.74 */
  --sbt-accent-text: #006fa0;    /* 700 · 5.55 on surface */
  --sbt-accent-bright: #00a5ea;  /* 500 · rails, rings, dots — 2.77 on white, so
                                    decorative only: never text, never state
                                    carried alone, never a fill behind a label */
  --sbt-accent-vivid: #0d9ce0;   /* WO5-7 (§14b) · the brightest sky that still
                                    clears SC 1.4.11's 3:1 non-text floor on
                                    white (3.07). MAY carry state — unread dots,
                                    active indicator bars, rings, rail markers.
                                    ⛔ NEVER a fill behind white text: white on
                                    it is 3.07, and the seed #0ea5e9 is 2.77. */
  --sbt-on-accent: #ffffff;      /* what the accent engine recomputes per hue */
  --sbt-nav-active-border: #00a5ea;
  /* status — the stock light steps all fail AA on these neutrals (success 3.77,
     warning 3.19, danger 4.70, info 3.68 on surface), so prime uses darker
     steps: 5.48 / 5.38 / 5.64 / 5.72 on surface and >=4.82 on surface-2. */
  --sbt-success: #047857;
  --sbt-success-weak: #ecfdf5;
  --sbt-warning: #a2570a;        /* white on it 5.38, so the badge keeps white */
  --sbt-on-warning: #ffffff;
  --sbt-warning-weak: #fffbeb;
  --sbt-danger: #c81e42;
  --sbt-danger-weak: #fff1f2;
  --sbt-info: #0e6f8c;
  --sbt-info-weak: #ecfeff;
  /* chart series — the CVD-validated light order, unchanged. sbt-dash.js:35-45
     reads these from computed style and falls back to hardcoded hexes if a
     theme omits them, so all eight must be present or charts silently drift. */
  --sbt-series-1: #2a78d6;
  --sbt-series-2: #eb6834;
  --sbt-series-3: #1baf7a;
  --sbt-series-4: #eda100;
  --sbt-series-5: #e87ba4;
  --sbt-series-6: #008300;
  --sbt-series-7: #4a3aa7;
  --sbt-series-8: #e34948;
  /* elevation is RING-FIRST: the 1px ring is what makes a panel read as
     foreground, and the blur only softens it. Every shipping design system
     measured for this (Radix, Geist, Superhuman, Campsite) leads with a ring at
     4-10% black and keeps the resting blur at 3-6% alpha. Stock Tailwind's 10%
     shadow is heavier than any of them — which is precisely why default cards
     read "template-y". ⚠ forced-colors mode drops box-shadow entirely, so
     anything relying on this for separation also needs a border there. */
  --sbt-shadow: 0 0 0 1px rgba(13, 26, 43, .06), 0 1px 2px rgba(13, 26, 43, .04),
    0 2px 6px rgba(13, 26, 43, .04);
  --sbt-shadow-lg: 0 0 0 1px rgba(13, 26, 43, .07), 0 8px 24px rgba(13, 26, 43, .10),
    0 2px 6px rgba(13, 26, 43, .05);
}

/* prime-dark — the sibling. The user asked only for a light theme; without a
   dark partner a dark-preferring user loses the colour picker entirely and the
   theme cycle strands them. Same sky family, same engine, re-stepped: the solid
   is the brightest sky that still carries white text (5.45) while keeping 3:1
   against its own surface, and hover goes brighter rather than darker. */
html[data-sbt-theme="prime-dark"] { color-scheme: dark; }
html[data-sbt-theme="prime-dark"],
html[data-sbt-theme="prime-dark"] body { background: #0b1118; }
html[data-sbt-theme="prime-dark"],
.sbt-theme-scope[data-sbt-theme="prime-dark"] {
  --sbt-bg: #0b1118;
  --sbt-surface: #181e26;
  --sbt-surface-2: #222831;
  --sbt-surface-3: #2e353f;
  /* text 16.54/14.62/12.94 · text-2 11.85/10.48/9.27 · muted 7.25/6.41/5.67
     on bg/surface/surface-2. muted on surface-3 is 4.73, so it holds here. */
  --sbt-text: #ebf0f5;
  --sbt-text-2: #c7cdd5;
  --sbt-muted: #97a1af;
  --sbt-faint: #687380;          /* non-text only — 3.48 on surface */
  --sbt-border: #2d333c;
  --sbt-border-2: #424a55;
  --sbt-accent: #1d709c;         /* white on it 5.45 · 3.07 against surface */
  --sbt-accent-strong: #217aaa;  /* hover, brighter · white on it 4.74 */
  --sbt-accent-weak: rgba(45, 159, 219, .16);
  --sbt-accent-tint: rgba(45, 159, 219, .26);
  --sbt-accent-text: #48b8f7;    /* 7.56 on surface */
  --sbt-accent-bright: #48b8f7;
  --sbt-accent-vivid: #48b8f7;   /* 7.56 on prime-dark surface — same role (WO5-7) */
  --sbt-on-accent: #ffffff;
  --sbt-nav-active-border: #48b8f7;
  /* the stock dark status steps all clear AA on these neutrals (5.51-11.36) */
  --sbt-success: #34d399;
  --sbt-success-weak: rgba(16, 185, 129, .14);
  --sbt-warning: #fbbf24;        /* white on it is 1.67 -- the badge takes black */
  --sbt-on-warning: #000000;
  --sbt-warning-weak: rgba(245, 158, 11, .14);
  --sbt-danger: #fb7185;
  --sbt-danger-weak: rgba(225, 29, 72, .16);
  --sbt-info: #22d3ee;
  --sbt-info-weak: rgba(8, 145, 178, .16);
  --sbt-series-1: #3987e5;
  --sbt-series-2: #d95926;
  --sbt-series-3: #199e70;
  --sbt-series-4: #c98500;
  --sbt-series-5: #d55181;
  --sbt-series-6: #008300;
  --sbt-series-7: #9085e9;
  --sbt-series-8: #e66767;
  --sbt-shadow: 0 0 0 1px rgba(0, 0, 0, .55), 0 1px 3px rgba(0, 0, 0, .45);
  --sbt-shadow-lg: 0 0 0 1px rgba(0, 0, 0, .6), 0 12px 32px rgba(0, 0, 0, .55);
}

/* ==========================================================================
   BLUEBELL — the soft theme. A light palette built from pastel tints on a
   periwinkle ground, for people who find prime's contrast clinical and the
   legacy light theme flat.

   WHERE IT CAME FROM. The reference was a consumer security app whose screens
   read as friendly without reading as a toy. Measured off its own screenshots,
   the trick is not that it is pastel: its accent is a LOUD #006fff, and the
   softness is carried entirely by pale tint chips behind small icons —
   #e1f3fa baby blue, #f6f2ff baby lavender, #f1fff8 baby mint — floating on
   generous white. Pastel where a colour is DECORATIVE, confident where a
   colour carries MEANING.

   That split is the whole design, and it is also what makes a pastel theme
   survive this app's contrast rules. Nothing here is "large text" (§ the
   --sbt-faint note in prime-light), so every label needs the full 4.5:1; a
   theme that were pastel all the way through could not carry a single word.
   So the pastels live in the -weak/-tint fills and the surfaces, and every
   token that carries text is a deepened step of the same hue.

   NOT a recolour of prime-light. Prime is a SKY theme — cyan-leaning, cool,
   its neutrals faintly blue. Bluebell is a PERIWINKLE theme: the neutrals are
   violet-leaning (#1a1b3d ink rather than #0d1a2b), the ground is lilac-white
   rather than blue-grey, and the accent is an indigo-blue at the hue where
   blue and violet meet — which is where the reference's two brand colours,
   its #006fff blue and its #726fdb violet, actually sit together.

   MEASURED, not asserted. Every ratio below was computed, and Bluebell clears
   the two floors prime-light has to carry a caveat for:
     · --sbt-muted holds 4.5:1 on ALL FOUR grounds including --sbt-surface-3
       (4.99). Prime-light is 4.48 there and has to warn callers off it.
     · --sbt-faint clears the 3:1 non-text floor on all four (3.76/3.49/3.31/
       3.03). Prime-light documents 3.56 on surface alone.
   ⚠ --sbt-faint is still NON-TEXT ONLY. Clearing 3:1 makes it legal for icon
   strokes and dividers, not for a timestamp.

   The chart series are the CVD-validated light order, byte-for-byte. They are
   chosen for colour-vision-deficiency separation, which is a property of the
   set and not of this palette, and re-tinting them to match the theme would
   throw that away for a decoration nobody asked for. sbt-dash.js:35-45 reads
   all eight from computed style, so all eight must be present.
   ========================================================================== */
html[data-sbt-theme="bluebell"] { color-scheme: light; }
html[data-sbt-theme="bluebell"],
html[data-sbt-theme="bluebell"] body { background: #f5f6fe; }
html[data-sbt-theme="bluebell"],
.sbt-theme-scope[data-sbt-theme="bluebell"] {
  /* neutrals — violet-leaning, so white cards read as warm-cool white against
     a lilac ground rather than as a grey hole. bg -> surface is a 1.08 lift,
     the same separation prime uses, so a card still visibly rises. */
  --sbt-bg: #f5f6fe;
  --sbt-surface: #ffffff;
  --sbt-surface-2: #eef0fc;
  --sbt-surface-3: #e1e6fa;
  /* text ramp, on surface/bg/surface-2/surface-3:
     text 16.56/15.37/14.59/13.33 · text-2 9.87/9.16/8.69/7.94
     muted 6.20/5.76/5.47/4.99 — clears 4.5 on all four, surface-3 included. */
  --sbt-text: #1a1b3d;
  --sbt-text-2: #3c3f6e;
  --sbt-muted: #5b5e85;
  /* the fourth step. ⚠ NON-TEXT ONLY — icon strokes, dividers, disabled
     glyphs. 3.76/3.49/3.31/3.03, so it clears SC 1.4.11's 3:1 on every ground
     this theme has; that still does not make it a text colour. */
  --sbt-faint: #7e81a7;
  --sbt-border: #e4e6f6;
  --sbt-border-2: #cbcfec;
  /* periwinkle ramp. Same rule prime found the hard way: no blue-violet at its
     600 step carries white, so the solid is the deepened step and the softness
     lives in the tints. */
  --sbt-accent: #4f5bd5;         /* white on it 5.54 */
  --sbt-accent-strong: #3f4abb;  /* white on it 7.22 (hover) */
  --sbt-accent-weak: #f0f2fe;    /* the baby-blue chip · accent text on it 4.97 */
  --sbt-accent-tint: #e4e8fe;    /* stronger chip · accent text on it 4.56 */
  --sbt-accent-text: #4f5bd5;    /* 5.54 on surface, 4.88 on surface-2 */
  --sbt-accent-bright: #8fa0f5;  /* rails, rings, dots — 2.47 on white, so
                                    decorative only: never text, never state
                                    carried alone, never a fill behind a label */
  --sbt-accent-vivid: #7385f0;   /* the brightest periwinkle still clearing the
                                    3:1 non-text floor on white (3.31). MAY
                                    carry state — unread dots, indicator bars,
                                    rings. ⛔ NEVER a fill behind white text. */
  --sbt-on-accent: #ffffff;
  --sbt-nav-active-border: #7385f0;
  /* status — pastel fills, deepened solids. Solids measured on surface / bg /
     surface-2 / their own -weak fill, and each also carries white at >=5.33,
     so a solid badge keeps a white label whichever way it is used. */
  --sbt-success: #0e7a57;        /* 5.33/4.95/4.70/4.95 · white on it 5.33 */
  --sbt-success-weak: #e8fbf2;   /* baby mint */
  --sbt-warning: #99590f;        /* 5.54/5.14/4.88/5.13 · white on it 5.54 */
  --sbt-on-warning: #ffffff;
  --sbt-warning-weak: #fff5e6;   /* baby apricot */
  --sbt-danger: #c22a50;         /* 5.61/5.21/4.95/5.05 · white on it 5.61 */
  --sbt-danger-weak: #ffeff3;    /* baby rose */
  --sbt-info: #1a6d95;           /* 5.73/5.32/5.05/5.20 · white on it 5.73 */
  --sbt-info-weak: #e9f6fd;      /* baby sky */
  /* chart series — the CVD-validated light order, unchanged and complete. */
  --sbt-series-1: #2a78d6;
  --sbt-series-2: #eb6834;
  --sbt-series-3: #1baf7a;
  --sbt-series-4: #eda100;
  --sbt-series-5: #e87ba4;
  --sbt-series-6: #008300;
  --sbt-series-7: #4a3aa7;
  --sbt-series-8: #e34948;
  /* elevation is RING-FIRST, like prime, but the ring is tinted with the ink
     (#1a1b3d) rather than black: a neutral-black ring over a lilac ground
     reads as a dirty edge. Alphas are prime's, which are already at the light
     end of what shipping systems use. */
  --sbt-shadow: 0 0 0 1px rgba(26, 27, 61, .06), 0 1px 2px rgba(26, 27, 61, .04),
    0 2px 6px rgba(26, 27, 61, .04);
  --sbt-shadow-lg: 0 0 0 1px rgba(26, 27, 61, .07), 0 8px 24px rgba(26, 27, 61, .10),
    0 2px 6px rgba(26, 27, 61, .05);
}

/* ==========================================================================
   BLUEBELL DARK — bluebell's sibling, added by prime-dark's own rule: without
   a dark partner, a dark-preferring user who wants the periwinkle family is
   stranded by the theme cycle. Same family, same engine, re-stepped for a
   night ground: the neutrals are the bluebell ink (#1a1b3d) deepened into
   grounds rather than used as text, the pastels become low-alpha tints (a
   pastel FILL on a dark ground is paint, not light, so the -weak chips are
   alpha steps of the vivid hue), and every token that carries text is a
   brightened step of the same periwinkle.

   MEASURED, not asserted — on bg / surface / surface-2 / surface-3:
     · text 15.69/13.83/11.92/9.96 · text-2 11.51/10.14/8.74/7.30
     · muted 7.19/6.34/5.47/4.57 — clears 4.5:1 on ALL FOUR grounds,
       surface-3 included, keeping the floor bluebell holds in the light.
     · faint 4.84/4.27/3.68/3.07 — clears the 3:1 non-text floor on all four.
       ⚠ NON-TEXT ONLY, same as everywhere: icon strokes, dividers, disabled
       glyphs. Clearing 3:1 does not make it a timestamp colour.
   The solid follows prime-dark's hard-won rule: the deepest periwinkle that
   still reads as a button on the surface (3.22 against it) while carrying
   white at 4.92, and hover goes BRIGHTER, not darker (white on it 4.56).

   The chart series are the CVD-validated dark order, byte-for-byte from
   prime-dark. They are chosen for colour-vision-deficiency separation, which
   is a property of the set and not of the palette, and sbt-dash.js:35-45
   reads all eight from computed style, so all eight must be present.
   ========================================================================== */
html[data-sbt-theme="bluebell-dark"] { color-scheme: dark; }
html[data-sbt-theme="bluebell-dark"],
html[data-sbt-theme="bluebell-dark"] body { background: #12142f; }
html[data-sbt-theme="bluebell-dark"],
.sbt-theme-scope[data-sbt-theme="bluebell-dark"] {
  /* neutrals — the bluebell ink family as grounds: violet-leaning night, not
     neutral black, so cards rise as warm-dark lilac rather than grey slabs.
     bg -> surface is the same visible lift prime-dark uses. */
  --sbt-bg: #12142f;
  --sbt-surface: #1c1f42;
  --sbt-surface-2: #262a52;
  --sbt-surface-3: #313663;
  /* text ramp — measured values in the header comment. */
  --sbt-text: #eceffb;
  --sbt-text-2: #c9cdec;
  --sbt-muted: #9ba1cf;
  --sbt-faint: #7b81b5;          /* non-text only — 3.07 on surface-3 */
  --sbt-border: #2b2f59;
  --sbt-border-2: #454b82;
  /* periwinkle ramp, re-stepped for dark. */
  --sbt-accent: #5563e2;         /* white on it 4.92 · 3.22 against surface */
  --sbt-accent-strong: #5b69e6;  /* hover, brighter · white on it 4.56 */
  --sbt-accent-weak: rgba(139, 155, 247, .16);
  --sbt-accent-tint: rgba(139, 155, 247, .26);
  --sbt-accent-text: #a3b1fb;    /* 7.73 on surface, 8.78 on bg */
  --sbt-accent-bright: #a9b6fc;  /* rails, rings, dots — 8.14 on surface */
  --sbt-accent-vivid: #8b9bf7;   /* state-carrier — 6.14 on surface (WO5-7) */
  --sbt-on-accent: #ffffff;
  --sbt-nav-active-border: #8b9bf7;
  /* status — the baby-tint family carried to night: bright steps that clear
     AA as text on bg/surface/surface-2 (success 9.69/8.54/7.37 · warning
     10.70/9.43/8.13 · danger 7.30/6.43/5.55 · info 9.64/8.50/7.33), with the
     pastel softness moved into the alpha fills. */
  --sbt-success: #3bd6a0;        /* night mint */
  --sbt-success-weak: rgba(59, 214, 160, .14);
  --sbt-warning: #f5bf5b;        /* night apricot · white on it is 1.55 — the
                                    badge takes black (12.49) */
  --sbt-on-warning: #000000;
  --sbt-warning-weak: rgba(245, 191, 91, .14);
  --sbt-danger: #fb7d9c;         /* night rose */
  --sbt-danger-weak: rgba(251, 125, 156, .16);
  --sbt-info: #43cdf2;           /* night sky */
  --sbt-info-weak: rgba(67, 205, 242, .16);
  /* chart series — the CVD-validated dark order, unchanged and complete. */
  --sbt-series-1: #3987e5;
  --sbt-series-2: #d95926;
  --sbt-series-3: #199e70;
  --sbt-series-4: #c98500;
  --sbt-series-5: #d55181;
  --sbt-series-6: #008300;
  --sbt-series-7: #9085e9;
  --sbt-series-8: #e66767;
  /* elevation — ring-first at prime-dark's alphas, but the ring is the ink's
     own black-violet (#07 08 18) rather than neutral black: a neutral ring
     over a violet ground reads as a dirty edge, exactly the lesson bluebell
     learned in the light. */
  --sbt-shadow: 0 0 0 1px rgba(7, 8, 24, .55), 0 1px 3px rgba(7, 8, 24, .45);
  --sbt-shadow-lg: 0 0 0 1px rgba(7, 8, 24, .6), 0 12px 32px rgba(7, 8, 24, .55);
}

/* ---- aura base: cosmic gradient ground (ported from dtap .aura) ---- */
html[data-sbt-theme="aura"] {
  background: linear-gradient(to bottom right, #020617, #0f172a, #1e1b4b) !important;
}
/* aura's identity is the ground itself, not a --sbt-bg value, so a swatch that
   painted only the token would preview the one theme dishonestly. */
.sbt-theme-scope[data-sbt-theme="aura"] {
  background: linear-gradient(to bottom right, #020617, #0f172a, #1e1b4b) !important;
}
html[data-sbt-theme="aura"] body.sbt-body { background: transparent; }

/* ---- base ---- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body.sbt-body {
  margin: 0;
  font-family: var(--sbt-font);
  font-size: .9375rem;
  line-height: 1.5;
  color: var(--sbt-text);
  background: var(--sbt-bg);
  min-height: 100vh;
}
.sbt-body h1, .sbt-body h2, .sbt-body h3, .sbt-body h4, .sbt-body h5, .sbt-body h6 {
  margin: 0 0 .5rem; font-weight: 600; line-height: 1.3; color: var(--sbt-text);
}
.sbt-body h4 { font-size: 1.25rem; }
.sbt-body h5 { font-size: 1.05rem; }
.sbt-body p { margin: 0 0 .75rem; }
.sbt-body a { color: var(--sbt-accent-text); text-decoration: none; }
.sbt-body a:hover { text-decoration: underline; }
.sbt-body ::placeholder { color: var(--sbt-muted); opacity: .8; }
/* Long unbroken tokens — an API key, a URL, a JSON blob — must never widen the
   page. An inline <code> offers no wrap opportunity inside a long token, and a
   <pre> that already sets overflow-x:auto still cannot shrink while it is a
   flex item, because a flex item's default min-width:auto refuses to go below
   its content width. Together those overflowed /settings/api-keys/docs by 119px
   at 390px — in every one of the five themes, so these are base rules and not
   prime-scoped. `overflow-wrap` has no effect inside <pre> (white-space:pre
   creates no soft wrap opportunities), so code blocks keep scrolling instead. */
.sbt-body code { overflow-wrap: anywhere; }
.sbt-body pre { max-width: 100%; min-width: 0; overflow-x: auto; }

/* ---- aura background effects (rendered always, visible only under aura;
        ported verbatim from dtap app.css) ---- */
.sbt-aura-fx { display: none; }
html[data-sbt-theme="aura"] .sbt-aura-fx {
  display: block; position: fixed; inset: 0; z-index: 0;
  pointer-events: none; overflow: hidden;
}
.aura-geometric {
  position: absolute; inset: 0;
  background-image:
    linear-gradient(30deg, rgba(99,102,241,0.02) 12%, transparent 12.5%, transparent 87%, rgba(99,102,241,0.02) 87.5%),
    linear-gradient(150deg, rgba(99,102,241,0.02) 12%, transparent 12.5%, transparent 87%, rgba(99,102,241,0.02) 87.5%),
    linear-gradient(30deg, rgba(99,102,241,0.02) 12%, transparent 12.5%, transparent 87%, rgba(99,102,241,0.02) 87.5%),
    linear-gradient(150deg, rgba(99,102,241,0.02) 12%, transparent 12.5%, transparent 87%, rgba(99,102,241,0.02) 87.5%);
  background-size: 80px 140px;
  background-position: 0 0, 0 0, 40px 70px, 40px 70px;
}
.aura-grid {
  position: absolute; inset: 0;
  background-image:
    linear-gradient(rgba(99,102,241,0.015) 1px, transparent 1px),
    linear-gradient(90deg, rgba(99,102,241,0.015) 1px, transparent 1px);
  background-size: 64px 64px;
}
.aura-noise {
  position: absolute; inset: 0; opacity: 0.02;
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-repeat: repeat; background-size: 256px 256px;
}
.aura-orb {
  position: absolute; border-radius: 9999px; filter: blur(48px);
  animation: aura-pulse-glow 8s ease-in-out infinite;
}
.aura-orb-1 {
  top: 0; left: 25%; width: 300px; height: 300px;
  background: linear-gradient(to bottom right, rgba(139,92,246,0.1), rgba(99,102,241,0.1));
}
.aura-orb-2 {
  bottom: 0; right: 25%; width: 250px; height: 250px;
  background: linear-gradient(to bottom right, rgba(37,99,235,0.1), rgba(6,182,212,0.1));
}
.aura-orb-3 {
  top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 400px; height: 400px;
  background: linear-gradient(to bottom right, rgba(168,85,247,0.05), rgba(236,72,153,0.05));
}
.aura-delay-1 { animation-delay: -1s; }
.aura-delay-2 { animation-delay: -2s; }
.aura-delay-4 { animation-delay: -4s; }

/* Everything above the fx layer */
.sbt-nav, .sbt-main, .sbt-toasts { position: relative; z-index: 1; }

/* ---- icons (inline SVG sprite) ---- */
.si {
  width: 1.15em; height: 1.15em; stroke: currentColor; fill: none;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
  vertical-align: -.2em; flex: 0 0 auto;
}
.si-lg { width: 1.5em; height: 1.5em; }
.si-xl { width: 2.4em; height: 2.4em; }

/* ---- top navigation (dtap structure: h-16 bar; light/dark = Breeze underline
        links; aura = glass bar + pill links) ---- */
.sbt-nav {
  background: var(--sbt-surface);
  border-bottom: 1px solid var(--sbt-border);
  position: sticky; top: 0; z-index: 900;
}
.sbt-nav-inner {
  max-width: 1400px; margin: 0 auto; padding: 0 1.25rem;
  display: flex; align-items: stretch; gap: .25rem; min-height: 64px; flex-wrap: wrap;
}
.sbt-brand {
  display: flex; align-items: center; gap: .55rem; margin-right: 1.75rem;
  font-weight: 700; font-size: 1.05rem; color: var(--sbt-text) !important;
  text-decoration: none !important;
}
.sbt-brand-mark {
  width: 30px; height: 30px; border-radius: 9px; display: inline-flex;
  align-items: center; justify-content: center; color: #fff;
  /* a11y (A-15): an 8% scrim under the letter — white on the bare gradient
     reads 4.23..4.47, with the scrim 4.87..5.12, hue untouched. Aura replaces
     this background wholesale (its inner span is near-black) and is unaffected. */
  background: linear-gradient(rgba(0,0,0,.08), rgba(0,0,0,.08)), linear-gradient(135deg, #6366f1, #8b5cf6);
  font-size: .8rem; font-weight: 800;
}
.sbt-nav-links { display: flex; align-items: stretch; gap: 1.25rem; flex-wrap: wrap; }
/* underline nav item (dtap/Breeze NavLink): border-b-2, no radius, full height */
.sbt-nav-link {
  display: inline-flex; align-items: center; gap: .4rem;
  padding: 2px .15rem 0; border: 0; border-bottom: 2px solid transparent;
  font-size: .875rem; font-weight: 500; line-height: 1.4;
  color: var(--sbt-muted) !important; text-decoration: none !important;
  background: none; cursor: pointer; font-family: inherit;
  transition: color .12s, border-color .12s;
}
.sbt-nav-link:hover {
  color: var(--sbt-text-2) !important; border-bottom-color: var(--sbt-border-2);
}
.sbt-nav-link.active {
  color: var(--sbt-text) !important; border-bottom-color: var(--sbt-nav-active-border);
}
/* Its children are rem-sized and `white-space: nowrap`, so a reader who raises
   the browser's default font size grows this row without letting it break: at a
   32px root (the WCAG 1.4.4 200% bar) it needs 483px inside a 390px bar and
   pushed an icon button clean off the screen, on every page of the suite. The
   wrap is a no-op wherever the row already fits — at the default 16px root the
   whole bar measures byte-identical in all five themes. */
.sbt-nav-right { margin-left: auto; display: flex; flex-wrap: wrap; align-items: center; gap: .35rem; }
.sbt-nav-burger { display: none; }

/* aura nav: glass bar + pill links (dtap .aura-nav / .aura-nav-link) */
html[data-sbt-theme="aura"] .sbt-nav {
  background: rgba(15,23,42,0.5);
  backdrop-filter: blur(24px);
  -webkit-backdrop-filter: blur(24px);
  border-bottom: 1px solid rgba(51,65,85,0.5);
}
html[data-sbt-theme="aura"] .sbt-nav-links { gap: .3rem; align-items: center; }
html[data-sbt-theme="aura"] .sbt-nav-link {
  padding: .5rem .75rem; border-bottom: 0; border-radius: .5rem;
  color: #94a3b8 !important; transition: all 0.2s;
}
html[data-sbt-theme="aura"] .sbt-nav-link:hover {
  color: #fff !important; background: rgba(30,41,59,0.5);
}
html[data-sbt-theme="aura"] .sbt-nav-link.active {
  color: #fff !important; background: rgba(30,41,59,0.8) !important;
  box-shadow: 0 10px 15px -3px rgba(99,102,241,0.1);
}
/* aura brand: gradient-ring logo + gradient wordmark (dtap aura-logo-*) */
html[data-sbt-theme="aura"] .sbt-brand-mark {
  background: linear-gradient(to bottom right, #6366f1, #a855f7, #ec4899);
  padding: 2px; border-radius: .65rem;
}
html[data-sbt-theme="aura"] .sbt-brand-mark > span {
  background: #0f172a; border-radius: calc(.65rem - 2px);
  width: 100%; height: 100%; display: flex; align-items: center; justify-content: center;
}
.sbt-brand-mark > span { width: 100%; height: 100%; display: flex;
  align-items: center; justify-content: center; }
html[data-sbt-theme="aura"] .sbt-brand-text {
  background: linear-gradient(to right, #fff, #cbd5e1);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent;
}

@media (max-width: 991px) {
  .sbt-nav-inner { align-items: center; }
  .sbt-nav-links {
    display: none; width: 100%; flex-direction: column; align-items: stretch;
    gap: .125rem; padding: .5rem 0; border-top: 1px solid var(--sbt-border); margin-top: .25rem;
  }
  .sbt-nav-links.open { display: flex; }
  .sbt-nav-burger { display: inline-flex; }
  /* stacked mobile items: pill style regardless of theme */
  .sbt-nav-link { border-bottom: 0; border-radius: 8px; padding: .5rem .7rem; }
  .sbt-nav-link:hover { background: var(--sbt-surface-2); }
  .sbt-nav-link.active { background: var(--sbt-accent-weak); color: var(--sbt-accent-text) !important; }
  .sbt-dropdown { position: static; }
  .sbt-nav-links .sbt-dropdown-menu { position: static; box-shadow: none; border: 0;
    padding-left: 1.5rem; min-width: 0; background: transparent; }
}

/* ---- dropdowns (dtap submenu: white/gray-800 rounded-md shadow; active item
        indigo-50/indigo-700 + right-side indigo dot) ---- */
.sbt-dropdown { position: relative; display: flex; align-items: stretch; }
.sbt-dropdown-menu {
  position: absolute; top: calc(100% + 2px); left: 0; min-width: 220px;
  background: var(--sbt-surface); border: 1px solid var(--sbt-border);
  border-radius: var(--sbt-radius); box-shadow: var(--sbt-shadow-lg);
  padding: .4rem; display: none; z-index: 1000;
}
.sbt-dropdown-menu.end { left: auto; right: 0; }
.sbt-dropdown.open > .sbt-dropdown-menu { display: block; }
.sbt-dropdown-item {
  display: flex; align-items: center; gap: .5rem; width: 100%;
  padding: .45rem .65rem; border-radius: 7px; font-size: .875rem;
  color: var(--sbt-text-2) !important; text-decoration: none !important;
  border: 0; background: none; cursor: pointer; text-align: left; font-family: inherit;
}
.sbt-dropdown-item:hover { background: var(--sbt-surface-2); color: var(--sbt-text) !important; }
.sbt-dropdown-item.active {
  background: var(--sbt-accent-weak); color: var(--sbt-accent-text) !important;
}
.sbt-dropdown-item.active::after {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: var(--sbt-accent); margin-left: auto; flex: 0 0 auto;
}
.sbt-dropdown-header {
  padding: .35rem .65rem .2rem; font-size: .7rem; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; color: var(--sbt-muted);
}
.sbt-dropdown-divider { border-top: 1px solid var(--sbt-border); margin: .35rem 0; }
html[data-sbt-theme="aura"] .sbt-dropdown-menu {
  background: #1e293b; border: 1px solid rgba(51,65,85,0.5);
  border-radius: .75rem; box-shadow: 0 25px 50px -12px rgba(0,0,0,0.5);
}

/* ---- user chip / avatar ---- */
.sbt-avatar {
  width: 30px; height: 30px; border-radius: 50%; display: inline-flex;
  align-items: center; justify-content: center; flex: 0 0 auto;
  background: var(--sbt-accent-weak); color: var(--sbt-accent-text);
  font-weight: 600; font-size: .75rem;
  position: relative; overflow: hidden;
}

/* WO5-9 (#319) — one profile photo, wherever an identity avatar is drawn.
   SBT.avatarInner() emits this INSIDE the initials, not instead of them, so a
   photo that 404s or is blocked reveals the initials underneath rather than an
   empty coloured disc. That is why every avatar wrapper carries
   `position: relative` and this is absolute.

   ⚠ Theme-agnostic ON PURPOSE, and it is the one part of WO5-9 that is not
   prime-only: a photo is DATA, not styling, so it would be absurd for someone's
   face to disappear when they switch to `dark`. The richer initials RAMP below
   stays prime-scoped; with no photo uploaded — which is every user until they
   upload one — light/dark/aura render exactly what they rendered before. */
.sbt-av-img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; border-radius: inherit; display: block;
}
html[data-sbt-theme="aura"] .sbt-avatar {
  background: linear-gradient(to bottom right, #6366f1, #a855f7); color: #fff;
}
.sbt-user-btn {
  display: inline-flex; align-items: center; gap: .5rem; padding: .35rem .6rem;
  border: 1px solid var(--sbt-border); border-radius: 10px; cursor: pointer;
  background: var(--sbt-surface); color: var(--sbt-text-2);
  font-family: inherit; font-size: .875rem; font-weight: 500;
}
.sbt-user-btn:hover { background: var(--sbt-surface-2); }
html[data-sbt-theme="aura"] .sbt-user-btn {
  background: rgba(30,41,59,0.5); border-color: rgba(51,65,85,0.5); color: #e2e8f0;
}
html[data-sbt-theme="aura"] .sbt-user-btn:hover { background: rgba(30,41,59,0.8); }

/* ---- page scaffold ---- */
.sbt-main { max-width: 1400px; margin: 0 auto; padding: 1.5rem 1.25rem 3rem; }
/* ---- WO5-8: the app frame (workorder 05 §14c) ----------------------------
   The two Dribbble references are application WINDOWS: the app owns the
   viewport, and there is no page title, no subtitle and no centred column.
   Ours was a document page with the app inside a bordered card under ~250px of
   chrome, which accounts for more of the visual gap than every colour token
   combined (§14a-4).

   Opt-in via `.is-app` (the blade sets it) AND prime-scoped AND >=1024px:
     - the other 29 pages never see it (no `.is-app`),
     - light/dark/aura keep the centred document page (prime scope),
     - phone and tablet keep the existing one-pane machine, which §1c says is
       already good and must be preserved (the min-width bound).
   ⚠ The min-width bound is not optional: sbt.css's own narrow blocks live
   earlier at lower specificity, the same ordering trap as §12 defect 4b. */
@media (min-width: 1024px) {
  html[data-sbt-theme^="prime"] .sbt-main.is-app {
    max-width: none;
    padding: 0;
  }
  /* The app supplies its own title where the references put it — at the top of
     the list pane — so the page head is redundant chrome here. */
  html[data-sbt-theme^="prime"] .sbt-main.is-app .sbt-page-head { display: none; }

  /* `padding: 0` on the main is what lets the PANES reach the window edge, the
     way both references do. The bands ABOVE the panes are not panes, so they
     would otherwise sit flush against the glass. Give those their own inline
     padding rather than re-padding the main. ⚠ Only the bands: adding this to
     the pane wrap would inset the panes and undo the frame. */
  html[data-sbt-theme^="prime"] .sbt-main.is-app .ml-seg,
  html[data-sbt-theme^="prime"] .sbt-main.is-app .ml-mail-head {
    padding-inline: var(--sbt-sp-4);
  }
  html[data-sbt-theme^="prime"] .sbt-main.is-app .ml-seg { padding-top: var(--sbt-sp-3); }
}

.sbt-page-head { display: flex; flex-wrap: wrap; justify-content: space-between;
  align-items: flex-start; gap: 1rem; margin-bottom: 1.25rem; }
.sbt-page-head p { color: var(--sbt-muted); margin: 0; }

/* ---- cards ---- */
.sbt-card {
  background: var(--sbt-surface); border: 1px solid var(--sbt-border);
  border-radius: var(--sbt-radius); box-shadow: var(--sbt-shadow); margin-bottom: 1.5rem;
}
.sbt-card-header {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: .75rem; padding: .9rem 1.15rem; border-bottom: 1px solid var(--sbt-border);
}
.sbt-card-header h5 { margin: 0; display: flex; align-items: center; gap: .5rem; }
.sbt-card-body { padding: 1.15rem; }
/* aura glass card (dtap .aura-glass-card) */
html[data-sbt-theme="aura"] .sbt-card {
  background: linear-gradient(135deg, rgba(15,23,42,0.8), rgba(30,41,59,0.6));
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border: 1px solid rgba(51,65,85,0.5);
  border-radius: 1rem;
  transition: border-color .25s ease, box-shadow .25s ease;
}
html[data-sbt-theme="aura"] .sbt-card:hover {
  border-color: rgba(139,92,246,0.34);
  box-shadow: 0 8px 34px rgba(99,102,241,0.20);
}
html[data-sbt-theme="aura"] .sbt-card-header { border-bottom-color: rgba(51,65,85,0.5); }

/* ---- buttons ---- */
.sbt-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .4rem;
  padding: .5rem .95rem; border-radius: 8px; font-size: .875rem; font-weight: 500;
  font-family: inherit; line-height: 1.4; cursor: pointer; white-space: nowrap;
  border: 1px solid transparent; background: var(--sbt-surface-2);
  color: var(--sbt-text-2); text-decoration: none !important;
  transition: background .12s, border-color .12s, color .12s;
}
.sbt-btn:hover { background: var(--sbt-surface-3); text-decoration: none !important; }
.sbt-btn:disabled { opacity: .5; cursor: not-allowed; }
.sbt-btn-sm { padding: .32rem .65rem; font-size: .8125rem; border-radius: 7px; }
.sbt-btn-xs { padding: .18rem .45rem; font-size: .75rem; border-radius: 6px; }
.sbt-btn-primary { background: var(--sbt-accent); color: #fff; }
.sbt-btn-primary:hover { background: var(--sbt-accent-strong); color: #fff; }
/* `.sbt-body a` (line 392) is a more specific selector than `.sbt-btn-primary`,
   so an <a> styled as a primary button took the LINK colour instead of white —
   and in prime-light --sbt-accent-text and --sbt-accent are the same value, so
   the label sat on its own background at a contrast ratio of 1.00 and was
   simply invisible. An anchor button must render identically to a <button>. */
.sbt-body a.sbt-btn-primary,
.sbt-body a.sbt-btn-primary:hover { color: #fff; }
.sbt-btn-outline { background: transparent; border-color: var(--sbt-border-2); }
.sbt-btn-outline:hover { background: var(--sbt-surface-2); }
.sbt-btn-danger { background: var(--sbt-danger-weak); color: var(--sbt-danger); }
.sbt-btn-danger:hover { background: var(--sbt-danger); color: #fff; }
.sbt-btn-danger-solid { background: var(--sbt-danger); color: #fff; }
.sbt-btn-danger-solid:hover { filter: brightness(.92); color: #fff; }
/* The rule above sets a filter and a colour but no BACKGROUND, so the generic
   `.sbt-btn:hover` (line 718, one class more specific than the danger rule)
   wins and repaints the button in --sbt-surface-3. On the light themes that is
   a pale grey and the white label on it measured 1.21:1 in prime-light and
   1.10:1 in light: the "Delete all" button's own text vanished the moment a
   pointer touched it. Restoring the fill is the functional half of the fix, so
   it reaches `light` too.
   The dark-surface themes take a LIGHT rose for --sbt-danger (#fb7185), where
   white is 2.69:1 and black is 7.80:1 — the same finding the bell badge already
   carries at line 1143, and the same convention as this theme's own
   --sbt-on-warning. prime-dark is in scope so it takes both halves. `dark` and
   `aura` stay byte-identical: their idle 2.69 is the legacy ruling parked since
   c72, and giving them the fill back without the black label would have made
   their hover worse, so it is deliberately not done. */
html[data-sbt-theme="prime-light"] .sbt-btn-danger-solid:hover,
html[data-sbt-theme="light"] .sbt-btn-danger-solid:hover,
html[data-sbt-theme="prime-dark"] .sbt-btn-danger-solid:hover { background: var(--sbt-danger); }
html[data-sbt-theme="prime-dark"] .sbt-btn-danger-solid,
html[data-sbt-theme="prime-dark"] .sbt-btn-danger-solid:hover { color: #000; }
.sbt-btn-icon { padding: .38rem; width: 2.1rem; height: 2.1rem; }
.sbt-btn-icon.sbt-btn-sm { width: 1.85rem; height: 1.85rem; padding: .28rem; }
/* aura primary: animated indigo→purple gradient + shimmer (dtap .aura-btn-primary) */
html[data-sbt-theme="aura"] .sbt-btn-primary {
  position: relative; overflow: hidden;
  background: linear-gradient(to right, #4f46e5, #9333ea, #4f46e5);
  background-size: 200% 100%;
  animation: aura-gradient-shift 8s ease infinite;
  border: none; transition: all 0.2s;
}
html[data-sbt-theme="aura"] .sbt-btn-primary:hover {
  box-shadow: 0 0 20px rgba(99,102,241,0.4);
}
html[data-sbt-theme="aura"] .sbt-btn-primary::after {
  content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
  animation: aura-shimmer 3s infinite;
}
html[data-sbt-theme="aura"] .sbt-btn {
  background-color: rgba(30,41,59,0.5); border-color: rgba(51,65,85,0.5);
}
html[data-sbt-theme="aura"] .sbt-btn:hover { background-color: rgba(30,41,59,0.8); }
html[data-sbt-theme="aura"] .sbt-btn-primary,
html[data-sbt-theme="aura"] .sbt-btn-primary:hover { border: none; }
html[data-sbt-theme="aura"] .sbt-btn-danger { background: var(--sbt-danger-weak); }
html[data-sbt-theme="aura"] .sbt-btn-danger:hover { background: var(--sbt-danger); }
html[data-sbt-theme="aura"] .sbt-btn-danger-solid { background: var(--sbt-danger); }

/* ---- badges ---- */
.sbt-badge {
  display: inline-flex; align-items: center; gap: .3rem; padding: .18rem .55rem;
  border-radius: 999px; font-size: .72rem; font-weight: 600; line-height: 1.35;
  background: var(--sbt-surface-3); color: var(--sbt-text-2); white-space: nowrap;
}
.sbt-badge-accent { background: var(--sbt-accent-weak); color: var(--sbt-accent-text); }
.sbt-badge-success { background: var(--sbt-success-weak); color: var(--sbt-success); }
.sbt-badge-warning { background: var(--sbt-warning-weak); color: var(--sbt-warning); }
.sbt-badge-danger { background: var(--sbt-danger-weak); color: var(--sbt-danger); }
.sbt-badge-info { background: var(--sbt-info-weak); color: var(--sbt-info); }
.sbt-badge-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }

/* #372 -- the one real AA failure of the 465-cell sweep (w05 SS16b): under the
   legacy light theme the four badge families read 3.07-4.28 on their own tints,
   because the base token was chosen to sit on a white surface and the tint is
   not white. Measured on the shipped values:
                current            ->  stepped
     success    #059669 3.58           #047857 5.21
     warning    #d97706 3.07           #a2570a 5.19
     danger     #e11d48 4.28           #c81e42 5.14
     info       #0891b2 3.54           #0e6f8c 5.50
   Badge-only and light-only: the tints do not move, and every other use of
   --sbt-success / -warning / -danger / -info is untouched, so nothing that
   already passes is restyled to fix something that did not. `light` is always
   stamped explicitly (the no-attribute state maps to the dark palette above),
   so scoping by the attribute reaches every light-theme page. */
html[data-sbt-theme="light"] .sbt-badge-success { color: #047857; }
html[data-sbt-theme="light"] .sbt-badge-warning { color: #a2570a; }
html[data-sbt-theme="light"] .sbt-badge-danger  { color: #c81e42; }
html[data-sbt-theme="light"] .sbt-badge-info    { color: #0e6f8c; }

/* ---- alerts ---- */
.sbt-alert {
  display: flex; align-items: flex-start; gap: .6rem; padding: .7rem .95rem;
  border-radius: var(--sbt-radius-sm); font-size: .875rem; margin-bottom: 1rem;
  border: 1px solid transparent;
}
.sbt-alert-info { background: var(--sbt-info-weak); color: var(--sbt-info); }
.sbt-alert-warning { background: var(--sbt-warning-weak); color: var(--sbt-warning); }
.sbt-alert-danger { background: var(--sbt-danger-weak); color: var(--sbt-danger); }
.sbt-alert-success { background: var(--sbt-success-weak); color: var(--sbt-success); }

/* manu code for erasure banner overlap starts here */
/* ---- G6d: the company-wide erasure cooling-off banner ----
   A strip at the top of every page, for every member of a company with a live
   erasure request. Every colour is a token, so it follows the viewer's theme
   like the rest of the app.
   It no longer sticks on its own: it was sticky at top 0 with z-index 1990
   (under the impersonation banner's 2000, over the nav's 900), and once a page
   scrolled it lay over the nav and over the top edge of every overlay. It now
   sticks as part of .sbt-chrome below, one block with the nav. */
.sbt-erasure-banner {
/* manu code for erasure banner overlap ends here */
  display: flex; align-items: center; justify-content: center;
  gap: .55rem; padding: .55rem 1rem;
  background: var(--sbt-danger-weak); color: var(--sbt-danger);
  border-bottom: 1px solid var(--sbt-danger);
  font-size: .875rem; line-height: 1.35;
}
/* Deliberately NOT flex-wrap. A flex line breaks on an item's CONTENT width
   before any shrinking, so a wrapping container puts this sentence — and then
   the link — on lines of their own: measured at 109px of sticky chrome on a
   780px viewport. Without it the sentence wraps INSIDE its own column and the
   row stays one row. */
.sbt-erasure-banner > span { min-width: 0; }
.sbt-erasure-banner .si { width: 1rem; height: 1rem; flex: none; }
.sbt-erasure-banner-link {
  color: var(--sbt-danger); font-weight: 600; text-decoration: underline;
  text-underline-offset: 2px; white-space: nowrap;
}
/* manu code for erasure banner overlap starts here */
/* ---- the sticky top chrome: the erasure banners and the nav as ONE block ----
   The layout wraps both in .sbt-chrome. Each used to stick to top 0 on its
   own, so once a page scrolled the banner lay over the nav — measured c321 on
   dev: at 1280px every nav control's centre was under the 37px banner, and at
   390px the whole 79px nav was under the 94px banner, with the menu button's
   centre on the banner's "Review it" link. Stacked in one sticky block they
   cannot overlap. The nav keeps its own sticky rule: inside a block that is
   exactly the banners plus the nav it never moves, and it stays the
   containing block that the bell and money menus pin to on a phone.
   z-index 1030 keeps the block above the page and the nav's own menus and
   BELOW every overlay: the task/ticket drawer's veil (1040) and panel (1041),
   tooltips (1050), the assistant (1085/1086), the chat lightbox (1090),
   context menus (1100), modals (1200), toasts (1400) and confirm dialogs
   (1500). At 1990 the banner sat over all of them: the drawer's History,
   Follow, Archive, Delete and Close buttons (y 10-40) were under it at every
   width, and at 390px so was the History panel's close button. The
   impersonation banner (2000) still sits above everything: it is about who
   you currently ARE. */
.sbt-chrome { position: sticky; top: 0; z-index: 1030; }
/* manu code for erasure banner overlap ends here */
/* manu code for impersonation banner overlap starts here */
/* ---- the impersonation banner: above everything, and everything below it --
   The banner keeps its place OVER every overlay (2000 — who you ARE), but it
   used to be the erasure story again, one storey up: sticky at top 0 it lay
   over the chrome once a page scrolled (c460 measured on dev: all 11 nav
   control centres under its 38px at 1280px), over the task drawer's head at
   ANY scroll (History, Follow, Archive, Delete, Close — centres y 24), and
   at 390px, where it wraps to 97px, over a modal's close button (centre y
   62). It cannot join .sbt-chrome — it must outrank the overlays the chrome
   sits under — so instead sbt.js publishes its height as --sbt-imp-h and the
   three surfaces start below it: the chrome (here), the drawer (work.css
   #wk-drawer) and the modal band (.sbt-modal-backdrop below, which is also
   every confirm dialog's shell). Without an impersonation the variable is
   unset and every rule falls back to 0. */
.sbt-imp-banner { position: sticky; top: 0; z-index: 2000; font-size: .9rem; }
.sbt-chrome { top: var(--sbt-imp-h, 0px); }
/* manu code for impersonation banner overlap ends here */

/* ---- breadcrumb ---- */
.sbt-breadcrumb {
  display: flex; flex-wrap: wrap; align-items: center; gap: .35rem;
  list-style: none; margin: 0 0 1rem; padding: 0; font-size: .875rem;
}
.sbt-breadcrumb li { display: flex; align-items: center; gap: .35rem; }
.sbt-breadcrumb li + li::before { content: "/"; color: var(--sbt-muted); opacity: .6; }
.sbt-breadcrumb .current { color: var(--sbt-muted); }

/* ---- tables ---- */
.sbt-table-wrap { overflow-x: auto; }
.sbt-table { width: 100%; border-collapse: collapse; font-size: .875rem; }
.sbt-table th {
  text-align: left; padding: .6rem .75rem; font-size: .72rem; font-weight: 600;
  letter-spacing: .04em; text-transform: uppercase; color: var(--sbt-muted);
  border-bottom: 1px solid var(--sbt-border); white-space: nowrap;
}
.sbt-table td {
  padding: .55rem .75rem; border-bottom: 1px solid var(--sbt-border);
  vertical-align: middle;
}
.sbt-table tbody tr:last-child td { border-bottom: 0; }
.sbt-table tbody tr:hover { background: var(--sbt-surface-2); }
html[data-sbt-theme="aura"] .sbt-table tbody tr:hover { background: rgba(30,41,59,0.5); }
.sbt-table th.sortable { cursor: pointer; user-select: none; }
.sbt-table th.sortable:hover { color: var(--sbt-text-2); }

/* A-18 (c69/c73): opt-in pinned last column for tables whose per-row controls
   live in their final column. On a phone — and at 200% text — those controls
   otherwise sit behind a sideways scroll with nothing on screen saying the
   region scrolls. Reachability, not styling: every theme gets it, only below
   640px. Colspan cells (Loading… / empty / Load more) are never pinned. */
@media (max-width: 640px) {
  .sbt-table-pin thead th:last-child,
  .sbt-table-pin tbody td:last-child:not([colspan]) {
    position: sticky; right: 0; z-index: 2;
    background: var(--sbt-surface);
    box-shadow: -8px 0 8px -8px rgba(0, 0, 0, .35);
  }
  .sbt-table-pin thead th:last-child { z-index: 3; }
  .sbt-table-pin tbody tr:hover td:last-child:not([colspan]) { background: var(--sbt-surface-2); }
  /* aura paints cards as a translucent gradient; --sbt-surface would read as
     a foreign block behind the pinned cell — use the gradient's first stop */
  html[data-sbt-theme="aura"] .sbt-table-pin thead th:last-child,
  html[data-sbt-theme="aura"] .sbt-table-pin tbody td:last-child:not([colspan]) {
    background: rgba(15, 23, 42, .96);
  }
  /* A-18 companions: what actually pushes a phone page sideways at 200% text
     is the page-head's action row (no wrap) and its nowrap button labels —
     let the row wrap and the labels break. Empty-state CTAs share the shape. */
  .sbt-page-head .d-flex { flex-wrap: wrap; }
  .sbt-page-head .sbt-btn, .sbt-empty .sbt-btn { white-space: normal; }
  /* A pinned cell wider than the scroll wrap cannot be pinned — the browser
     clamps it at the row edge and the controls hang off screen. Cap the
     cell's content and let action groups and text labels wrap inside it. */
  .sbt-table-pin tbody td:last-child:not([colspan]) > * { max-width: 220px; }
  .sbt-table-pin td:last-child .d-inline-flex { flex-wrap: wrap; justify-content: flex-end; }
  .sbt-table-pin td:last-child .d-inline-flex > * { min-width: 0; max-width: 100%; }
  .sbt-table-pin td:last-child .sbt-btn { white-space: normal; word-break: break-word; }
  /* Pagination rows (Prev · Page N of M · Next) run a few px over at 200%. */
  .sbt-card-body.d-flex { flex-wrap: wrap; }
}
.sbt-table .text-end { text-align: right; }

/* ---- forms ---- */
.sbt-label { display: block; font-size: .8125rem; font-weight: 500; margin-bottom: .3rem;
  color: var(--sbt-text-2); }
.sbt-input, .sbt-select, .sbt-textarea {
  width: 100%; padding: .48rem .7rem; border-radius: 8px; font-size: .875rem;
  font-family: inherit; color: var(--sbt-text);
  background: var(--sbt-surface); border: 1px solid var(--sbt-border-2);
  transition: border-color .12s, box-shadow .12s;
}
.sbt-input:focus, .sbt-select:focus, .sbt-textarea:focus {
  outline: none; border-color: var(--sbt-accent);
  box-shadow: 0 0 0 3px var(--sbt-accent-weak);
}
/* aura inputs (dtap .aura-input) */
html[data-sbt-theme="aura"] .sbt-input,
html[data-sbt-theme="aura"] .sbt-select,
html[data-sbt-theme="aura"] .sbt-textarea {
  background-color: rgba(15,23,42,0.5); border-color: rgba(51,65,85,0.5); color: #fff;
}
html[data-sbt-theme="aura"] .sbt-input::placeholder,
html[data-sbt-theme="aura"] .sbt-textarea::placeholder { color: #64748b; }
html[data-sbt-theme="aura"] .sbt-input:focus,
html[data-sbt-theme="aura"] .sbt-select:focus,
html[data-sbt-theme="aura"] .sbt-textarea:focus {
  border-color: rgba(99,102,241,0.5);
  box-shadow: 0 0 0 3px rgba(99,102,241,0.1), 0 0 20px rgba(99,102,241,0.2);
  outline: none;
}
.sbt-input-sm, .sbt-select-sm { padding: .3rem .55rem; font-size: .8125rem; }
select.sbt-select { -webkit-appearance: none; appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right .6rem center; padding-right: 2rem; }

/* A-42: no text entry under 16px on a touch-primary device — below that,
   iOS Safari auto-zooms the page the moment the field is tapped (the viewport
   meta rightly allows zoom, WCAG 1.4.4, so the only fix is the font size).
   Element selectors + !important on purpose: later page sheets set their own
   control sizes (13/13.12px measured across all 31 URLs) and every one of
   them must lose here, or the zoom comes back on that one field. Buttons are
   untouched — focusing a button never zooms. Desktop keeps its density: the
   query never matches a mouse/trackpad-primary machine. */
@media (hover: none) and (pointer: coarse) {
  input, select, textarea, [contenteditable="true"] { font-size: 16px !important; }
}
.sbt-check { width: 1rem; height: 1rem; accent-color: var(--sbt-accent); cursor: pointer; }

/* ---- modal ---- */
.sbt-modal-backdrop {
  position: fixed; inset: 0; background: rgba(3, 7, 18, .55); z-index: 1200;
  display: none; align-items: flex-start; justify-content: center;
  padding: 4vh 1rem; overflow-y: auto;
  /* manu code for impersonation banner overlap starts here */
  /* Below the impersonation banner (2000): the band starts under it so a
     modal's close button stays clickable — at 390px the wrapped banner (97px,
     measured c460) reached past the close button's centre (y 62). Confirm
     dialogs reuse this shell (dialogShell, z 1500), so this line is theirs
     too. Unset outside an impersonation -> 0. */
  top: var(--sbt-imp-h, 0px);
  /* manu code for impersonation banner overlap ends here */
}
.sbt-modal-backdrop.open { display: flex; }
.sbt-modal {
  background: var(--sbt-surface); border: 1px solid var(--sbt-border);
  border-radius: 14px; box-shadow: var(--sbt-shadow-lg);
  width: 100%; max-width: 560px; margin-bottom: 4vh;
}
html[data-sbt-theme="aura"] .sbt-modal {
  background: #1e293b; border-color: rgba(51,65,85,0.5);
  box-shadow: 0 25px 50px -12px rgba(0,0,0,0.5);
}
.sbt-modal-lg { max-width: 860px; }
.sbt-modal-xl { max-width: 1140px; }
.sbt-modal-header {
  display: flex; align-items: center; gap: .75rem; padding: .9rem 1.15rem;
  border-bottom: 1px solid var(--sbt-border);
}
.sbt-modal-header h5 { margin: 0; flex: 1; display: flex; align-items: center;
  gap: .5rem; min-width: 0; }
.sbt-modal-body { padding: 1.15rem; }
.sbt-modal-footer {
  display: flex; justify-content: flex-end; gap: .5rem; padding: .85rem 1.15rem;
  border-top: 1px solid var(--sbt-border);
}
.sbt-modal-close {
  border: 0; background: none; cursor: pointer; color: var(--sbt-muted);
  padding: .3rem; border-radius: 6px; display: inline-flex;
}
.sbt-modal-close:hover { background: var(--sbt-surface-2); color: var(--sbt-text); }

/* ---- toasts ---- */
.sbt-toasts {
  position: fixed; top: 1rem; right: 1rem; z-index: 1400;
  display: flex; flex-direction: column; gap: .5rem; max-width: 340px;
}
.sbt-toast {
  display: flex; align-items: center; gap: .55rem; padding: .65rem .9rem;
  border-radius: 10px; font-size: .875rem; font-weight: 500;
  background: var(--sbt-surface); color: var(--sbt-text);
  border: 1px solid var(--sbt-border); box-shadow: var(--sbt-shadow-lg);
  animation: sbt-toast-in .18s ease-out;
}
html[data-sbt-theme="aura"] .sbt-toast { background: #1e293b; border-color: rgba(51,65,85,0.5); }
.sbt-toast.success { border-left: 3px solid var(--sbt-success); }
.sbt-toast.error { border-left: 3px solid var(--sbt-danger); }
.sbt-toast.info { border-left: 3px solid var(--sbt-info); }
@keyframes sbt-toast-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; } }

/* ---- progress ---- */
.sbt-progress { height: 6px; border-radius: 999px; background: var(--sbt-surface-3);
  overflow: hidden; }
.sbt-progress-bar { height: 100%; border-radius: 999px; background: var(--sbt-accent);
  transition: width .15s linear; }

/* ---- spinner ---- */
.sbt-spin {
  display: inline-block; width: 1em; height: 1em; border-radius: 50%;
  border: 2px solid currentColor; border-right-color: transparent;
  animation: sbt-spin .65s linear infinite; vertical-align: -.15em;
}
@keyframes sbt-spin { to { transform: rotate(360deg); } }

/* ---- dropzone ---- */
.sbt-dropzone {
  border: 2px dashed var(--sbt-border-2); border-radius: var(--sbt-radius);
  padding: 1.75rem 1rem; text-align: center; color: var(--sbt-muted);
  cursor: pointer; transition: border-color .15s, background .15s;
}
.sbt-dropzone.dragover { border-color: var(--sbt-accent); background: var(--sbt-accent-weak); }
html[data-sbt-theme="aura"] .sbt-dropzone { border-color: rgba(51,65,85,0.7); }
html[data-sbt-theme="aura"] .sbt-dropzone.dragover {
  border-color: rgba(99,102,241,0.6); background: rgba(99,102,241,0.1);
}

/* ---- aura scrollbar (ported from dtap) ---- */
html[data-sbt-theme="aura"] ::-webkit-scrollbar { width: 6px; height: 6px; }
html[data-sbt-theme="aura"] ::-webkit-scrollbar-track { background: #0f172a; }
html[data-sbt-theme="aura"] ::-webkit-scrollbar-thumb {
  background: rgba(99,102,241,0.3); border-radius: 3px;
}
html[data-sbt-theme="aura"] ::-webkit-scrollbar-thumb:hover {
  background: rgba(99,102,241,0.5);
}

/* ---- aura keyframes (ported verbatim from dtap) ---- */
@keyframes aura-pulse-glow {
  0%, 100% { opacity: 0.3; transform: scale(1); }
  50% { opacity: 0.6; transform: scale(1.05); }
}
@keyframes aura-float {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50% { transform: translateY(-20px) rotate(5deg); }
}
@keyframes aura-gradient-shift {
  0%, 100% { background-position: 0% 50%; }
  50% { background-position: 100% 50%; }
}
@keyframes aura-shimmer {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(100%); }
}
@keyframes aura-spin-slow {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

/* ---- misc ---- */
.sbt-empty { text-align: center; color: var(--sbt-muted); padding: 3rem 1rem; }
.sbt-kbd { font-family: var(--sbt-mono); font-size: .8em; background: var(--sbt-surface-3);
  border: 1px solid var(--sbt-border); border-radius: 5px; padding: .05rem .35rem; }
.sbt-pre {
  background: var(--sbt-surface-2); border: 1px solid var(--sbt-border);
  border-radius: var(--sbt-radius-sm); padding: 1rem; overflow: auto;
  font-family: var(--sbt-mono); font-size: .8125rem; margin: 0;
  white-space: pre-wrap; word-break: break-word;
}
.sbt-truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---- utility + compatibility layer ----
   Small set of layout utilities shared with the self-contained cross-layout
   partials (AI chat widget, impersonation banner) so they render correctly
   on this layout too. */
.d-none { display: none !important; }
.d-block { display: block !important; }
.d-flex { display: flex !important; }
.d-inline { display: inline !important; }
.d-inline-flex { display: inline-flex !important; }
.flex-wrap { flex-wrap: wrap !important; }
.flex-column { flex-direction: column !important; }
.flex-grow-1 { flex-grow: 1 !important; }
.align-items-center { align-items: center !important; }
/* Was MISSING while 11 call sites across 8 blades already used it, so every one
   of them silently fell back to `normal` (= stretch). Measured on the Data &
   Erasure page: a "Cancel this request" button next to a taller text column was
   drawn 127px tall. The set had `center` and `end` but not `start`. */
.align-items-start { align-items: flex-start !important; }
.align-items-end { align-items: flex-end !important; }
.justify-content-center { justify-content: center !important; }
.justify-content-between { justify-content: space-between !important; }
.text-center { text-align: center !important; }
.text-end { text-align: right !important; }
.text-start { text-align: left !important; }
.text-muted { color: var(--sbt-muted) !important; }
.text-danger { color: var(--sbt-danger) !important; }
.text-success { color: var(--sbt-success) !important; }
.text-warning { color: var(--sbt-warning) !important; }
.fw-medium { font-weight: 500 !important; }
.fw-bold { font-weight: 700 !important; }
.small { font-size: .8125rem !important; }
.w-100 { width: 100% !important; }
.mb-0 { margin-bottom: 0 !important; }
.mb-1 { margin-bottom: .25rem !important; }
.mb-2 { margin-bottom: .5rem !important; }
.mb-3 { margin-bottom: 1rem !important; }
.mb-4 { margin-bottom: 1.5rem !important; }
.mt-1 { margin-top: .25rem !important; }
.mt-2 { margin-top: .5rem !important; }
.mt-3 { margin-top: 1rem !important; }
.ms-1 { margin-left: .25rem !important; }
.ms-2 { margin-left: .5rem !important; }
.ms-auto { margin-left: auto !important; }
.me-1 { margin-right: .25rem !important; }
.me-2 { margin-right: .5rem !important; }
.py-0 { padding-top: 0 !important; padding-bottom: 0 !important; }
.py-1 { padding-top: .25rem !important; padding-bottom: .25rem !important; }
.py-2 { padding-top: .5rem !important; padding-bottom: .5rem !important; }
.px-2 { padding-left: .5rem !important; padding-right: .5rem !important; }
.px-4 { padding-left: 1.5rem !important; padding-right: 1.5rem !important; }
.gap-1 { gap: .25rem !important; }
.gap-2 { gap: .5rem !important; }
.gap-3 { gap: 1rem !important; }
@media (min-width: 768px) {
  .d-md-inline { display: inline !important; }
  .d-md-flex { display: flex !important; }
  .d-md-inline-flex { display: inline-flex !important; }
}
/* bootstrap-named pieces used by the shared partials */
.bg-warning { background: #f59e0b !important; }
.text-dark { color: #111827 !important; }
.btn { display: inline-flex; align-items: center; gap: .35rem; border: 1px solid transparent;
  border-radius: 8px; padding: .45rem .9rem; font-size: .875rem; font-weight: 500;
  cursor: pointer; font-family: inherit; }
.btn-sm { padding: .28rem .6rem; font-size: .8125rem; }
.btn-dark { background: #111827; color: #f9fafb; }
.btn-dark:hover { background: #1f2937; color: #fff; }

/* ---- Task F utilities (users/departments + later page ports) ---- */
.sbt-grid-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem; }
.sbt-grid-3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .5rem; }
@media (max-width: 640px) { .sbt-grid-2, .sbt-grid-3 { grid-template-columns: 1fr; } }
.font-monospace { font-family: var(--sbt-mono); }
.text-nowrap { white-space: nowrap; }
.text-info { color: var(--sbt-info) !important; }
.border { border: 1px solid var(--sbt-border); }
.rounded { border-radius: var(--sbt-radius-sm); }
.p-2 { padding: .5rem; }
.p-3 { padding: 1rem; }
.py-3 { padding-top: .75rem; padding-bottom: .75rem; }
.mt-4 { margin-top: 1.5rem; }
.sbt-input-group { display: flex; align-items: stretch; }
.sbt-input-group > .sbt-input { flex: 1 1 auto; min-width: 0; }
.sbt-input-group > .sbt-input:not(:first-child) { border-top-left-radius: 0; border-bottom-left-radius: 0; }
.sbt-input-group > .sbt-input:not(:last-child) { border-top-right-radius: 0; border-bottom-right-radius: 0; }
.sbt-input-group > .sbt-btn { flex: 0 0 auto; }
.sbt-input-group > .sbt-btn:not(:first-child) { border-top-left-radius: 0; border-bottom-left-radius: 0; margin-left: -1px; }
.sbt-input-group > .sbt-btn:not(:last-child) { border-top-right-radius: 0; border-bottom-right-radius: 0; }

/* ---- Task F dashboards (SBTDash widget grid + widgets) ---- */
.sbt-dashgrid { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 1rem; }
.sbt-dashgrid > .sbt-dw { grid-column: span 12; min-width: 0; }
.sbt-dashgrid > .sbt-dw-stat { grid-column: span 6; }
@media (min-width: 992px) {
  .sbt-dashgrid > .sbt-colspan-1 { grid-column: span 1; }
  .sbt-dashgrid > .sbt-colspan-2 { grid-column: span 2; }
  .sbt-dashgrid > .sbt-colspan-3 { grid-column: span 3; }
  .sbt-dashgrid > .sbt-colspan-4 { grid-column: span 4; }
  .sbt-dashgrid > .sbt-colspan-5 { grid-column: span 5; }
  .sbt-dashgrid > .sbt-colspan-6 { grid-column: span 6; }
  .sbt-dashgrid > .sbt-colspan-7 { grid-column: span 7; }
  .sbt-dashgrid > .sbt-colspan-8 { grid-column: span 8; }
  .sbt-dashgrid > .sbt-colspan-9 { grid-column: span 9; }
  .sbt-dashgrid > .sbt-colspan-10 { grid-column: span 10; }
  .sbt-dashgrid > .sbt-colspan-11 { grid-column: span 11; }
  .sbt-dashgrid > .sbt-colspan-12 { grid-column: span 12; }
}
.sbt-dw > .sbt-card { height: 100%; transition: opacity .25s ease; }
/* Aura's own card rule outranks the line above and lists only border-color and
   box-shadow, so aura dashboard cards used to snap back to full opacity with no
   fade. Re-state the transition for aura, keeping its two properties. Written
   without the html type selector so this lands at the same specificity as the
   updating rule below, which therefore still wins on source order and keeps the
   dim itself instant. */
[data-sbt-theme="aura"] .sbt-dw > .sbt-card {
  transition: opacity .25s ease, border-color .25s ease, box-shadow .25s ease;
}
/* Dim instantly when an update starts, fade back only on the way out: a stale
   card must read as stale from the first frame, even when the refresh is fast. */
.sbt-dw.sbt-updating > .sbt-card { opacity: .55; transition-duration: 0s; }
.sbt-dw-head { padding-bottom: .5rem; }
.sbt-dw-head h5 { font-size: .95rem; }
.sbt-dw-ic { width: 34px; height: 34px; border-radius: 8px; display: inline-flex;
  align-items: center; justify-content: center; flex: 0 0 auto; }
.sbt-dw-ic .si { width: 18px; height: 18px; }
.sbt-table-sm th, .sbt-table-sm td { padding: .4rem .6rem; }
.sbt-table tfoot td { border-top: 2px solid var(--sbt-border-2); }
.sbt-decomp-node { width: 210px; border: 1px solid var(--sbt-border); border-radius: 8px;
  padding: .5rem; margin-bottom: .5rem; background: var(--sbt-surface); }
.sbt-decomp-node:hover { border-color: var(--sbt-accent); }
.sbt-decomp-node.sel { border-color: var(--sbt-accent); box-shadow: 0 0 0 2px var(--sbt-accent-weak); }
.sbt-skel { position: relative; overflow: hidden; background: var(--sbt-surface-3);
  border-radius: var(--sbt-radius-sm); }
.sbt-skel::after { content: ""; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .28), transparent);
  animation: sbt-skel 1.4s infinite; }
html[data-sbt-theme="dark"] .sbt-skel::after,
html[data-sbt-theme="aura"] .sbt-skel::after {
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .07), transparent);
}
@keyframes sbt-skel { to { transform: translateX(100%); } }

/* ---- notification bell (WO-9) ---- */
#sbt-bell > .sbt-btn { position: relative; }
.sbt-bell-badge { position: absolute; top: 2px; right: 2px; min-width: 15px; height: 15px;
  padding: 0 3px; border-radius: 8px; background: var(--sbt-danger); color: #fff;
  font-size: .62rem; line-height: 15px; font-weight: 600; text-align: center; }
/* The badge hard-codes white, but --sbt-danger is not one colour: the themes
   built for a dark surface pick a LIGHT rose (#fb7185), and white on that is
   2.69:1 — on 10px text, the unread count was the one thing on the bell nobody
   could read. Black on the same rose is 7.80:1, and it is this theme's own
   convention already (--sbt-on-warning is #000 here for exactly this reason,
   while --sbt-on-accent stays #fff on its darker fill). Scoped to prime-dark:
   `aura` and legacy `dark` carry the same #fb7185 and the same 2.69, but
   restyling a legacy theme is the ruling parked since c72 — NEEDS_USER c75. */
html[data-sbt-theme="prime-dark"] .sbt-bell-badge { color: #000; }
#sbt-bell-menu { min-width: 320px; max-width: 360px; padding: 0; overflow: hidden; }
.sbt-bell-head { display: flex; align-items: center; justify-content: space-between;
  padding: .55rem .8rem; border-bottom: 1px solid var(--sbt-border); }
.sbt-bell-empty { color: var(--sbt-muted); text-align: center; padding: 1.1rem .8rem; }
#sbt-bell-list { max-height: 380px; overflow-y: auto; }
.sbt-bell-item { display: flex; flex-direction: column; gap: 2px; padding: .55rem .8rem;
  color: var(--sbt-text); text-decoration: none; font-size: .84rem;
  border-bottom: 1px solid var(--sbt-border); }
.sbt-bell-item.unread { background: var(--sbt-accent-weak); }
/* hover after .unread so it wins for both read and unread rows */
.sbt-bell-item:hover { background: var(--sbt-surface-2); }
.sbt-bell-item:last-child { border-bottom: 0; }
.sbt-bell-title { display: flex; align-items: center; gap: .45rem; font-weight: 500; }
.sbt-bell-title .si { width: 15px; height: 15px; color: var(--sbt-muted); flex: 0 0 auto; }
.sbt-bell-when { color: var(--sbt-muted); font-size: .72rem; padding-left: calc(15px + .45rem); }

/* On a phone the bell menu is wider than the room to the left of the bell, and
   `.end` pins it to the bell's right edge, so the list ran off the LEFT of the
   screen — where nothing can scroll to it (page overflow stays 0). Dropping
   #sbt-bell to static makes the already-sticky, full-width .sbt-nav the menu's
   containing block, so the menu pins to the viewport instead, and the inherited
   `top: calc(100% + 2px)` now follows the nav's own height — including the
   prime themes' safe-area padding on a notched phone. */
@media (max-width: 640px) {
  #sbt-bell { position: static; }
  #sbt-bell-menu { left: .5rem; right: .5rem; min-width: 0; max-width: none; width: auto; }
}

/* The AI tokens / AI credits chip in .sbt-nav-right opens a 300px-wide menu
   pinned to the chip's right edge, and on a phone that chip sits ~200px from
   the left — so the menu ran off the LEFT of the screen with page overflow 0,
   exactly like the bell did, taking its "Usage details" and "Open AI
   Assistant" buttons with it. The width used to be an inline style, which no
   media query could override; it now lives here so the phone rule can.
   Same remedy as the bell: the parent goes static so the sticky, full-width
   .sbt-nav becomes the containing block and the menu pins to the viewport. */
.sbt-money-menu { min-width: 300px; }
@media (max-width: 640px) {
  .sbt-money-dd { position: static; }
  .sbt-dropdown-menu.sbt-money-menu { left: .5rem; right: .5rem; min-width: 0; max-width: none; width: auto; }
}

/* ==========================================================================
   WO5-3 — the prime shell: spacing, radius, focus, motion, and the
   accessibility floor.

   Three layers, deliberately kept apart:

   A. GLOBAL. The `[hidden]` guard, the focus ring, reduced motion, forced
      colors and prefers-contrast. These are not a look, they are a floor, and
      leaving `light`/`dark`/`aura` without them would be shipping a worse app
      to anyone who has not switched. Where a value differs per theme it goes
      through a token, so the floor is shared and the colour is not.
   B. PRIME TOKENS. The spacing and radius scales. Defined on :root so nothing
      downstream can ever hit an undefined var(), but only CONSUMED by the
      prime-scoped rules below — which is what keeps the three legacy themes
      provably where WO5-1 left them.
   C. PRIME FURNITURE. The shared chrome, re-cut to the scales.
   ========================================================================== */

/* ---- A1. the `hidden` guard (workorder §1b) ------------------------------
   mail.js, chips.js and richtext.js hide everything through the `hidden`
   PROPERTY -- 53 sites in mail.js alone -- and `[hidden]{display:none}` is the
   weakest rule there is, so ANY author rule that sets `display` on the same
   element defeats it silently. Six elements are unguarded today and work only
   because their classes happen not to set display: #ml-ct-empty, #ml-acc-empty,
   #ml-acc-shared-empty (.sbt-empty), #ml-acc-ms, #ml-acc-gmail (.ml-notice),
   #ml-acc-form and #ml-a-shared-row. This lands BEFORE anything gives those
   classes a display value, so it can never regress into a debugging session
   about a panel that will not close. (R334/R384) */
[hidden] { display: none !important; }

/* ---- A2. focus-visible: the first author focus style in this codebase -----
   `outline`, never `box-shadow`: forced-colors mode overrides box-shadow but
   keeps outlines, so a shadow ring vanishes for exactly the users who need it
   most. 3px rather than 2px clears SC 2.4.13's "at least as large as a 2px
   perimeter" without anyone having to measure a curve.

   The ring is NEUTRAL, not the accent (§8a-2 -- Linear overrides its own accent
   ring to 40% black in light mode). An accent ring also breaks the moment a
   user picks a pale colour, which is the whole point of the picker. */
:root { --sbt-focus: rgba(17, 24, 39, .55); }
html[data-sbt-theme="dark"],
html[data-sbt-theme="aura"] { --sbt-focus: rgba(255, 255, 255, .65); }
html[data-sbt-theme="prime-light"] { --sbt-focus: rgba(13, 26, 43, .6); }
html[data-sbt-theme="prime-dark"] { --sbt-focus: rgba(235, 240, 245, .7); }

:focus-visible {
  outline: 3px solid var(--sbt-focus);
  outline-offset: 2px;
  border-radius: 2px;
}
/* the inputs already had a :focus ring that also set outline:none -- keep their
   coloured border for mouse users, but let the real ring through for keyboards */
.sbt-input:focus-visible, .sbt-select:focus-visible, .sbt-textarea:focus-visible {
  outline: 3px solid var(--sbt-focus);
  outline-offset: 2px;
}

/* ---- A3. reduced motion --------------------------------------------------
   The reset must also neutralise scroll-behavior, or a `smooth` set anywhere
   survives it and keeps animating for someone who asked it not to. */
@media (prefers-reduced-motion: reduce) {
  html:focus-within { scroll-behavior: auto !important; }
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---- A4. forced colors (Windows High Contrast) ---------------------------
   This mode forces `box-shadow: none`. A light theme that separates surfaces
   with elevation alone therefore collapses into one undifferentiated slab --
   the single biggest forced-colors failure for exactly the aesthetic this
   workorder builds, and a business suite has a Windows audience. Everything
   that leans on --sbt-shadow to read as foreground gets a real border here. */
@media (forced-colors: active) {
  :focus-visible { outline-color: Highlight; }
  .sbt-card, .sbt-modal-box, .sbt-dropdown-menu, .sbt-toast,
  .sbt-dropzone, .sbt-table { border: 1px solid CanvasText; }
  .sbt-btn { border: 1px solid ButtonText; }
}

/* ---- A5. prefers-contrast: more ------------------------------------------
   --sbt-border is decorative at 1.27:1 on white and is simply invisible here,
   so it is promoted to a real 3:1 edge and muted text is darkened. Measured on
   surface / bg / surface-2: light border 3.47 / 3.23 / 3.11, muted 7.49 / 6.97
   / 6.71; dark border 3.68 on surface-2 and 3.07 on surface-3, muted 10.05. */
@media (prefers-contrast: more) {
  html[data-sbt-theme="prime-light"] {
    --sbt-border: #7c8ba0;
    --sbt-border-2: #5b6b80;
    --sbt-muted: #46566b;
  }
  html[data-sbt-theme="prime-dark"] {
    --sbt-border: #6e8099;
    --sbt-border-2: #8a97a8;
    --sbt-muted: #c2c9d3;
  }
}

/* ---- B. the scales -------------------------------------------------------
   Spacing is a 4pt grid WITH half-steps: the half-step is not padding, it is
   what produces the density these references actually have (Campsite builds its
   whole rhythm on a 2px grid).

   Density is ONE root multiplier, not a two-value enum -- which is how both
   products that ship a density control do it (Missive derives spacing from type
   size, Radix multiplies every space/font/radius token by [data-scaling]).
   Preferences writes --sbt-density; this is what finally consumes it. The
   fallback of 1 matters: the token is absent until someone chooses.

   Radius working set is 4 / 6 / 8 / 12 / pill. Nothing in a dense list exceeds
   12px. --sbt-radius drops 10px -> 8px (the row-plate and card value) and
   --sbt-radius-sm 8px -> 6px (menu item), with the old 10-12px band kept for
   modals and popovers where it belongs. */
:root {
  --sbt-density: 1;
  --sbt-sp-05: calc(2px * var(--sbt-density));
  --sbt-sp-1:  calc(4px * var(--sbt-density));
  --sbt-sp-15: calc(6px * var(--sbt-density));
  --sbt-sp-2:  calc(8px * var(--sbt-density));
  --sbt-sp-25: calc(10px * var(--sbt-density));
  --sbt-sp-3:  calc(12px * var(--sbt-density));
  --sbt-sp-4:  calc(16px * var(--sbt-density));
  --sbt-sp-5:  calc(20px * var(--sbt-density));
  --sbt-sp-6:  calc(24px * var(--sbt-density));
  --sbt-sp-8:  calc(32px * var(--sbt-density));
  --sbt-radius-xs: 4px;
  --sbt-radius-lg: 12px;
  --sbt-radius-pill: 9999px;
}
html[data-sbt-theme^="prime"] {
  --sbt-radius: 8px;
  --sbt-radius-sm: 6px;
}

/* ---- C. prime furniture --------------------------------------------------
   Scoped at the ROOT, never under #ml-wrap/#tc-wrap: .sbt-chips-menu is
   appended to document.body, so a container-scoped rule would leave body-level
   popups unstyled. */

/* the shell breathes: page padding and card padding move onto the scale */
html[data-sbt-theme^="prime"] .sbt-page-head { margin-bottom: var(--sbt-sp-5); }
html[data-sbt-theme^="prime"] .sbt-card-body { padding: var(--sbt-sp-5); }
html[data-sbt-theme^="prime"] .sbt-modal-box { border-radius: var(--sbt-radius-lg); }
html[data-sbt-theme^="prime"] .sbt-dropdown-menu { border-radius: var(--sbt-radius-lg); padding: var(--sbt-sp-1); }
html[data-sbt-theme^="prime"] .sbt-dropdown-item { border-radius: var(--sbt-radius-sm); }
html[data-sbt-theme^="prime"] .sbt-badge { border-radius: var(--sbt-radius-pill); }
html[data-sbt-theme^="prime"] .sbt-input,
html[data-sbt-theme^="prime"] .sbt-select,
html[data-sbt-theme^="prime"] .sbt-textarea { border-radius: var(--sbt-radius-sm); }
html[data-sbt-theme^="prime"] .sbt-toast { border-radius: var(--sbt-radius); }
/* .sbt-btn is already on the working set at 8px; .sbt-btn-sm's 7px is not, and
   .sbt-dropdown-item's 7px is overridden above. Nothing else in the shared
   furniture carries an off-scale radius. */
html[data-sbt-theme^="prime"] .sbt-btn-sm { border-radius: var(--sbt-radius-sm); }

/* scrollbars: thin and neutral, so a dense list does not carry a heavy grey
   gutter down its whole height */
html[data-sbt-theme^="prime"] * { scrollbar-width: thin; scrollbar-color: var(--sbt-border-2) transparent; }
html[data-sbt-theme^="prime"] ::-webkit-scrollbar { width: 10px; height: 10px; }
html[data-sbt-theme^="prime"] ::-webkit-scrollbar-track { background: transparent; }
html[data-sbt-theme^="prime"] ::-webkit-scrollbar-thumb {
  background: var(--sbt-border-2); border-radius: var(--sbt-radius-pill);
  border: 3px solid transparent; background-clip: content-box;
}
html[data-sbt-theme^="prime"] ::-webkit-scrollbar-thumb:hover { background: var(--sbt-muted); background-clip: content-box; }

/* ---- C2. touch + safe area -----------------------------------------------
   24x24 is the normative AA floor (SC 2.5.8) and the spacing exception is
   "at least 24px centre-to-centre", so dense list furniture -- a star, a flag,
   an unread dot -- may stay 24px at 24px pitch rather than bloating every row.
   44px is the DESIGN target for anything a thumb aims at, which is what this
   block gives the chrome. Applied on a coarse POINTER, not a width: a small
   window on a desktop is still a mouse. */
@media (pointer: coarse) {
  html[data-sbt-theme^="prime"] .sbt-btn,
  html[data-sbt-theme^="prime"] .sbt-dropdown-item,
  html[data-sbt-theme^="prime"] .sbt-nav-link {
    min-height: 44px;
  }
  html[data-sbt-theme^="prime"] .sbt-btn-icon { min-width: 44px; }
  /* .sbt-btn-sm is the deliberately dense variant and it lives inside table rows
     and list furniture across ~30 blades. It measures ~30px, comfortably above
     SC 2.5.8's normative 24px floor, and bloating it to 44px is exactly the "do
     not bloat dense rows" case -- so it is excluded, not overlooked. */
  html[data-sbt-theme^="prime"] .sbt-btn.sbt-btn-sm { min-height: 0; }
}
/* env() is 0 unless the viewport meta carries viewport-fit=cover, and a bare
   env() with no fallback makes the whole calc() invalid on desktop -- so always
   the two-argument form. Padding, not margin, so the bar still paints into the
   inset rather than leaving a bare strip. */
html[data-sbt-theme^="prime"] .sbt-nav { padding-top: env(safe-area-inset-top, 0px); }
html[data-sbt-theme^="prime"] .sbt-nav-inner {
  padding-left: calc(var(--sbt-sp-5) + env(safe-area-inset-left, 0px));
  padding-right: calc(var(--sbt-sp-5) + env(safe-area-inset-right, 0px));
}
html[data-sbt-theme^="prime"] .sbt-toasts {
  top: calc(var(--sbt-sp-4) + env(safe-area-inset-top, 0px));
  right: calc(var(--sbt-sp-4) + env(safe-area-inset-right, 0px));
}
