/*
  StrikeSimple design system — dist/css/app.css
  GENERATED by scripts/build.mjs from the token JSON under tokens/. Do not edit: change the JSON and run `npm run build`.
  Deterministic output (no timestamp), so dist/ is committed and `npm run check` fails while it is stale.
  Everything the app surface needs in one file: dist/css/foundation.css, app.tokens.css, app.roles.css and the recipes of components/app/ in index.css order, with every local @import inlined.
  Hand-authored component CSS is copied verbatim from components/; edit it there.
*/

/* ── dist/css/foundation.css ── */
:root {
  /* foundation.brand.mark */
  --mark-ray-start: 4;
  --mark-ray-end: 96;
  --mark-diag-start: 19;
  --mark-diag-end: 81;

  /* foundation.brand.mark.stroke */
  --mark-stroke-inline: 8;
  --mark-stroke-favicon: 11;
  --mark-stroke-app: 12;

  /* foundation.brand.mark.linecap */
  --mark-linecap-landing: square;
  --mark-linecap-app: butt;

  /* foundation.color.blue */
  --blue-300: #9b9bff;
  --blue-400: #5757ff;
  --blue-500: #2a2bf5;
  --blue-600: #1e1fce;
  --blue-700: #15159e;

  /* foundation.color.teal */
  --teal-300: #5fd0c5;
  --teal-400: #2fb8aa;
  --teal-500: #109a8c;
  --teal-600: #0c7a6f;
  --teal-700: #08574f;

  /* foundation.color.violet */
  --violet-300: #b9a6ff;
  --violet-400: #9472ff;
  --violet-500: #6d43f0;
  --violet-600: #5530c4;
  --violet-700: #3f2397;

  /* foundation.color */
  --white: #ffffff;
  --black: #000000;

  /* foundation.color.neutral.cool */
  --neutral-cool-50: #f7f7f8;
  --neutral-cool-100: #eeeeef;
  --neutral-cool-200: #e2e2e4;
  --neutral-cool-300: #d0d0d3;
  --neutral-cool-400: #b0b0b5;
  --neutral-cool-500: #88888e;
  --neutral-cool-600: #64646a;
  --neutral-cool-700: #444449;
  --neutral-cool-800: #262629;
  --neutral-cool-900: #141416;
  --neutral-cool-950: #0a0a0b;

  /* foundation.color.neutral.navy */
  --neutral-navy-50: #f7f9fb;
  --neutral-navy-100: #edf1f6;
  --neutral-navy-200: #dfe6ee;
  --neutral-navy-300: #c6d0dc;
  --neutral-navy-400: #98a5b6;
  --neutral-navy-500: #6e7d92;
  --neutral-navy-600: #4d5a6d;
  --neutral-navy-700: #333d4c;
  --neutral-navy-800: #212934;
  --neutral-navy-900: #151a22;
  --neutral-navy-950: #0b0e14;

  /* foundation.color.product.sun */
  --sun-50: #fbf3e6;
  --sun-100: #f6e4c4;
  --sun-200: #efd09a;
  --sun-300: #e7b96b;
  --sun-400: #dfa343;
  --sun-500: #ce8a20;
  --sun-600: #a86c16;
  --sun-700: #835111;
  --sun-800: #5e3a0d;
  --sun-900: #3b2407;

  /* foundation.color.product.flame */
  --flame-50: #fcefe6;
  --flame-100: #f8d8c2;
  --flame-200: #f3b894;
  --flame-300: #ec9461;
  --flame-400: #e5763a;
  --flame-500: #d55e22;
  --flame-600: #ae4818;
  --flame-700: #863612;
  --flame-800: #5f260c;
  --flame-900: #3c1707;

  /* foundation.color.product.clay */
  --clay-50: #faede9;
  --clay-100: #f3d3c8;
  --clay-200: #e8ae9c;
  --clay-300: #db856b;
  --clay-400: #cf6446;
  --clay-500: #bc4a2e;
  --clay-600: #993826;
  --clay-700: #732a1d;
  --clay-800: #4f1d14;
  --clay-900: #30110b;

  /* foundation.color.product.cobalt */
  --cobalt-50: #ecedff;
  --cobalt-100: #d6d7ff;
  --cobalt-200: #b0b1ff;
  --cobalt-300: #8a8bff;
  --cobalt-400: #5757ff;
  --cobalt-500: #2a2bf5;
  --cobalt-600: #1e1fce;
  --cobalt-700: #15159e;
  --cobalt-800: #0e0e70;
  --cobalt-900: #08083f;

  /* foundation.color.product.azure */
  --azure-50: #e6f4fb;
  --azure-100: #c4e6f6;
  --azure-200: #95d2ee;
  --azure-300: #5fb8e2;
  --azure-400: #2e9bd2;
  --azure-500: #1380be;
  --azure-600: #0f6699;
  --azure-700: #0b4d74;
  --azure-800: #07344e;
  --azure-900: #041c2b;

  /* foundation.color.product.indigo */
  --indigo-50: #eeebfb;
  --indigo-100: #d8d0f5;
  --indigo-200: #b7a8ec;
  --indigo-300: #9580e1;
  --indigo-400: #785fd6;
  --indigo-500: #5e43c8;
  --indigo-600: #4a33a0;
  --indigo-700: #382678;
  --indigo-800: #261951;
  --indigo-900: #150d2e;

  /* foundation.color.signal */
  --signal-yellow: #f5c518;
  --signal-gold: #f2c46d;

  /* foundation.color.status.sage */
  --sage-500: #1f9d63;
  --sage-bg: #e6f1ea;

  /* foundation.color.status.oxblood */
  --oxblood-500: #d23b3b;
  --oxblood-bg: #fae7e5;

  /* foundation.color.status.slate */
  --slate-500: #4f5a6b;
  --slate-bg: #e9ecf1;

  /* foundation.color.status.amber */
  --amber-500: #b07a12;
  --amber-bg: #f4ecdc;

  /* foundation.motion.ease */
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-inout: cubic-bezier(0.65, 0, 0.35, 1);

  /* foundation.motion.duration */
  --dur-fast: 120ms;
  --dur-base: 200ms;

  /* foundation.space */
  --space-unit: 4px;
}

/* ── dist/css/app.tokens.css ── */
:root,
[data-theme="dark"] {
  /* Not a token: the theme block tells native controls which scheme to render in (demo:app/globals.css sets the same). */
  color-scheme: dark;

  /* app.color.gray */
  --gray-50: var(--neutral-navy-50);
  --gray-100: var(--neutral-navy-100);
  --gray-200: var(--neutral-navy-200);
  --gray-300: var(--neutral-navy-300);
  --gray-400: var(--neutral-navy-400);
  --gray-500: var(--neutral-navy-500);
  --gray-600: var(--neutral-navy-600);
  --gray-700: var(--neutral-navy-700);
  --gray-800: var(--neutral-navy-800);
  --gray-900: var(--neutral-navy-900);
  --gray-950: var(--neutral-navy-950);

  /* app.color.bg */
  --bg-page: var(--gray-950);

  /* app.color.surface */
  --surface-panel: var(--gray-900);
  --surface-row: #1a2029;
  --surface-hover: var(--gray-800);
  --surface-hover-light: var(--gray-700);
  --surface-raised: var(--gray-800);
  --surface-inset: var(--gray-950);

  /* app.color.text */
  --text-strong: var(--gray-50);
  --text-body: var(--gray-200);
  --text-muted: var(--gray-400);
  --text-faint: var(--gray-500);
  --text-dim: var(--gray-600);
  --text-onaccent: var(--white);
  --text-on-fill-dark: var(--gray-950);
  --text-on-fill-light: var(--white);

  /* app.color.accent */
  --accent: var(--blue-500);
  --accent-hover: var(--blue-600);
  --accent-bright: var(--blue-400);
  --accent-soft: var(--blue-300);
  --accent-tint: rgba(87, 87, 255, 0.14);
  --accent-tint-strong: rgba(87, 87, 255, 0.24);

  /* app.color.border */
  --border: #232c38;
  --border-strong: var(--gray-700);
  --border-tertiary: rgba(247, 249, 251, 0.08);

  /* app.color */
  --focus-ring: var(--blue-400);
  --grid-line: rgba(247, 249, 251, 0.03);

  /* app.color.market */
  --up: var(--sage-500);
  --up-dim: #1b7a4f;
  --down: #c24a4a;
  --down-dim: #93403e;

  /* app.color.viz */
  --viz-settle: var(--blue-400);
  --viz-now: #d95926;

  /* app.color.impact */
  --impact-high: var(--down);
  --impact-med: var(--amber-500);
  --impact-low: var(--text-dim);

  /* app.color.today */
  --today-highlight: var(--signal-yellow);
  --text-on-today: var(--gray-950);

  /* app.color.spot */
  --spot-highlight: var(--signal-yellow);
  --text-on-spot: var(--gray-950);

  /* app.color */
  --star: var(--signal-yellow);

  /* app.color.scrollbar */
  --scrollbar-thumb: var(--gray-700);
  --scrollbar-thumb-hover: var(--gray-600);

  /* app.category.breaking */
  --cat-breaking: #e02d2d;
  --cat-breaking-tint: rgba(224, 45, 45, 0.16);

  /* app.category.crypto */
  --cat-crypto: var(--blue-400);
  --cat-crypto-tint: rgba(87, 87, 255, 0.14);

  /* app.category.macro */
  --cat-macro: #2bb8b0;
  --cat-macro-tint: rgba(43, 184, 176, 0.14);

  /* app.category.political */
  --cat-political: var(--amber-500);
  --cat-political-tint: rgba(176, 122, 18, 0.16);

  /* app.category.banking */
  --cat-banking: #a78bfa;
  --cat-banking-tint: rgba(167, 139, 250, 0.14);

  /* app.category.equities */
  --cat-equities: var(--up);
  --cat-equities-tint: rgba(31, 157, 99, 0.14);

  /* app.component.control.height */
  --control-h-sm: 32px;
  --control-h-md: 40px;
  --control-h-lg: 48px;

  /* app.component.button.padding-x */
  --btn-px-sm: var(--space-3);
  --btn-px-md: var(--space-4);
  --btn-px-lg: var(--space-5);

  /* app.component.chip.height */
  --chip-h-filter: 32px;
  --chip-h-tag: 20px;

  /* app.component.chip.padding-x */
  --chip-px-filter: var(--space-3);
  --chip-px-tag: var(--space-2);

  /* app.component.segmented */
  --segmented-pad: var(--space-1);
  --segmented-height: 32px;
  --segmented-option-padding-x: 14px;

  /* app.component.rail.width */
  --rail-w-collapsed: 60px;
  --rail-w-expanded: 220px;

  /* app.component.rail */
  --rail-header-h: 56px;
  --rail-row-h: 40px;

  /* app.component.icon.size */
  --icon-xs: 14px;
  --icon-sm: 16px;
  --icon-md: 18px;
  --icon-lg: 24px;

  /* app.component.card */
  --card-radius: var(--radius-lg);
  --card-pad: var(--space-4);
  --card-pad-roomy: var(--space-5);

  /* app.component.popover */
  --popover-gap: var(--space-2);
  --popover-side-gap: 10px;
  --popover-side-width: 288px;

  /* app.component.modal */
  --modal-max-width: 384px;

  /* app.component.day */
  --day-size: 56px;

  /* app.component.slider */
  --slider-thumb-size: 16px;

  /* app.component.focus */
  --focus-ring-width: 2px;
  --focus-ring-offset: 2px;

  /* app.font.family */
  --font-display: var(--font-inter, "Inter", system-ui, -apple-system, "Segoe UI", "Helvetica Neue", Arial, sans-serif);
  --font-num: var(--font-inter, "Inter", system-ui, -apple-system, "Segoe UI", "Helvetica Neue", Arial, sans-serif);
  --font-mono: var(--font-inter, "Inter", system-ui, -apple-system, "Segoe UI", "Helvetica Neue", Arial, sans-serif);
  --font-code: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;

  /* app.layout */
  --container-max: 1280px;
  --gutter: var(--space-4);

  /* app.motion.ease */
  --ease-ui: cubic-bezier(0.4, 0, 0.2, 1);

  /* app.motion.duration */
  --dur-micro: 150ms;
  --dur-slow: 340ms;

  /* app.palette.action.signal.color */
  --action-signal-low: #84848c;
  --action-signal-mid: #d6a232;
  --action-signal-high: #e2793a;
  --action-signal-critical: #dd4040;

  /* app.palette.action.cobalt-heat.color */
  --action-cobalt-heat-low: #6f7bff;
  --action-cobalt-heat-mid: #8b5cf6;
  --action-cobalt-heat-high: #c9469b;
  --action-cobalt-heat-critical: #e0266b;

  /* app.palette.action.vital.color */
  --action-vital-low: #2bb8b0;
  --action-vital-mid: #d9c23a;
  --action-vital-high: #e2793a;
  --action-vital-critical: #d23b3b;

  /* app.palette.action.verdant-crimson.color */
  --action-verdant-crimson-low: #3f7a4a;
  --action-verdant-crimson-mid: #c9a53a;
  --action-verdant-crimson-high: #d9822f;
  --action-verdant-crimson-critical: #d1272a;

  /* app.palette.dte.ember.color */
  --dte-ember-gt30: #b5a06b;
  --dte-ember-d21-30: #c9a95f;
  --dte-ember-d14-21: #dfae4c;
  --dte-ember-d7-14: #fb9225;
  --dte-ember-d3-7: #ff6f1f;
  --dte-ember-d0-2: #ff4a1f;
  --dte-ember-today: #ff2d2d;

  /* app.palette.dte.abyss.color */
  --dte-abyss-gt30: #31324a;
  --dte-abyss-d21-30: #3a3c68;
  --dte-abyss-d14-21: #40408f;
  --dte-abyss-d7-14: #3d3fb0;
  --dte-abyss-d3-7: #3436d6;
  --dte-abyss-d0-2: #2c2df0;
  --dte-abyss-today: #2a2bf5;

  /* app.palette.dte.verdant-crimson.color */
  --dte-verdant-crimson-gt30: #2f4a34;
  --dte-verdant-crimson-d21-30: #3f7a4a;
  --dte-verdant-crimson-d14-21: #7a9c3f;
  --dte-verdant-crimson-d7-14: #c9a53a;
  --dte-verdant-crimson-d3-7: #d9822f;
  --dte-verdant-crimson-d0-2: #d94f2f;
  --dte-verdant-crimson-today: #d1272a;

  /* app.radius */
  --radius-xs: 4px;
  --radius-sm: 6px;
  --radius-md: 8px;
  --radius-lg: 12px;
  --radius-xl: 16px;
  --radius-2xl: 20px;
  --radius-full: 9999px;

  /* app.shadow */
  --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.28);
  --shadow-raised: 0 4px 12px rgba(0, 0, 0, 0.34);
  --shadow-popover: 0 12px 32px rgba(0, 0, 0, 0.48), 0 2px 8px rgba(0, 0, 0, 0.32);

  /* app.space */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --space-8: 32px;
  --space-12: 48px;
  --space-16: 64px;
  --root-font-size: 16px;

  /* app.type.size */
  --fs-3xs: 0.625rem;
  --fs-2xs: 0.6875rem;
  --fs-xs: 0.75rem;
  --fs-sm: 0.8125rem;
  --fs-base: 0.875rem;
  --fs-md: 1rem;
  --fs-lg: 1.25rem;
  --fs-xl: 1.5rem;
  --fs-2xl: 2rem;
  --fs-3xl: 1.75rem;
  --fs-4xl: 2rem;
  --fs-5xl: 2.5rem;
  --fs-6xl: 3rem;
  --fs-7xl: 4rem;

  /* app.type.tracking */
  --tracking-label: 0.06em;
  --tracking-snug: -0.011em;
  --tracking-tight: -0.02em;

  /* app.underlying.btc */
  --underlying-btc: #f7931a;
  --underlying-btc-glyph: #f7931a;

  /* app.underlying.eth */
  --underlying-eth: #627eea;
  --underlying-eth-glyph: #aab0c2;

  /* app.underlying.gold */
  --underlying-gold: #d4a017;
  --underlying-gold-glyph: var(--underlying-gold-glyph-face);
  --underlying-gold-glyph-face: #c9a227;
  --underlying-gold-glyph-top: #f0d479;
  --underlying-gold-glyph-edge: #8a6b1a;

  /* app.underlying.nvda */
  --underlying-nvda: #76b900;
  --underlying-nvda-glyph: #76b900;

  /* app.underlying.aapl */
  --underlying-aapl: #a39e93;
  --underlying-aapl-glyph: var(--text-strong);

  /* app.underlying.crcl */
  --underlying-crcl: #2775ca;
  --underlying-crcl-glyph: #2775ca;

  /* app.underlying.spacex */
  --underlying-spacex: #8896a6;
  --underlying-spacex-glyph: var(--text-strong);

  /* app.underlying.sp500 */
  --underlying-sp500: #d6002a;
  --underlying-sp500-glyph: currentColor;
}

:root[data-theme="light"] {
  /* Not a token: the theme block tells native controls which scheme to render in (demo:app/globals.css sets the same). */
  color-scheme: light;

  /* app.color.bg */
  --bg-page: #f6f8fa;

  /* app.color.surface */
  --surface-panel: var(--white);
  --surface-row: var(--gray-100);
  --surface-hover: var(--gray-200);
  --surface-hover-light: var(--gray-300);
  --surface-raised: var(--white);
  --surface-inset: var(--white);

  /* app.color.text */
  --text-strong: var(--gray-950);
  --text-body: var(--gray-800);
  --text-muted: var(--gray-600);
  --text-dim: var(--gray-400);

  /* app.color.border */
  --border: #e3e9f0;
  --border-strong: var(--gray-300);
  --border-tertiary: rgba(11, 14, 20, 0.08);

  /* app.color */
  --grid-line: rgba(11, 14, 20, 0.035);

  /* app.color.viz */
  --viz-settle: var(--blue-500);
  --viz-now: #eb6834;

  /* app.color */
  --star: #b58900;

  /* app.color.scrollbar */
  --scrollbar-thumb: var(--gray-300);
  --scrollbar-thumb-hover: var(--gray-400);

  /* app.palette.action.signal.color */
  --action-signal-low: #5b5b63;

  /* app.palette.action.cobalt-heat.color */
  --action-cobalt-heat-low: #2a2bf5;

  /* app.palette.action.vital.color */
  --action-vital-mid: #a8891f;

  /* app.palette.action.verdant-crimson.color */
  --action-verdant-crimson-low: #2e8b4a;

  /* app.palette.dte.ember.color */
  --dte-ember-gt30: #8a7a35;
  --dte-ember-d21-30: #9c8330;
  --dte-ember-d14-21: #ad8a20;
  --dte-ember-d7-14: #c0790a;
  --dte-ember-d3-7: #d95f00;
  --dte-ember-d0-2: #e33d0f;
  --dte-ember-today: #e01212;

  /* app.palette.dte.abyss.color */
  --dte-abyss-gt30: #e8e8f5;
  --dte-abyss-d21-30: #d6d7f0;
  --dte-abyss-d14-21: #bcbdea;
  --dte-abyss-d7-14: #9b9de3;

  /* app.palette.dte.verdant-crimson.color */
  --dte-verdant-crimson-gt30: #e3f0e5;
  --dte-verdant-crimson-d21-30: #cbe8d2;
  --dte-verdant-crimson-d14-21: #dce8b0;

  /* app.shadow */
  --shadow-card: 0 1px 2px rgba(11, 14, 20, 0.05);
  --shadow-raised: 0 4px 12px rgba(11, 14, 20, 0.08);
  --shadow-popover: 0 12px 32px rgba(11, 14, 20, 0.14), 0 2px 8px rgba(11, 14, 20, 0.08);
}

@media (min-width: 768px) {
  /* app.layout.gutter */
  :root,
  [data-theme="dark"] {
    --gutter: var(--space-6);
  }
}

@media (min-width: 1280px) {
  /* app.layout.gutter */
  :root,
  [data-theme="dark"] {
    --gutter: var(--space-8);
  }
}

/* ── dist/css/app.roles.css ── */
/* app.type.role.title — demo:app/globals.css#.sx-title */
.sx-title {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: 700;
  line-height: 1.2;
  color: var(--text-strong);
}

/* app.type.role.page-title — demo:app/globals.css#.sx-page-title */
.sx-page-title {
  font-family: var(--font-display);
  font-size: var(--fs-2xl);
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: var(--tracking-tight);
  color: var(--text-strong);
}

/* app.type.role.eyebrow — demo:app/globals.css#.sx-eyebrow */
.sx-eyebrow {
  font-family: var(--font-display);
  font-size: var(--fs-2xs);
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-faint);
}

/* app.type.role.card-title — demo:BRAND.md#Rule 2 role table (Title) */
.sx-card-title {
  font-family: var(--font-display);
  font-size: var(--fs-md);
  font-weight: 600;
  line-height: 1.45;
  letter-spacing: var(--tracking-snug);
  color: var(--text-strong);
}

/* app.type.role.body — demo:app/globals.css#body */
.sx-body {
  font-family: var(--font-display);
  font-size: var(--fs-base);
  font-weight: 400;
  line-height: 1.5;
  color: var(--text-body);
}

/* app.type.role.meta — demo:BRAND.md#Rule 2 role table (Meta) */
.sx-meta {
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  font-weight: 400;
  line-height: 1.4;
  color: var(--text-muted);
}

/* app.type.role.figure — demo:app/design/page.tsx#font-num text-base font-semibold */
.sx-figure {
  font-family: var(--font-num);
  font-size: var(--fs-base);
  font-weight: 600;
  line-height: 1.5;
  color: var(--text-strong);
}

/* app.type.role.code — demo:app/design/page.tsx#font-code text-xs */
.sx-code {
  font-family: var(--font-code);
  font-size: var(--fs-xs);
  font-weight: 400;
  line-height: 1.4;
  color: var(--text-body);
}

/* ── components/app/index.css ── */
/* StrikeSimple design system — app surface — component entry point.
   What this is: the one file that pulls in every hand-authored app component recipe, in the order
   they depend on each other. `npm run build` inlines these imports after dist/css/app.tokens.css
   (every variable the rules below read: surfaces, text, accent, borders, space, type, radii,
   shadows, motion, component sizes) and dist/css/app.roles.css (the typography role classes
   .sx-eyebrow, .sx-title, .sx-page-title, .sx-card-title, .sx-body, .sx-meta, .sx-figure, .sx-code)
   to make dist/css/app.css — the single sheet a page links. This file therefore imports no tokens
   itself: load the two dist sheets before it, or link dist/css/app.css and nothing else. A page
   that also runs Tailwind links dist/css/app.tailwind.css for the @theme mappings; nothing here
   depends on it.
   Where it came from: the demo's primitives — app/globals.css (the non-token rules), Card.tsx,
   Button.tsx, Chip.tsx, SegmentedControl.tsx, Wordmark.tsx, Popover.tsx, SideNav.tsx,
   EventPill.tsx, TopicRow.tsx and the specimens on app/design/page.tsx — each translated from its
   Tailwind class string into plain CSS on the .sx-* names, reading only token variables. See each
   file's header for the exact mapping.
   Order: base first (the ground, the font helpers, the focus ring, the reduced-motion switch and
   the todayPulse keyframes that surfaces.css's day block reuses); card (the surface everything
   sits on); button and chip (the two controls, independent); segmented (its own radio-based
   recipe); controls (the Leverage range slider and card flip from globals.css, independent);
   wordmark (standalone); surfaces last, because its modal, popover, rail and row recipes compose
   the card, chip and countdown idioms defined above. */
/* ── components/app/base.css ── */
/* StrikeSimple design system — app surface — base layer.
   What this is: the page-wide ground the product stands on, framework-neutral: the 16px root, the
   box model, the <body>, the three font-class helpers (display / num / code), text selection, the
   one focus ring, the page container, theme-aware scrollbars, native form controls following the
   theme, the reduced-motion kill switch and the calendar's "today" pulse.
   Where it came from: the NON-token CSS of the demo's app/globals.css — `html { font-size }`,
   `* { box-sizing }`, `html, body { height }`, `body`, `.font-code`, `.font-num, .font-mono`,
   `.sx-container` and its two gutter media queries, `::selection`, `*:focus-visible`,
   `@keyframes todayPulse` / `.today-pulse-dot`, the scrollbar rules, `select, option` and the
   `prefers-reduced-motion` block. The Thesis globe and Strikey's antics stay in the app: they are
   page features, not system primitives. The Leverage range slider and card flip are token-driven
   controls and are ported in controls.css.
   How it maps: every literal is a token from dist/css/app.tokens.css (which must be loaded before
   this file, see index.css). `font-size: var(--text-base)` on the body becomes `var(--fs-base)`,
   because the `--text-*` names belong to Tailwind's @theme and are only emitted into
   dist/css/app.tailwind.css. The three role classes globals.css also held — .sx-eyebrow, .sx-title
   and .sx-page-title — are NOT here: they are typography-role tokens now (app.type.role.eyebrow /
   title / page-title) and come out of dist/css/app.roles.css, with .sx-card-title, .sx-body,
   .sx-meta, .sx-figure and .sx-code beside them. Nothing in this file is Tailwind-layered, so load
   it after Tailwind's preflight if the two share a page, exactly as globals.css's own unlayered
   rules sit after `@import "tailwindcss"`. */

/* ── RULE 1: the 4px grid starts here (globals.css) ─────────────────────────────────────────────
   A 16px root is what puts the whole app on a whole-pixel grid; the previous 14px root (a density
   knob) multiplied every rem by 0.875, so `p-4` landed on 14px and the type scale on sizes like
   12.25px. Density is set by the --fs-* scale, not by this number: "tune the scale, not the root".
   The value is the literal 16px because a custom property cannot size the root it is read from —
   the token app.space.root-font-size (--root-font-size, 16px) records the same number so the two
   cannot drift unnoticed. */
html {
  font-size: 16px; /* = var(--root-font-size); informational token, see above */
}

/* Everything sizes to its border box (globals.css). */
* {
  box-sizing: border-box;
}

/* The shell is a full-height column: a left rail beside a page column that scrolls on its own. */
html,
body {
  height: 100%;
}

/* ── RULE 2 + RULE 3: the page (globals.css body) ──────────────────────────────────────────────
   The quiet page tone, body-tone text, the one typeface at the 14px body default on a 1.5 leading,
   antialiased. Inter's contextual alternates (calt) round off a few awkward pairs; cv11 swaps in the
   single-storey l, "which is what keeps 1/l/I apart in a UI full of numbers" (globals.css). The
   feature string is recorded as the non-emitted token app.font.features.body. */
body {
  margin: 0;
  background: var(--bg-page);
  color: var(--text-body);
  font-family: var(--font-display);
  font-size: var(--fs-base);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  font-feature-settings: "cv11" 1, "calt" 1;
}

/* ── RULE 2: one family, three jobs (globals.css @theme + .font-num / .font-code) ──────────────
   `font-display` is the UI face: everything that is words. `font-num` is the SAME face with
   tabular figures for prices, strikes, countdowns and timestamps — Inter's tnum digits are
   fixed-width, so columns align without the second typeface the old system used. `font-mono` is
   the legacy alias kept so the ~180 existing call sites keep working; it no longer means
   "monospace", it means "tabular figures" (BRAND.md Rule 2). In the demo the family comes from
   Tailwind's @theme and only the numeric variant is set here; a framework-neutral consumer needs
   both, so each helper sets its family too. */
.font-display {
  font-family: var(--font-display);
}
.font-num,
.font-mono {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
}
/* The one exception to "one family": LITERAL CODE — raw wire frames, the token names on /design.
   A real system monospace stack (no webfont), and it opts out of tabular figures: a monospace face
   is already fixed-width, and `tnum` on top of it does nothing useful (globals.css). */
.font-code {
  font-family: var(--font-code);
  font-feature-settings: normal;
}

/* ── RULE 3: the page container (globals.css .sx-container) ───────────────────────────────────
   Content centred and gutter-padded rather than running to the edge of a wide monitor. For
   DOCUMENT-shaped pages (/design). The workspace pages — event, trade, portfolio — are deliberately
   full-bleed: a left rail plus a fluid grid that should use every pixel. The gutter steps
   16 → 24 → 32px with the viewport; those steps live on the --gutter token itself (its
   @media overrides are emitted by the build into dist/css/app.tokens.css), so this rule has none. */
.sx-container {
  width: 100%;
  max-width: var(--container-max);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* Selected text: the exact cobalt under white (globals.css `::selection`). The source writes the
   white as a literal #ffffff; --text-onaccent is that same white, named for exactly this pairing. */
::selection {
  background: var(--blue-500);
  color: var(--text-onaccent);
}

/* The ONLY outline in the system (/design "Laws of the terminal": hover is a background step, never
   a ring). 2px of --focus-ring set 2px off the element, on anything the keyboard lands on. */
*:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring);
  outline-offset: var(--focus-ring-offset);
}

/* ── Theme-aware scrollbars everywhere, Firefox + WebKit (globals.css) ─────────────────────────
   The thumb tone is its own token pair so the light theme can lighten it without touching the gray
   ramp. The WebKit bar is 8px (one --space-2) and the thumb rounds at --radius-xs. */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--scrollbar-thumb) transparent;
}
*::-webkit-scrollbar {
  width: var(--space-2);
  height: var(--space-2);
}
*::-webkit-scrollbar-track {
  background: transparent;
}
*::-webkit-scrollbar-thumb {
  background: var(--scrollbar-thumb);
  border-radius: var(--radius-xs);
}
*::-webkit-scrollbar-thumb:hover {
  background: var(--scrollbar-thumb-hover);
}

/* Native form controls (select dropdowns, date inputs) inherit the page's current color-scheme —
   which dist/css/app.tokens.css sets on :root / [data-theme] — instead of forcing dark, so they
   render correctly in light mode too (globals.css). */
select,
option {
  color-scheme: inherit;
}

/* ── Live "today" marker (globals.css todayPulse) ──────────────────────────────────────────────
   Every calendar's today block is a solid YELLOW block (founder 2026-08-16 round 2 item 9 — was
   cobalt; not a ring, RESTYLE-BRIEF.md B6) plus this small pulsing dot alongside it. The class name
   is the demo's own so its markup keeps working; surfaces.css's .sx-day__dot reuses the keyframes.
   2.4s is a literal: the motion tokens cover the UI's fast / base / slow steps and the framer
   durations, and this slow ambient pulse is none of them. */
@keyframes todayPulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.3;
  }
}
.today-pulse-dot {
  animation: todayPulse 2.4s var(--ease-out) infinite; /* literal: 2.4s, globals.css; no duration token */
}

/* The global reduced-motion kill switch (globals.css): every animation and transition in the app
   — the segmented thumb's slide, the rail's width change, the pulse above — is covered by this one
   rule rather than by per-component checks, which leaves each of them instant but intact. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation: none !important;
    transition: none !important;
  }
}
/* ── components/app/card.css ── */
/* StrikeSimple design system — app surface — card.
   What this is: the base surface every panel, list and modal sits on, and its two padding steps.
   Where it came from: components/Card.tsx — one class string,
   `rounded-lg border border-border bg-panel shadow-[var(--shadow-card)]` — and its header comment,
   which is RULE 3 of the round-12 system (app/globals.css, BRAND.md Rule 3): a card is a DISCRETE
   OBJECT, not a lighter patch of page. Panel tone + a hairline border + a 12px radius is what makes
   it read as something you can click. Tone alone (the round-3 "border massacre" idiom) left cards
   dissolving into each other wherever two of them stacked; the border is back, but as a hairline
   edge rather than the heavy 1px outlines that round 3 was right to remove. Tone for hierarchy,
   border for edges.
   How it maps: rounded-lg → --card-radius (an alias of --radius-lg, 12px); border-border → 1px of
   --border (#232c38 dark, #e3e9f0 light — the hairline the surfaces block calls "a card's edge");
   bg-panel → --surface-panel; the arbitrary shadow → --shadow-card ("barely past nothing so it
   doesn't look stamped on"). Padding is NOT baked in — cards wrap everything from a 4px-inset table
   to a prose block — "but when you do pad one, use p-4 (16px) or the --card-pad-* tokens so it
   stays on the 4px grid": those two tokens are the two modifiers below. Card.tsx names the
   --card-pad-* tokens but globals.css never declares them; app.component.card.* is where they
   exist now (FINDING, recorded on that group). */

/* The card. A real border rather than an inset shadow, so it takes part in layout like the
   demo's `border` utility does and the padding steps below include it in the box. It also sets
   its own text colour — the body tone the page already has — so that a card pinned to the other
   theme (`<div class="sx-card" data-theme="dark">` on a light page, as the reference page's pinned
   specimen is) re-resolves its ink along with its surface; inherited from the page, plain text
   inside it would keep the light theme's near-black on the dark panel. */
.sx-card {
  background: var(--surface-panel);
  border: 1px solid var(--border);
  border-radius: var(--card-radius);
  box-shadow: var(--shadow-card);
  color: var(--text-body);
}

/* The standard pad, p-4: 16px, "card padding-inline, grid gutter" in the space scale. */
.sx-card--pad {
  padding: var(--card-pad);
}

/* The roomy pad, p-5: 20px, what every specimen card on /design uses ("roomy card padding-inline"). */
.sx-card--roomy {
  padding: var(--card-pad-roomy);
}

/* A well: the page tone sunk INTO a card, for a stage or a specimen cell. /design draws these as
   `rounded-md bg-page p-3` (GalleryCell) and `rounded-md bg-page p-6` (the stage under the Card and
   modal mocks) — page tone, no border, no shadow, because a well is the one thing on a card that
   must not read as a second object sitting on it. The border is made transparent rather than
   removed, so a well's box stays exactly a card's size beside one. The radius is the card's;
   /design's wells sit at --radius-md because they are inner chrome, so set it where that matters. */
.sx-card--inset {
  background: var(--bg-page);
  border-color: transparent;
  box-shadow: none;
}
/* ── components/app/button.css ── */
/* StrikeSimple design system — app surface — button.
   What this is: the app's one button, in four fills and four sizes, with its disabled state.
   Where it came from: components/Button.tsx — `base`, `variants` and `sizes`, and the header
   comment, which is RULE 1 + RULE 2 of the round-12 system (app/globals.css, BRAND.md). Heights are
   grid values (32 / 40 / 48) rather than the old 32/40 pair that came out of a 14px root as
   28/35px. Labels are SENTENCE CASE at semibold: the old uppercase + wide-tracking treatment made
   every button shout at the same volume, so nothing read as primary. Weight and fill do that job
   now — which is why there is no text-transform anywhere in this file.
   How it maps (Tailwind on the 16px root): h-8 / h-10 / h-12 → --control-h-sm / md / lg;
   px-3 / px-4 / px-5 → --space-3 / 4 / 5; text-xs / text-sm / text-base → --fs-xs / sm / base with
   the @theme line-heights 1.4 / 1.45 / 1.5; gap-2 → --space-2; rounded-md → --radius-md ("buttons,
   inputs, nav rows"); duration-150 → var(--dur-micro) (the 150ms step between --dur-fast 120ms
   and --dur-base 200ms; the framer `micro` token is the same 0.15s, so a shared token is the open
   question, not a different value); disabled:opacity-40 → 0.4. Fills: bg-accent/text-white →
   --accent / --text-onaccent, hover --accent-hover; bg-row/text-strong → --surface-row /
   --text-strong, hover --surface-hover; ghost → transparent / --text-body; outline → 1px --border,
   "the only outlined control in the set — it reads as a card-shaped affordance, which is exactly
   when a border earns its place". The demo's `<button>` arrives through Tailwind's preflight
   (border 0, background none, font inherit); this file resets the same properties itself so a
   bare <button class="sx-btn"> or <a class="sx-btn"> looks the same without it. Medium and
   secondary are the defaults (Button.tsx's prop defaults), so .sx-btn alone IS a medium secondary
   button and the --md / --secondary modifiers are there for clarity.
   Three things Tailwind 4 does that a plain-CSS port has to do by hand: (1) transition-colors
   runs on Tailwind's default curve, cubic-bezier(0.4, 0, 0.2, 1), not the UA's `ease` — the
   token --ease-ui (app.motion.ease.ui) names it, so every transition in this file reads it;
   (2) a hover: variant is emitted inside `@media (hover: hover)`, so on a touch screen the demo
   shows no hover state at all and nothing sticks after a tap — the hover rules below sit in the
   same media query; (3) there is NO pointer cursor: Button.tsx's class string carries no
   cursor-pointer utility and Tailwind 4's preflight no longer gives buttons one (the demo opts in
   per element where it wants a hand — TopicRow's row, an interactive Chip), so a button renders
   with the default arrow cursor and this file sets none. */

/* ── Base (Button.tsx `base`, medium secondary by default) ──────────────────────────────────── */
.sx-btn {
  /* resets, in place of Tailwind's preflight */
  appearance: none;
  margin: 0;
  border: 0;
  text-decoration: none;
  /* no cursor: Button.tsx sets no cursor-pointer and Tailwind 4's preflight sets none on <button> */
  /* the class string */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  height: var(--control-h-md);
  padding: 0 var(--space-4);
  border-radius: var(--radius-md);
  background: var(--surface-row);
  color: var(--text-strong);
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  line-height: 1.45;
  font-weight: 600;
  white-space: nowrap;
  transition: background-color var(--dur-micro) var(--ease-ui), color var(--dur-micro) var(--ease-ui), border-color var(--dur-micro) var(--ease-ui); /* transition-colors duration-150: --dur-micro on Tailwind's default curve, --ease-ui */
}
/* disabled:opacity-40 disabled:pointer-events-none — the same two things for an <a> that carries
   aria-disabled, since an anchor has no :disabled. */
.sx-btn:disabled,
.sx-btn[aria-disabled="true"] {
  opacity: 0.4;
  pointer-events: none;
}

/* ── Fills (Button.tsx `variants`) ──────────────────────────────────────────────────────────── */
/* Primary: the exact cobalt under white; deepens one ramp step on hover. */
.sx-btn--primary {
  background: var(--accent);
  color: var(--text-onaccent);
}
/* Secondary (the default): row tone, strong text, one tone step up on hover (the base hover below). */
.sx-btn--secondary {
  background: var(--surface-row);
  color: var(--text-strong);
}
/* Ghost: no fill until hovered, body-tone text. */
.sx-btn--ghost {
  background: transparent;
  color: var(--text-body);
}
/* Outline: the only outlined control in the set. The border is inside the control's height
   (border-box), so an outline button lines up with its neighbours. */
.sx-btn--outline {
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text-strong);
}

/* ── Hover (Button.tsx hover:bg-*) ──────────────────────────────────────────────────────────── */
/* "Hover = background only" (/design "Laws of the terminal"): one tone step up and nothing else,
   no ring, no lift; the primary deepens one ramp step instead. Only under a real hover — Tailwind
   4 emits every hover: variant inside this media query, so on a touch screen the demo's buttons
   have no hover state and nothing sticks after a tap. The base hover is declared FIRST so that a
   fill's own :hover, at the same specificity, comes later in the cascade and wins. */
@media (hover: hover) {
  .sx-btn:hover {
    background: var(--surface-hover);
  }
  .sx-btn--primary:hover {
    background: var(--accent-hover);
  }
  .sx-btn--secondary:hover {
    background: var(--surface-hover);
  }
  .sx-btn--ghost:hover {
    background: var(--surface-hover);
  }
  .sx-btn--outline:hover {
    background: var(--surface-hover);
  }
}

/* ── Sizes (Button.tsx `sizes`) ─────────────────────────────────────────────────────────────── */
/* sm: h-8 px-3 text-xs — the Today action beside the calendar's segmented control. */
.sx-btn--sm {
  height: var(--control-h-sm);
  padding: 0 var(--space-3);
  font-size: var(--fs-xs);
  line-height: 1.4;
}
/* md: h-10 px-4 text-sm — the default, restated so the modifier can win over --sm / --lg. */
.sx-btn--md {
  height: var(--control-h-md);
  padding: 0 var(--space-4);
  font-size: var(--fs-sm);
  line-height: 1.45;
}
/* lg: h-12 px-5 text-base. */
.sx-btn--lg {
  height: var(--control-h-lg);
  padding: 0 var(--space-5);
  font-size: var(--fs-base);
  line-height: 1.5;
}
/* icon: h-10 w-10 p-0 — a square medium control around one glyph (give it an aria-label). */
.sx-btn--icon {
  width: var(--control-h-md);
  height: var(--control-h-md);
  padding: 0;
}
/* ── components/app/chip.css ── */
/* StrikeSimple design system — app surface — chip.
   What this is: the category filter chip, the "periodic" tag and the small category tags — one
   pill in two sizes, solid when it carries a category colour, lowest-tone block when it is off.
   Where it came from: components/Chip.tsx. Still a solid block — a category colour fills the whole
   chip with contrast text when active, lowest-tone dim block when off, no border and no leading
   colour dot (round 3, RESTYLE-BRIEF.md B5/B6; /design "Solid blocks over outlines"). Round 12
   changed only the SHAPE and the LABEL: a chip is a full pill (RULE 3 — pills are how a consumer
   market UI says "this is a tag, not a button"), and the label keeps its caps but drops to the
   system's 0.06em tracking instead of Tailwind's tracking-wide (RULE 2 — Inter caps don't need
   pulling apart at 11px).
   How it maps: rounded-full → --radius-full; h-8 px-3 text-xs (filter) → --chip-h-filter /
   --space-3 / --fs-xs; h-5 px-2 text-2xs (tag, the default) → --chip-h-tag / --space-2 / --fs-2xs;
   tracking-[var(--tracking-label)] → --tracking-label; transition-all duration-150 → var(--dur-micro);
   opacity-45 → 0.45; hover:brightness-110 → filter: brightness(1.1). The solid fill is an inline
   style in the demo (`style={{ background: color, color: textColor }}`); here it is the two
   component-scoped custom properties --chip-fill and --chip-ink, set the same way:
   `style="--chip-fill: var(--cat-macro); --chip-ink: var(--text-on-fill-dark)"`. Pair them with
   lib/categories.ts's categoryTextOn() (the app.category.<id>.text-on tokens) — the contrast text
   is computed per fill, never eyeballed. Without a fill the chip is the active OFF-colour state,
   bg-row / text-body; data-active="false" is the inactive state, bg-page / text-faint, and it wins
   over any fill, as Chip.tsx's `solid = active && !!color` does.
   The ink's fallback depends on whether a fill was given. Chip.tsx writes `color: textColor ??
   "var(--text-on-fill-light)"` on a solid chip — white, unless a contrast ink is passed — and
   bg-row / text-body on one without a colour. CSS cannot ask "is --chip-fill set?" inside var(),
   so the recipe reads the style attribute instead: `.sx-chip[style*="--chip-fill"]` matches any
   chip whose inline style names the fill (the attribute is also what element.style.setProperty
   writes to), and there the ink falls back to --text-on-fill-light; a fill inherited from a
   parent's stylesheet rule does not match, so pass both properties inline, as the demo does.
   Two more things Tailwind 4 does that the port does by hand: transition-all runs on Tailwind's
   default curve, --ease-ui (app.motion.ease.ui), not the UA's `ease`; and hover:brightness-110 is
   emitted inside `@media (hover: hover)`, so on a touch screen an interactive chip has no hover
   state and nothing sticks after a tap — the hover rule below sits in the same media query. */

/* ── The pill (Chip.tsx, tag size by default) ───────────────────────────────────────────────── */
.sx-chip {
  /* resets for a <button class="sx-chip"> */
  appearance: none;
  margin: 0;
  border: 0;
  /* the class string */
  display: inline-flex;
  align-items: center;
  height: var(--chip-h-tag);
  padding: 0 var(--space-2);
  border-radius: var(--radius-full);
  background: var(--chip-fill, var(--surface-row));
  color: var(--chip-ink, var(--text-body));
  font-family: var(--font-display);
  font-size: var(--fs-2xs);
  line-height: 1.4;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  white-space: nowrap;
  user-select: none;
  transition: all var(--dur-micro) var(--ease-ui); /* transition-all duration-150: --dur-micro on Tailwind's default curve, --ease-ui */
}
/* A solid chip (its inline style names --chip-fill): the ink falls back to white, Chip.tsx's
   `textColor ?? "var(--text-on-fill-light)"`, rather than to the no-fill body tone above. The
   inactive rule below is written later at the same specificity, so it still wins over any fill. */
.sx-chip[style*="--chip-fill"] {
  color: var(--chip-ink, var(--text-on-fill-light));
}

/* ── Sizes ──────────────────────────────────────────────────────────────────────────────────── */
/* filter: h-8 px-3 text-xs — the category filter row; the same height as a small button and a
   segmented option. "The inline padding a pill needs to keep its label off the curve: 12px at
   filter size, 8px at tag size." */
.sx-chip--filter {
  height: var(--chip-h-filter);
  padding: 0 var(--space-3);
  font-size: var(--fs-xs);
}
/* tag: h-5 px-2 text-2xs — the default, restated so the modifier can win over --filter. */
.sx-chip--tag {
  height: var(--chip-h-tag);
  padding: 0 var(--space-2);
  font-size: var(--fs-2xs);
}

/* ── States ─────────────────────────────────────────────────────────────────────────────────── */
/* Inactive: the lowest-tone block, page under faint — and never solid, whatever fill was passed. */
.sx-chip[data-active="false"] {
  background: var(--bg-page);
  color: var(--text-faint);
}
/* Muted (a filtered-out category): the whole chip at 45% with its label struck through. */
.sx-chip[data-muted] {
  opacity: 0.45;
}
.sx-chip[data-muted] .sx-chip__label {
  text-decoration: line-through;
}
/* Interactive: Chip.tsx renders a <button> only when it has an onClick, and only then does it
   take the pointer (`cursor-pointer`, set explicitly — the one primitive that asks for a hand) and
   brighten on hover — a solid block has no tone step to move to, so the fill itself lifts 10%.
   The brightening is a Tailwind hover: variant, so it applies only under a real hover. */
button.sx-chip {
  cursor: pointer;
}
@media (hover: hover) {
  button.sx-chip:hover {
    filter: brightness(1.1);
  }
}
/* ── components/app/segmented.css ── */
/* StrikeSimple design system — app surface — segmented control.
   What this is: the calendar's view switcher (Day / Week / Month / Year) and every other
   mutually-exclusive choice drawn as a sunken pill track with one solid cobalt thumb.
   Where it came from: components/SegmentedControl.tsx. The active option is a SOLID cobalt thumb
   on the sunken track (RESTYLE-BRIEF.md B6); round 12 rounds both track and thumb to full pills —
   RULE 3: a segmented control is the same shape family as a chip, not a miniature set of buttons.
   The demo moves one shared thumb under the options with a framer spring (stiffness 480, damping
   38, the app.motion.framer.spring tokens) and sets the column count from props.
   How it maps: `relative inline-grid rounded-full bg-page p-1` → the track below with
   --radius-full / --bg-page / --segmented-pad (4px); the thumb's `absolute inset-y-1 left-1
   rounded-full bg-accent` and `width: calc((100% - 0.5rem) / N)` → inset --segmented-pad on three
   sides and (100% − 2 × pad) / --count wide; an option's `h-8 rounded-full px-3.5 font-display
   text-xs font-semibold uppercase tracking-[var(--tracking-label)]` → --segmented-height (32px),
   --segmented-option-padding-x (14px — off the 4px grid, a FINDING recorded on that token),
   --fs-xs, --tracking-label; `text-white` when selected → --text-onaccent, `text-muted
   hover:text-body` otherwise; the spring → a 200ms --ease-out transform transition, the nearest
   thing CSS has (a spring cannot be written as a timing function). The label's transition-colors
   runs on Tailwind's default curve, --ease-ui (app.motion.ease.ui), not the UA's `ease`; its
   hover: variant is emitted inside `@media (hover: hover)`, so the idle hover below sits in the
   same media query and a touch screen shows none. No pointer cursor: the demo's option is a
   <button> with no cursor-pointer utility, and Tailwind 4's preflight gives buttons none, so the
   options render with the default arrow cursor and this file sets none on the radio.

   PURE CSS, NO SCRIPT. The options are radio inputs, so the browser owns the exclusive choice,
   the arrow keys and the form value; the thumb is one shared element that slides to the checked
   option. Markup (the thumb LAST, after every option):

     <fieldset class="sx-segmented" role="radiogroup" aria-label="View">
       <label class="sx-segmented__option"><input type="radio" name="view" value="day"><span>Day</span></label>
       <label class="sx-segmented__option"><input type="radio" name="view" value="week"><span>Week</span></label>
       <label class="sx-segmented__option"><input type="radio" name="view" value="month" checked><span>Month</span></label>
       <span class="sx-segmented__thumb" aria-hidden="true"></span>
     </fieldset>

   Two custom properties position the thumb. --count is the number of options and --index the
   checked option's 0-based position; both are derived with :has() for two to six options — the
   count from "the Nth child is the last label", the index from "the Nth child holds the checked
   input" — so the markup above needs nothing else. For more than six options, or without :has(),
   set them inline (`style="--count: 7; --index: 2"`, and update --index on change).
   Browser support: :has() is in Chrome/Edge 105+, Safari 15.4+ and Firefox 121+ (December 2023).
   Where it is missing, the @supports block at the end hides the sliding thumb and paints the
   checked option's own label solid cobalt instead — the same picture, without the slide. The
   global reduced-motion rule in base.css makes the slide instant for people who asked for that. */

/* ── The track (SegmentedControl.tsx outer div) ─────────────────────────────────────────────── */
.sx-segmented {
  --count: 2;
  --index: 0;
  position: relative;
  display: inline-grid;
  grid-template-columns: repeat(var(--count), minmax(0, 1fr));
  margin: 0; /* a <fieldset> carries browser margin, padding and a border; this is a pill, not a form group */
  border: 0;
  padding: var(--segmented-pad);
  border-radius: var(--radius-full);
  background: var(--bg-page);
  /* no vertical-align: the demo's inline-grid div sits on the baseline like any inline box */
}

/* --count: "the Nth child is the last label among the children" (the thumb is a span, so it is not
   counted). Two is the default above; three to six are derived here. */
.sx-segmented:has(> .sx-segmented__option:nth-child(3):last-of-type) { --count: 3; }
.sx-segmented:has(> .sx-segmented__option:nth-child(4):last-of-type) { --count: 4; }
.sx-segmented:has(> .sx-segmented__option:nth-child(5):last-of-type) { --count: 5; }
.sx-segmented:has(> .sx-segmented__option:nth-child(6):last-of-type) { --count: 6; }

/* --index: "the Nth child holds the checked input". Zero is the default above. */
.sx-segmented:has(> .sx-segmented__option:nth-child(2) > input:checked) { --index: 1; }
.sx-segmented:has(> .sx-segmented__option:nth-child(3) > input:checked) { --index: 2; }
.sx-segmented:has(> .sx-segmented__option:nth-child(4) > input:checked) { --index: 3; }
.sx-segmented:has(> .sx-segmented__option:nth-child(5) > input:checked) { --index: 4; }
.sx-segmented:has(> .sx-segmented__option:nth-child(6) > input:checked) { --index: 5; }

/* ── The thumb (SegmentedControl.tsx motion.div) ────────────────────────────────────────────── */
/* Inset by the track's pad on top, bottom and left, one column wide, slid --index columns right.
   Under the options (they are z-index 1), and transparent to the pointer so a click lands on the
   option beneath it. */
.sx-segmented__thumb {
  position: absolute;
  inset-block: var(--segmented-pad);
  left: var(--segmented-pad);
  width: calc((100% - 2 * var(--segmented-pad)) / var(--count));
  transform: translateX(calc(var(--index) * 100%));
  border-radius: var(--radius-full);
  background: var(--accent);
  pointer-events: none;
  transition: transform var(--dur-base) var(--ease-out);
}

/* ── An option (SegmentedControl.tsx button) ────────────────────────────────────────────────── */
/* The label is the hit area; the radio input is stretched invisibly over the whole of it (not
   display:none, so it stays focusable and the arrow keys still move between options), and the
   span is the visible pill label. */
.sx-segmented__option {
  position: relative;
  z-index: 1;
  display: inline-flex;
  min-width: 0;
}
.sx-segmented__option > input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  appearance: none;
  /* no cursor: SegmentedControl.tsx's <button> has no cursor-pointer and the preflight sets none */
}
.sx-segmented__option > span {
  display: inline-flex;
  flex: 1;
  align-items: center;
  justify-content: center;
  height: var(--segmented-height);
  padding: 0 var(--segmented-option-padding-x);
  border-radius: var(--radius-full);
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  line-height: 1.4;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--text-muted);
  white-space: nowrap;
  transition: color var(--dur-micro) var(--ease-ui); /* transition-colors duration-150: --dur-micro on Tailwind's default curve, --ease-ui */
}
/* Idle hover lifts the label one text step — under a real hover only, as Tailwind's hover: variant
   is; the checked label is white on the cobalt thumb and is declared after the hover so it wins. */
@media (hover: hover) {
  .sx-segmented__option:hover > span {
    color: var(--text-body);
  }
}
.sx-segmented__option > input:checked + span {
  color: var(--text-onaccent);
}
/* The system's one focus ring, drawn on the visible label because the input itself is invisible. */
.sx-segmented__option > input:focus-visible + span {
  outline: var(--focus-ring-width) solid var(--focus-ring);
  outline-offset: var(--focus-ring-offset);
}

/* ── Without :has() ─────────────────────────────────────────────────────────────────────────── */
/* No shared thumb: the checked option's own label goes solid cobalt in place. */
@supports not selector(:has(a)) {
  .sx-segmented__thumb {
    display: none;
  }
  .sx-segmented__option > input:checked + span {
    background: var(--accent);
  }
}
/* ── components/app/controls.css ── */
/* StrikeSimple design system — app surface — controls that need real CSS.
   What this is: the two Trade › Leverage pieces the demo could not write as utilities — a
   two-thumb range input (.sx-range), whose thumbs are only reachable through vendor
   pseudo-elements, and a 3D card flip (.sx-flip / .sx-flip-inner / .sx-flip-face), which needs a
   shared perspective on the container and a preserved 3D context on the element that turns.
   Where it came from: app/globals.css "Trade › Leverage (components/trade/leverage/)", lines
   762–866 at round 12 — the `.sx-range` block (BandSlider.tsx renders two of them stacked on one
   track) and the `.sx-flip*` block, ported rule for rule under the demo's own class names so its
   markup keeps working.
   How it maps: the thumb's colours are the accent tokens the source already reads — --accent-bright
   (blue-400, "strokes and marks … the range thumb") at rest and --accent while dragging — its
   corner --radius-xs (4px), and its focus ring the system's one ring: `outline: 2px solid
   var(--focus-ring); outline-offset: 2px`, written here as --focus-ring-width / --focus-ring-offset
   (the app.component.focus tokens, whose description says the range slider "moves the same ring
   onto its thumb"). The flip's 0.5s is the app.motion.framer.flip token (0.5, recorded in seconds
   and not emitted as CSS, because it is the one CSS transition filed with the long framer moves);
   it is a literal here for that reason, on the system's --ease-out as the source has it. One
   literal has no token and means nothing beyond this file: the 1200px perspective. (The 16px thumb,
   one grid step of four, reads app.component.slider.thumb-size as --slider-thumb-size.) The grab / grabbing cursors
   are the source's own. Reduced motion: the global kill switch in base.css covers the flip's
   transition, "which leaves the flip instant — still a flip, no motion". */

/* ── The dual-thumb band (BandSlider.tsx) ───────────────────────────────────────────────────── */
/* Two native range inputs stacked on one track, so the whole thing stays a real form control —
   keyboard-steppable, screen-reader-labelled, touch-sized — instead of a pair of divs with pointer
   handlers. The trick is that only the THUMBS take pointer events; the inputs themselves let
   clicks through to whichever thumb is on top, which is what keeps both grabbable when the two
   ends are dragged together (globals.css). */
.sx-range {
  -webkit-appearance: none;
  appearance: none;
  position: absolute;
  left: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  background: transparent;
  pointer-events: none;
}
.sx-range::-webkit-slider-runnable-track {
  background: transparent;
  height: 100%;
}
.sx-range::-moz-range-track {
  background: transparent;
  height: 100%;
}
/* The thumb: a --slider-thumb-size (16px) square at --radius-xs in the stroke accent; the one part that takes the pointer. */
.sx-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  pointer-events: auto;
  width: var(--slider-thumb-size); /* app.component.slider.thumb-size (16px) */
  height: var(--slider-thumb-size);
  border: none;
  border-radius: var(--radius-xs);
  background: var(--accent-bright);
  cursor: grab;
}
.sx-range::-moz-range-thumb {
  pointer-events: auto;
  width: var(--slider-thumb-size); /* app.component.slider.thumb-size (16px) */
  height: var(--slider-thumb-size);
  border: none;
  border-radius: var(--radius-xs);
  background: var(--accent-bright);
  cursor: grab;
}
/* Dragging: the thumb deepens to the accent proper and the hand closes. */
.sx-range:active::-webkit-slider-thumb {
  cursor: grabbing;
  background: var(--accent);
}
.sx-range:active::-moz-range-thumb {
  cursor: grabbing;
  background: var(--accent);
}
/* The system's one focus ring, on the thumb. */
.sx-range:focus-visible::-webkit-slider-thumb {
  outline: var(--focus-ring-width) solid var(--focus-ring);
  outline-offset: var(--focus-ring-offset);
}
.sx-range:focus-visible::-moz-range-thumb {
  outline: var(--focus-ring-width) solid var(--focus-ring);
  outline-offset: var(--focus-ring-offset);
}
/* The input's own focus ring would frame the whole invisible track; the ring belongs on the
   thumb, which is the thing that moves (globals.css). */
.sx-range:focus-visible {
  outline: none;
}

/* ── The card flip (components/trade/leverage/) ─────────────────────────────────────────────── */
/* The perspective lives on the OUTER element so every card in the grid shares one vanishing point
   rather than each rotating in its own private box, and the faces are absolutely stacked so the
   card keeps one height whichever side is up (globals.css). */
.sx-flip {
  perspective: 1200px; /* literal: globals.css; the shared vanishing point, no token */
}
.sx-flip-inner {
  position: relative;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
  transition: transform 0.5s var(--ease-out); /* literal 0.5s: app.motion.framer.flip (recorded in seconds, not emitted as CSS) */
}
.sx-flip-inner[data-flipped="true"] {
  transform: rotateY(180deg);
}
.sx-flip-face {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.sx-flip-face[data-face="back"] {
  transform: rotateY(180deg);
}
/* ── components/app/wordmark.css ── */
/* StrikeSimple design system — app surface — wordmark and mark.
   What this is: the StrikeSimple wordmark — the FULL word with both leading S's in the accent and
   an accent asterisk floating above each i — and the compact eight-spoke mark for where the word
   cannot fit.
   Where it came from: components/Wordmark.tsx (founder logo, 2026-07-21). The asterisk is drawn
   over a dotless ı so the mark owns that space; "the * glyph draws in the TOP of its own em box, so
   a small negative top is enough — anything more opens a visible gap between the mark and the
   letters". LogoMark is the asterisk from app/icon.svg (the favicon), for the collapsed left nav
   (left-nav round, 2026-09-11): the same accent the wordmark's S's use, drawn with currentColor so
   a parent can retint it. One component in the demo so the top bar and the design reference can
   never drift; one CSS recipe here for the same reason.
   How it maps: `whitespace-nowrap font-display font-bold tracking-tight text-strong` →
   --font-display / 700 / --tracking-tight / --text-strong; `text-accent-bright` → --accent-bright
   on the S's and the sparks; the spark's `absolute left-1/2 -translate-x-1/2 font-bold leading-none`
   with the inline `top: -0.18em; font-size: 0.85em` is kept to the em so it scales with whatever
   size the word is set at (the rail sets it at text-lg → .sx-wordmark--lg). The svg's attributes
   (viewBox -4 -4 108 108, stroke-width 12, square-cut ends) are the foundation's brand-mark
   geometry — --mark-stroke-app and --mark-linecap-app restate the stroke here so a mark whose
   attributes are missing still draws as the favicon does. Markup:

     <span class="sx-wordmark" role="img" aria-label="StrikeSimple"><span aria-hidden="true"><span
       class="sx-wordmark__s">S</span>tr<span class="sx-wordmark__i">ı<span
       class="sx-wordmark__spark">*</span></span>ke<span class="sx-wordmark__s">S</span><span
       class="sx-wordmark__i">ı<span class="sx-wordmark__spark">*</span></span>mple</span></span>

     <svg class="sx-mark" role="img" aria-label="StrikeSimple" width="22" height="22"
       viewBox="-4 -4 108 108" fill="none" stroke="currentColor" stroke-width="12" stroke-linecap="butt">
       <line x1="50" y1="4" x2="50" y2="96"/><line x1="4" y1="50" x2="96" y2="50"/>
       <line x1="19" y1="19" x2="81" y2="81"/><line x1="81" y1="19" x2="19" y2="81"/>
     </svg> */

/* ── The word (Wordmark.tsx) ────────────────────────────────────────────────────────────────── */
/* Inherits its size from the text around it; bold, tight, strong, and never wrapped. */
.sx-wordmark {
  display: inline-block;
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: var(--tracking-tight);
  color: var(--text-strong);
  white-space: nowrap;
}
/* The two leading S's in the bright accent (400, the stroke/mark step of the cobalt ramp). */
.sx-wordmark__s {
  color: var(--accent-bright);
}
/* The dotless ı: a positioning box for the spark above it. */
.sx-wordmark__i {
  position: relative;
  display: inline-block;
}
/* The brand asterisk, centred over the ı where the tittle would be — in ems so it rides with the
   word's size. The two offsets are literals from Wordmark.tsx's inline style; they are geometry,
   tuned by eye by the founder, not sizes on the grid. */
.sx-wordmark__spark {
  position: absolute;
  left: 50%;
  top: -0.18em; /* literal: Wordmark.tsx `top: "-0.18em"` */
  transform: translateX(-50%);
  font-size: 0.85em; /* literal: Wordmark.tsx `fontSize: "0.85em"` */
  font-weight: 700;
  line-height: 1;
  color: var(--accent-bright);
}
/* The sizes the shell sets it at: text-lg in the expanded rail's header (SideNav.tsx). */
.sx-wordmark--lg {
  font-size: var(--fs-lg);
  line-height: 1.3;
}
.sx-wordmark--xl {
  font-size: var(--fs-xl);
  line-height: 1.25;
}

/* ── The mark (Wordmark.tsx LogoMark, app/icon.svg) ─────────────────────────────────────────── */
/* A block-level svg in the bright accent; its size comes from its width / height attributes (22px
   in the collapsed rail). stroke: currentColor is what lets a parent's `color` retint it. The
   stroke width and cap restate the svg attributes from the foundation's brand-mark tokens — the
   app's mark is the 12-unit stroke with square-cut (butt) ends, heavier than the landing's 8. */
.sx-mark {
  display: block;
  flex-shrink: 0;
  color: var(--accent-bright);
  fill: none;
  stroke: currentColor;
  stroke-width: var(--mark-stroke-app);
  stroke-linecap: var(--mark-linecap-app);
}
/* ── components/app/surfaces.css ── */
/* StrikeSimple design system — app surface — floating surfaces, the rail, and the calendar idioms.
   What this is: the things that FLOAT (the modal shell, the popover, the tooltip), the left nav
   rail with its rows, flyout and utility block, and the small calendar idioms the product pages
   repeat — the stat block, the day numeral block, the countdown text, the event pill and the
   topic row.
   Where it came from: components/Popover.tsx (the floating panel), components/SideNav.tsx (the
   60/220 rail: ACTIVE_ROW / IDLE_ROW, NavGroup, NavSubItem, RailButton, RailTip, the flyout and
   the Demo badge), components/EventPill.tsx (the three pill sizes), components/TopicRow.tsx (the
   row, its hidden severity bar, the countdown colours and the watch star), and the specimens on
   app/design/page.tsx: the static modal mock ("Modal shell"), MiniDay (the four day states),
   GalleryStat (the stat block), GalleryCell (the specimen well, see card.css) and the "Countdown
   text states" card.
   How it maps: the same translation as the other files — Tailwind utilities on the 16px root to
   the --space-*, --fs-*, --radius-* and surface tokens, `shadow-[var(--shadow-popover)]` to
   --shadow-popover, `duration-150` to var(--dur-micro), the 150ms step. BRAND.md Rule 3: "Shadows are for things
   that float — popovers, modals, drag targets", which is why only this file reaches for
   --shadow-popover. A few of the demo's values are off the 4px grid and have no token: px-3.5 /
   py-2.5 / p-1.5 / gap-0.5 (14 / 10 / 6 / 2px), the 22px month pill, the 3px severity bar, the
   184px flyout. Each is kept as the source has it, marked `literal:` where it is used, and listed
   as a FINDING in the slice report rather than rounded onto the grid. Where a /design specimen and
   the live component disagree (the countdown's text-sm vs TopicRow's text-xs, GalleryStat's
   font-code for a number, `tracking-wide` on labels that BRAND.md puts at --tracking-label), the
   system value is used and the disagreement is a FINDING.
   Three things Tailwind 4 does that this port does by hand. (1) A bare transition-colors runs on
   Tailwind's default curve, cubic-bezier(0.4, 0, 0.2, 1) — the token --ease-ui
   (app.motion.ease.ui) — so every colour transition here names it; only the rail's width, which
   the demo eases with the ease-out utility, reads --ease-out. (2) Every hover: and group-hover:
   variant is emitted inside `@media (hover: hover)`: on a touch screen the demo shows no hover
   state and nothing sticks after a tap, so the pointer-hover rules below (row and button tone
   steps, the flyout and tooltip reveals, the topic row, the star, the modal close) sit in the same
   media query, while the :focus-within reveals stay outside it for the keyboard. (3) No pointer
   cursor on a <button>: Tailwind 4's preflight no longer sets one, and RailButton, the modal
   close and the watch star carry no cursor-pointer utility, so they render with the default arrow
   and this file sets none on them. The demo opts in where it wants a hand — TopicRow's row is
   `cursor-pointer`, a soon leaf `cursor-not-allowed` — and a nav row is a <Link>, an anchor,
   which the browser gives the pointer natively; those three are the cursors this file keeps. */

/* ══ Modal shell (app/design/page.tsx "Modal shell (static mock)") ═══════════════════════════════
   A panel-tone column, overflow hidden, the popover shadow, 24rem wide at most, with a row-tone
   header strip under a hairline. The real modals (DayEventsModal, SearchModal, TicketModal, the
   pickers) are framer-animated portals with the same anatomy — the app.motion.framer.modal-*
   tokens hold their backdrop and panel durations. The mock rounds at rounded-md (8px); BRAND.md's
   radius table puts "cards, panels, modals" at --radius-lg (12px), and the system follows the
   table — FINDING recorded on app.radius.lg. */
.sx-modal {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: var(--modal-max-width);
  overflow: hidden;
  border-radius: var(--radius-lg);
  background: var(--surface-panel);
  box-shadow: var(--shadow-popover);
}
/* The header strip: `flex items-center justify-between gap-2 border-b border-border bg-row px-3.5
   py-2.5` — an eyebrow (the day, the count) and the close glyph. */
.sx-modal__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  padding: 10px 14px; /* literal: py-2.5 px-3.5, off the 4px grid, no token */
  border-bottom: 1px solid var(--border);
  background: var(--surface-row);
}
/* The body: `flex flex-col gap-1.5 p-3.5`. */
.sx-modal__body {
  display: flex;
  flex-direction: column;
  gap: 6px; /* literal: gap-1.5 */
  padding: 14px; /* literal: p-3.5 */
}
/* The close affordance: `rounded-sm p-1 text-faint`, a 14px X inside. The mock's is a static span;
   the live close buttons (SearchModal, PositionDetailsModal, ExtendPositionModal,
   DayPositionsModal, UpdatePositionModal, AssetSearchModal, the pickers) are all `shrink-0
   rounded-sm p-1 text-faint transition-colors hover:bg-hover hover:text-strong` — a tone step
   under strong text — and that is the hover here. No cursor: those buttons set no cursor-pointer
   (TicketModal's alone does) and the preflight gives a <button> none. */
.sx-modal__close {
  appearance: none;
  margin: 0;
  border: 0;
  background: transparent;
  display: inline-flex;
  padding: var(--space-1);
  border-radius: var(--radius-sm);
  color: var(--text-faint);
  transition: background-color var(--dur-micro) var(--ease-ui), color var(--dur-micro) var(--ease-ui); /* transition-colors: --dur-micro on --ease-ui */
}
@media (hover: hover) {
  .sx-modal__close:hover {
    background: var(--surface-hover);
    color: var(--text-strong);
  }
}

/* ══ Popover (components/Popover.tsx) ════════════════════════════════════════════════════════════
   The portal-rendered floating panel: `fixed z-50 rounded-md bg-raised shadow-[var(--shadow-popover)]`,
   positioned against its trigger's viewport rect so it escapes any clipping ancestor (an
   overflow-hidden calendar cell). The position itself is script — --popover-gap (8px below the
   trigger), --popover-side-gap (10px beside it) and --popover-side-width (288px) are the numbers
   that script uses, as tokens. Raised, not panel: popovers "float above everything else"
   (/design Surfaces). The 6px padding is the rail flyout's p-1.5, the one popover in the demo that
   pads itself; Popover.tsx leaves padding to its children, which --flush restores. */
.sx-popover {
  border-radius: var(--radius-md);
  background: var(--surface-raised);
  box-shadow: var(--shadow-popover);
  padding: 6px; /* literal: p-1.5, SideNav.tsx flyout; Popover.tsx itself has none */
}
.sx-popover--flush {
  padding: 0;
}

/* ══ Tooltip (components/SideNav.tsx RailTip) ════════════════════════════════════════════════════
   `whitespace-nowrap rounded-md bg-raised px-2.5 py-1.5 font-display text-xs font-semibold
   text-strong shadow-[var(--shadow-popover)]`, and it never takes the pointer. The rail positions
   it (see .sx-rail__tip below); on its own it is just the bubble. */
.sx-tooltip {
  display: inline-block;
  padding: 6px 10px; /* literal: py-1.5 px-2.5 */
  border-radius: var(--radius-md);
  background: var(--surface-raised);
  box-shadow: var(--shadow-popover);
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  line-height: 1.4;
  font-weight: 600;
  color: var(--text-strong);
  white-space: nowrap;
  pointer-events: none;
}

/* ══ The left nav rail (components/SideNav.tsx, left-nav round 2026-09-11) ═══════════════════════
   pump.fun proportions: a 220px rail with icon + label rows, or a 60px icon-only rail with the
   label in a hover flyout. NARROW AT REST, WIDE ON HOVER: the rail sits at 60px and opens to 220px
   while the pointer or the keyboard focus is inside it; the pin makes the open state stick.
   THE HOVER STATE DOES NOT MOVE THE PAGE: an unpinned rail keeps its 60px of LAYOUT (the slot) and
   the open panel overlays the page column — widening the layout on hover would reflow a chart and
   a grid of live cards every time the pointer crossed the rail; the overlay costs a shadow instead.
   A PINNED rail does take its width in layout, because that is a deliberate, persistent choice.
   The demo drives `expanded` and `pinned` from React state; here they are two data attributes on
   the rail (and `data-pinned` on the slot), set by whatever script owns hover / focus / the pin:

     <div class="sx-rail-slot" data-pinned>
       <aside class="sx-rail" aria-label="Primary" data-expanded data-pinned>
         <div class="sx-rail__header"> …wordmark or mark… </div>
         <nav class="sx-rail__nav"> <a class="sx-rail__row" aria-current="page">…</a> … </nav>
         <div class="sx-rail__footer"> <span class="sx-rail__badge">Demo · Sample Data</span> <button class="sx-rail__button">…</button> </div>
       </aside>
     </div> */

/* The slot: the rail's layout footprint — collapsed width unless pinned. Hidden below `sm`
   (640px), where MobileNav's top bar + drawer takes over. */
.sx-rail-slot {
  position: relative;
  height: 100%;
  flex-shrink: 0;
  width: var(--rail-w-collapsed);
}
.sx-rail-slot[data-pinned]:not([data-pinned="false"]) {
  width: var(--rail-w-expanded);
}
@media (max-width: 639px) {
  .sx-rail-slot {
    display: none;
  }
}

/* The rail itself: `absolute inset-y-0 left-0 z-30 flex h-full flex-col border-r border-border
   bg-panel transition-[width] duration-150 ease-out`. Tailwind 4's `ease-out` utility reads
   var(--ease-out), whose theme default is cubic-bezier(0, 0, 0.2, 1) — but globals.css redeclares
   --ease-out on :root after the Tailwind import, so in the demo the rail already slides on the
   system's curve; the same token here, and not --ease-ui, which is for the bare transition-colors
   utilities. */
.sx-rail {
  position: absolute;
  inset-block: 0;
  left: 0;
  z-index: 30;
  display: flex;
  height: 100%;
  flex-direction: column;
  width: var(--rail-w-collapsed);
  border-right: 1px solid var(--border);
  background: var(--surface-panel);
  transition: width var(--dur-micro) var(--ease-out); /* --dur-micro: Tailwind duration-150 */
}
.sx-rail[data-expanded]:not([data-expanded="false"]) {
  width: var(--rail-w-expanded);
}
/* Only while it is overlaying the page does it need to read as floating above it; a pinned rail
   is part of the layout and carries no shadow. */
.sx-rail[data-expanded]:not([data-expanded="false"]):not([data-pinned]),
.sx-rail[data-expanded]:not([data-expanded="false"])[data-pinned="false"] {
  box-shadow: var(--shadow-popover);
}

/* Brand block: `flex h-14 shrink-0 items-center`, centred around the mark when collapsed, padded
   px-4 with the wordmark (at --lg) when expanded. */
.sx-rail__header {
  display: flex;
  height: var(--rail-header-h);
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
}
.sx-rail[data-expanded]:not([data-expanded="false"]) .sx-rail__header {
  justify-content: flex-start;
  padding-inline: var(--space-4);
}
/* The brand link rounds at --radius-sm so the focus ring hugs the word (SideNav.tsx `rounded-sm`). */
.sx-rail__brand {
  display: flex;
  align-items: center;
  border-radius: var(--radius-sm);
  color: inherit;
  text-decoration: none;
}

/* The section list: `flex flex-col gap-0.5 p-2`. */
.sx-rail__nav {
  display: flex;
  flex-direction: column;
  gap: 2px; /* literal: gap-0.5 */
  padding: var(--space-2);
}

/* ── Rows (SideNav.tsx NavGroup, ACTIVE_ROW / IDLE_ROW) ─────────────────────────────────────────
   RULE 2: sentence case at semibold, not wide-tracked caps. The labels are already written as
   words in lib/nav.ts ("Event calendar", "My portfolio") — uppercasing them made the rail read as
   a control panel rather than a list of places. `flex h-10 items-center gap-3 rounded-md
   font-display text-sm font-semibold transition-colors`; `px-3` when expanded, `w-full
   justify-center` (icon only) when not. Idle: text-faint, hover bg-hover + text-body. Active:
   bg-row text-strong with a 2px cobalt bar inset on the left edge — aria-current="page" is the
   active state, as the demo sets it. */
.sx-rail__row {
  appearance: none;
  margin: 0;
  border: 0;
  background: transparent;
  text-decoration: none;
  cursor: pointer; /* a nav row is a <Link> — an anchor, which the browser gives the pointer natively; kept so a <button class="sx-rail__row"> matches */
  display: flex;
  width: 100%;
  height: var(--control-h-md);
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  border-radius: var(--radius-md);
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  line-height: 1.45;
  font-weight: 600;
  color: var(--text-faint);
  transition: background-color var(--dur-micro) var(--ease-ui), color var(--dur-micro) var(--ease-ui); /* transition-colors: --dur-micro on Tailwind's default curve, --ease-ui */
}
.sx-rail[data-expanded]:not([data-expanded="false"]) .sx-rail__row {
  justify-content: flex-start;
  padding-inline: var(--space-3);
}
/* IDLE_ROW's hover:bg-hover hover:text-body — under a real hover only, as Tailwind's hover:
   variant is. */
@media (hover: hover) {
  .sx-rail__row:hover {
    background: var(--surface-hover);
    color: var(--text-body);
  }
}
.sx-rail__row[aria-current="page"],
.sx-rail__row.is-active {
  background: var(--surface-row);
  color: var(--text-strong);
  box-shadow: inset 2px 0 0 var(--accent); /* literal: 2px, SideNav.tsx shadow-[inset_2px_0_0_var(--accent)] */
}
/* A group heading whose CHILD is the active page never wears the fill itself when expanded — its
   active child does — but it reads strong, and hover steps its BACKGROUND only: SideNav.tsx gives
   it `text-strong hover:bg-hover`, no hover:text-*. The :hover half of the selector is what keeps
   the strong text from being replaced by the idle row's hover text above (same specificity, later
   in the cascade). */
.sx-rail__row--group-active,
.sx-rail__row--group-active:hover {
  color: var(--text-strong);
}
/* Icons and labels inside a row: the glyph never shrinks (18px, --icon-md, by attribute), the
   label truncates. */
.sx-rail__row > svg {
  flex-shrink: 0;
}
.sx-rail__label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ── Sub-rows (SideNav.tsx NavSubItem) ──────────────────────────────────────────────────────────
   Leverage / Strategy / Thesis-soon under the expanded group, inside the collapsed flyout, and in
   MobileNav's drawer. RULE 2 again: a sub-item is one step quieter than its parent by COLOUR
   (IDLE_ROW), not by being smaller and more spaced out. `flex items-center gap-2.5 rounded-md
   px-2.5 font-display font-medium`, `h-8 text-xs` (sm, the rail) or `h-10 text-sm` (md, the
   drawer). The padding is the same whether the rail is open or not — a sub-row only ever renders
   where there is room for its label. */
.sx-rail__row--sub {
  height: var(--control-h-sm);
  justify-content: flex-start;
  gap: 10px; /* literal: gap-2.5 */
  padding-inline: 10px; /* literal: px-2.5 */
  font-size: var(--fs-xs);
  line-height: 1.4;
  font-weight: 500;
}
/* Restated at the expanded rail's specificity, so the parent row's `px-3` rule above does not
   widen a sub-row's inset when the rail opens. */
.sx-rail[data-expanded]:not([data-expanded="false"]) .sx-rail__row--sub {
  justify-content: flex-start;
  padding-inline: 10px; /* literal: px-2.5 */
}
.sx-rail__row--sub.sx-rail__row--md {
  height: var(--control-h-md);
  font-size: var(--fs-sm);
  line-height: 1.45;
}
/* A `soon` leaf: inert text that LOOKS like the two live rows next to it, with a SOON tag. */
.sx-rail__row--soon,
.sx-rail__row--soon:hover {
  cursor: not-allowed;
  background: transparent;
  color: var(--text-dim);
}
.sx-rail__soon {
  margin-left: auto;
  padding: 2px var(--space-2); /* literal: py-0.5 */
  border-radius: var(--radius-full);
  background: var(--surface-row);
  font-family: var(--font-display);
  font-size: var(--fs-3xs);
  line-height: 1.4;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--text-faint);
}
/* The indented child list under an expanded group: a guide rule under the parent's icon centre —
   px-3 (12px on the round-12 grid) + half an 18px icon = 21px (SideNav.tsx). */
.sx-rail__children {
  display: flex;
  flex-direction: column;
  gap: 2px; /* literal: gap-0.5 */
  margin-left: calc(var(--space-3) + var(--icon-md) / 2);
  margin-top: 2px; /* literal: mt-0.5 */
  padding-left: var(--space-2);
  border-left: 1px solid var(--border);
}

/* ── The collapsed group's flyout (SideNav.tsx `group/fly`) ─────────────────────────────────────
   One icon stands in for the whole group; its hover (or focus-within) opens a raised menu just
   right of the rail: `absolute left-full top-0 z-50 hidden pl-3`, holding a `min-w-[184px]
   flex-col gap-0.5 rounded-md bg-raised p-1.5 shadow-popover` panel — i.e. an .sx-popover — with
   the group's name as a 10px eyebrow above its sub-rows. */
.sx-rail__group {
  position: relative;
}
.sx-rail__flyout {
  position: absolute;
  left: 100%;
  top: 0;
  z-index: 50;
  display: none;
  padding-left: var(--space-3);
}
/* group-hover/fly:block under a real hover only; group-focus-within/fly:block always, for the
   keyboard. */
@media (hover: hover) {
  .sx-rail__group:hover > .sx-rail__flyout {
    display: block;
  }
}
.sx-rail__group:focus-within > .sx-rail__flyout {
  display: block;
}
.sx-rail__menu {
  display: flex;
  flex-direction: column;
  gap: 2px; /* literal: gap-0.5 */
  min-width: 184px; /* literal: min-w-[184px], SideNav.tsx; no token */
}
.sx-rail__menu-title {
  margin: 0;
  padding: var(--space-1) 10px; /* literal: px-2.5 */
  font-family: var(--font-display);
  font-size: var(--fs-3xs);
  line-height: 1.4;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--text-faint);
}

/* ── The utility block (SideNav.tsx RailButton, the Demo badge) ─────────────────────────────────
   Pinned to the bottom: `mt-auto flex flex-col gap-1 border-t border-border p-2` — wallet, theme,
   tour, analytics, the pin. A RailButton is `flex h-9 items-center gap-2.5 rounded-md font-display
   text-xs font-semibold text-muted hover:bg-hover hover:text-body`, `w-full px-2.5` expanded or
   `w-full justify-center` collapsed; 36px is one grid step shorter than a nav row. */
.sx-rail__footer {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin-top: auto;
  padding: var(--space-2);
  border-top: 1px solid var(--border);
}
.sx-rail__button {
  appearance: none;
  margin: 0;
  border: 0;
  background: transparent;
  /* no cursor: RailButton sets no cursor-pointer and the preflight gives a <button> none */
  display: flex;
  width: 100%;
  height: calc(var(--control-h-md) - var(--space-1));
  align-items: center;
  justify-content: center;
  gap: 10px; /* literal: gap-2.5 */
  border-radius: var(--radius-md);
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  line-height: 1.4;
  font-weight: 600;
  color: var(--text-muted);
  transition: background-color var(--dur-micro) var(--ease-ui), color var(--dur-micro) var(--ease-ui); /* transition-colors: --dur-micro on --ease-ui */
}
.sx-rail[data-expanded]:not([data-expanded="false"]) .sx-rail__button {
  justify-content: flex-start;
  padding-inline: 10px; /* literal: px-2.5 */
}
@media (hover: hover) {
  .sx-rail__button:hover {
    background: var(--surface-hover);
    color: var(--text-body);
  }
}
.sx-rail__button > svg {
  flex-shrink: 0;
}
/* "Demo · Sample Data": a pill in the political amber over its tint — the one place the rail
   borrows a category tint for something that is not a category (SideNav.tsx inline style). Shown
   only when the rail is expanded. */
.sx-rail__badge {
  align-self: flex-start;
  margin-bottom: var(--space-1);
  padding: var(--space-1) 10px; /* literal: px-2.5 */
  border-radius: var(--radius-full);
  background: var(--cat-political-tint);
  color: var(--amber-500);
  font-family: var(--font-display);
  font-size: var(--fs-2xs);
  line-height: 1.4;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  white-space: nowrap;
}

/* ── The collapsed rail's tooltip (SideNav.tsx RailTip) ─────────────────────────────────────────
   The label floats just right of the rail on hover / focus: pure CSS, no portal — the rail is a
   stacking context above the page column, so nothing clips it. `absolute left-full top-1/2 ml-3
   -translate-y-1/2 z-50`, hidden until the wrapper is hovered or holds focus. */
.sx-rail__tip {
  position: relative;
  display: flex;
  justify-content: center;
}
.sx-rail__tip > .sx-tooltip {
  position: absolute;
  left: 100%;
  top: 50%;
  z-index: 50;
  display: none;
  margin-left: var(--space-3);
  transform: translateY(-50%);
}
/* group-hover/tip:block under a real hover only; group-focus-within/tip:block always. */
@media (hover: hover) {
  .sx-rail__tip:hover > .sx-tooltip {
    display: block;
  }
}
.sx-rail__tip:focus-within > .sx-tooltip {
  display: block;
}

/* ══ Stat block (app/design/page.tsx GalleryStat; the portfolio's cost basis / value / PnL) ══════
   `flex shrink-0 flex-col gap-0.5`: a 2xs caps label over a text-lg figure in tabular figures,
   toned up / down / neutral. Two FINDINGS against the specimen: it sets the figure in font-code
   (the monospace reserved for LITERAL CODE) where BRAND.md Rule 2 says "numbers use font-num", and
   it tracks the label with Tailwind's tracking-wide (0.025em) rather than --tracking-label — the
   system values are used here. text-lg carries --tracking-snug in the app's @theme, so the figure
   does too. */
.sx-stat {
  display: flex;
  flex-shrink: 0;
  flex-direction: column;
  gap: 2px; /* literal: gap-0.5 */
}
.sx-stat__label {
  font-family: var(--font-display);
  font-size: var(--fs-2xs);
  line-height: 1.4;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--text-faint);
}
.sx-stat__value {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-lg);
  line-height: 1.3;
  letter-spacing: var(--tracking-snug);
  font-weight: 600;
  color: var(--text-strong);
}
.sx-stat--up .sx-stat__value {
  color: var(--up);
}
.sx-stat--down .sx-stat__value {
  color: var(--down);
}

/* ══ Day numeral block (app/design/page.tsx MiniDay; CalendarMonth's day cells) ═════════════════
   "Emphasis follows events, not position": a day cell's numeral weight is driven entirely by
   whether it HAS events — not by weekday, column or row. Event days render text-strong on the
   panel; empty days recede to text-faint on the page; past days dim further to text-dim regardless
   of events; today is a solid YELLOW block (--today-highlight, founder 2026-08-16 round 2 — accent
   stays reserved for interactive / selection state) with near-black text and the pulsing dot.
   `flex h-14 w-14 items-center justify-center gap-1 rounded-md font-display text-2xl
   font-extrabold leading-none` → --day-size (56px), --radius-md, --fs-2xl at 800 with the
   display ramp's --tracking-tight. The month grid's real cells are fluid; this is the specimen's
   fixed square. */
.sx-day {
  display: flex;
  width: var(--day-size);
  height: var(--day-size);
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  border-radius: var(--radius-md);
  background: var(--surface-panel);
  color: var(--text-strong);
  font-family: var(--font-display);
  font-size: var(--fs-2xl);
  line-height: 1;
  letter-spacing: var(--tracking-tight);
  font-weight: 800;
}
.sx-day--event {
  background: var(--surface-panel);
  color: var(--text-strong);
}
.sx-day--empty {
  background: var(--bg-page);
  color: var(--text-faint);
}
.sx-day--past {
  background: var(--bg-page);
  color: var(--text-dim);
}
.sx-day--today {
  background: var(--today-highlight);
  color: var(--text-on-today);
}
/* The pulsing dot beside today's numeral: `h-1.5 w-1.5 rounded-full bg-black/60` on the
   todayPulse keyframes from base.css. Black at 60% over the yellow, mixed from the foundation's
   --black rather than written as an rgba literal. */
.sx-day__dot {
  flex-shrink: 0;
  width: 6px; /* literal: h-1.5 w-1.5 */
  height: 6px;
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--black) 60%, transparent);
  animation: todayPulse 2.4s var(--ease-out) infinite; /* literal: 2.4s, see base.css */
}
/* The caption under a specimen day (`text-3xs font-semibold uppercase tracking-wide text-faint`;
   --tracking-label in place of tracking-wide, the same FINDING as the stat label). */
.sx-day__label {
  font-family: var(--font-display);
  font-size: var(--fs-3xs);
  line-height: 1.4;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--text-faint);
}

/* ══ Countdown text (app/design/page.tsx "Countdown text states"; TopicRow.tsx countdownClass) ══
   The humanised settlement countdown — "settles in 21d 4h" — in lowercase bold: text-body once 7+
   days out, text-strong once inside a week (lib/countdown.ts's formatHumanCountdown, paired with
   TopicRow's own `days < 7` check), text-dim once settled. The specimen sets it at text-sm; the
   live TopicRow sets the same text at text-xs (FINDING) — the row's rule below keeps the live size
   inside a topic row. */
.sx-countdown {
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  line-height: 1.45;
  font-weight: 700;
  text-transform: lowercase;
  color: var(--text-body);
}
.sx-countdown--far {
  color: var(--text-body);
}
.sx-countdown--near {
  color: var(--text-strong);
}
.sx-countdown--settled {
  color: var(--text-dim);
}

/* ══ Event pill (components/EventPill.tsx) ═══════════════════════════════════════════════════════
   A settlement tag: a full solid category-coloured block (allocation-block style) with contrast
   text — no outline, no dot-as-color (RESTYLE-BRIEF.md B6). Founder 2026-07-23: pills are purely
   decorative — no hover lift, no click; pointer-events: none lets the pointer fall through to the
   day cell, whose own hover / click opens the story card. shrink-0: a pill either renders at full
   height or gets clipped whole by its container — never squashed into a sliver when a month cell
   runs out of room (round 2 item 3 regression guard). The fill is the demo's inline style, here
   the two component-scoped properties --pill-fill / --pill-ink, set from the category tokens:
   `style="--pill-fill: var(--cat-macro); --pill-ink: var(--text-on-fill-dark)"`.
   Sizes: month `h-[22px] px-2 text-sm leading-[22px]` (the default), week `h-7 px-2.5 text-sm
   leading-7`, day `h-11 px-4 text-base leading-[44px]`; the line-height equals the height so the
   title sits centred. FINDINGS: the 22px month pill is the one height in the set off the 4px grid
   (28 and 44 are on it), and `rounded-[3px]` is a radius below the system's smallest (--radius-xs,
   4px). Both are kept as EventPill.tsx has them. `pulse` (calendar navigation, not the pointer) is
   a framer scale flash plus a 2px --accent-bright ring; the ring is data-pulse here. */
.sx-event-pill {
  --pill-h: 22px; /* literal: h-[22px], EventPill.tsx month size; off the 4px grid */
  display: flex;
  width: 100%;
  flex-shrink: 0;
  align-items: center;
  height: var(--pill-h);
  padding: 0 var(--space-2);
  border-radius: 3px; /* literal: rounded-[3px], EventPill.tsx; below --radius-xs */
  background: var(--pill-fill, var(--surface-row));
  color: var(--pill-ink, var(--text-body));
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  line-height: var(--pill-h);
  font-weight: 600;
  text-align: left;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  pointer-events: none;
}
.sx-event-pill--week {
  --pill-h: 28px; /* literal: h-7 */
  padding: 0 10px; /* literal: px-2.5 */
}
.sx-event-pill--day {
  --pill-h: 44px; /* literal: h-11 */
  padding: 0 var(--space-4);
  font-size: var(--fs-base);
}
.sx-event-pill[data-pulse] {
  box-shadow: 0 0 0 2px var(--accent-bright); /* literal: 2px ring, EventPill.tsx */
}
.sx-event-pill__title {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ══ Topic row (components/TopicRow.tsx; the Hot Topics list) ════════════════════════════════════
   An event row is title, categories, countdown, star. Nothing else (founder 2026-08-16: volume and
   the underlying-glyph row are gone from every event surface). `relative flex flex-col gap-1 py-2
   pl-3.5 pr-2.5 transition-colors odd:bg-panel even:bg-row hover:bg-hover`: the rows alternate
   panel / row tone, and round 9 item 2 gave the row its own hover feedback (a tone-step brighten)
   in addition to the day-cell highlight it still fires. Disabled (filtered out): `pointer-events-none
   opacity-45`, the title struck through. The severity bar — a solid 3px left-edge bar in the impact
   colour, `absolute inset-y-1 left-0 w-[3px] rounded-r-[1px]` (RESTYLE-BRIEF.md B7) — is HIDDEN in
   the demo (SHOW_SEVERITY_BAR = false, founder round 6, "may return"), so it is opt-in here:
   data-severity="high | med | low" draws it. */
.sx-topic-row {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-2) 10px var(--space-2) 14px; /* literal: py-2 pr-2.5 pl-3.5 */
  background: var(--surface-panel);
  cursor: pointer; /* TopicRow.tsx `cursor-pointer` on a live row — set explicitly in the demo */
  transition: background-color var(--dur-micro) var(--ease-ui); /* transition-colors: --dur-micro on --ease-ui */
}
.sx-topic-row:nth-child(even) {
  background: var(--surface-row);
}
/* hover:bg-hover under a real hover only (the row's hover also fires the day-cell highlight, which
   is script). */
@media (hover: hover) {
  .sx-topic-row:hover {
    background: var(--surface-hover);
  }
}
.sx-topic-row[aria-disabled="true"] {
  opacity: 0.45;
  pointer-events: none;
  cursor: default;
}
/* The severity bar, when asked for. */
.sx-topic-row[data-severity]::before {
  content: "";
  position: absolute;
  top: var(--space-1);
  bottom: var(--space-1);
  left: 0;
  width: 3px; /* literal: w-[3px] */
  border-radius: 0 1px 1px 0;
  background: var(--impact-low);
}
.sx-topic-row[data-severity="high"]::before {
  background: var(--impact-high);
}
.sx-topic-row[data-severity="med"]::before {
  background: var(--impact-med);
}
/* Line 1: title + periodic icon (round 7) + humanised countdown — no exact date (founder, round 5).
   `flex items-center gap-1.5`. */
.sx-topic-row__line {
  display: flex;
  align-items: center;
  gap: 6px; /* literal: gap-1.5 */
}
.sx-topic-row__title {
  min-width: 0;
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  line-height: 1.45;
  font-weight: 600;
  color: var(--text-strong);
}
.sx-topic-row[aria-disabled="true"] .sx-topic-row__title {
  text-decoration: line-through;
}
.sx-topic-row__periodic {
  flex-shrink: 0;
  display: inline-flex;
  color: var(--text-faint);
}
/* The live row sets its countdown one step smaller than the /design specimen (text-xs). */
.sx-topic-row .sx-countdown {
  flex-shrink: 0;
  font-size: var(--fs-xs);
  line-height: 1.4;
}
/* Line 2: one chip per category tag, primary first (2026-08-16 item 4), and the watch star.
   `flex items-center justify-between gap-2`; the tags `flex min-w-0 flex-wrap items-center gap-1`. */
.sx-topic-row__meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}
.sx-topic-row__tags {
  display: flex;
  min-width: 0;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1);
}
/* The watch star: `shrink-0 rounded-sm p-1.5 transition-colors`, star yellow when watched (founder
   2026-08-15 item 10 — star yellow, not periwinkle), faint otherwise and body on hover. aria-pressed
   is the watched state, as TopicRow.tsx sets it. No cursor of its own: the star has no
   cursor-pointer utility (the row it sits in has), and the preflight gives a <button> none. */
.sx-topic-row__star {
  appearance: none;
  margin: 0;
  border: 0;
  background: transparent;
  display: inline-flex;
  flex-shrink: 0;
  padding: 6px; /* literal: p-1.5 */
  border-radius: var(--radius-sm);
  color: var(--text-faint);
  transition: color var(--dur-micro) var(--ease-ui); /* transition-colors: --dur-micro on --ease-ui */
}
/* The unwatched star's hover:text-body, under a real hover only; the watched star is declared
   after it so its yellow wins. */
@media (hover: hover) {
  .sx-topic-row__star:hover {
    color: var(--text-body);
  }
}
.sx-topic-row__star[aria-pressed="true"] {
  color: var(--star);
}
