/* =============================================================================
   tokens.css — the single source of truth for the visual language.

   Why this file exists
   --------------------
   Until now dashboard.css, landing.css and auth.css each opened with their own
   `:root` block, and those blocks had already drifted apart: two different
   values for --bg-base (#0b0d12 vs #080a0f), two different neutral ramps, two
   different --text-secondary, two spellings of the accent (--accent-a vs
   --accent-purple). Nobody decided that; it is what happens when the same
   decision is written down in three places. A visitor moving from the landing
   page to the dashboard crossed a seam they should never have been able to see.

   So: every colour, size, radius, shadow and easing curve is declared exactly
   once, here, and every other stylesheet consumes it. Changing the brand accent
   is now one line, not a hunt.

   Load order matters. This file must be linked *before* any other stylesheet,
   which base.html now guarantees for every page in the app — a page cannot
   forget it.

   Structure
   ---------
     1. Primitives      raw scales — the ramp, the type steps, the spacing steps
     2. Semantic        what the primitives *mean* (surface, border, danger…)
     3. Components      shared decisions used by more than one component
     4. Compatibility   the legacy names, aliased onto the semantic layer

   Section 4 is the reason this refactor is safe. Every custom property the old
   stylesheets and the inline styles in the telemetry templates referenced still
   resolves — it just now resolves to a value chosen once instead of three
   times. No selector had to be renamed and no template had to be rewritten for
   the palette to become consistent.
   ============================================================================= */

:root {
  color-scheme: dark;

  /* ==========================================================================
     1. PRIMITIVES
     ========================================================================== */

  /* --- Neutral ramp --------------------------------------------------------
     Perceptually even steps rather than hand-picked hexes, so a surface one
     level up from another always *looks* one level up, wherever it is used.
     Every neutral in the app comes from this ladder. */
  --mf-n-1000: #050609;
  --mf-n-950:  #07080c;
  --mf-n-900:  #0b0d13;
  --mf-n-850:  #101319;
  --mf-n-800:  #151922;
  --mf-n-750:  #1b202b;
  --mf-n-700:  #232936;
  --mf-n-600:  #333b4b;
  --mf-n-500:  #4d5668;
  --mf-n-400:  #6d7688;
  --mf-n-300:  #98a1b3;
  --mf-n-200:  #c4cad6;
  --mf-n-100:  #e3e7ee;
  --mf-n-50:   #f2f4f9;

  /* --- Brand ---------------------------------------------------------------
     One accent with a real ramp, plus the cyan it gradients into. The pink and
     emerald from the old landing palette are kept because the landing page
     genuinely uses them, but they are decoration, not part of the UI language:
     nothing functional should depend on them. */
  --mf-brand-100: #ded6ff;
  --mf-brand-300: #b3a2ff;
  --mf-brand-400: #a58cff;
  --mf-brand-500: #7c5cff;   /* the accent */
  --mf-brand-600: #6b47f5;
  --mf-brand-700: #5836d6;
  --mf-cyan-400:  #22d3ee;
  --mf-cyan-500:  #06b6d4;
  --mf-pink-400:  #ff529a;
  --mf-emerald-400: #10b981;

  /* --- Status --------------------------------------------------------------
     Four meanings, each with a text-weight tone (readable on dark) and a
     surface-weight tone (a wash behind it). Badges, alerts, chart series and
     severity chips all draw from these instead of inventing their own. */
  --mf-ok-400:      #34d399;
  --mf-ok-500:      #10b981;
  --mf-warn-400:    #fbbf24;
  --mf-warn-500:    #f59e0b;
  --mf-danger-400:  #f87171;
  --mf-danger-500:  #ef4444;
  --mf-info-400:    #60a5fa;
  --mf-info-500:    #3b82f6;

  /* --- Type scale ----------------------------------------------------------
     A 1.125/1.200 hybrid ramp. Sizes are in rem so a user's browser font-size
     setting still means something; the app previously mixed rem and px, which
     silently broke that for anyone who had turned their text up. */
  --mf-text-2xs:  0.6875rem;  /* 11px — dense table meta only */
  --mf-text-xs:   0.75rem;    /* 12px — labels, badges, captions */
  --mf-text-sm:   0.8125rem;  /* 13px — secondary UI text */
  --mf-text-md:   0.875rem;   /* 14px — the app's body size */
  --mf-text-base: 1rem;       /* 16px — public site body */
  --mf-text-lg:   1.125rem;   /* 18px — lead paragraphs */
  --mf-text-xl:   1.375rem;   /* 22px — card / page titles */
  --mf-text-2xl:  1.75rem;    /* 28px — page headings */
  --mf-text-3xl:  2.25rem;    /* 36px — section headings */
  --mf-text-4xl:  3rem;       /* 48px — hero */
  --mf-text-5xl:  3.75rem;    /* 60px — hero, wide viewports */

  --mf-leading-tight:  1.18;
  --mf-leading-snug:   1.35;
  --mf-leading-normal: 1.55;
  --mf-leading-relaxed: 1.7;

  /* Optical letter-spacing: large type needs tightening, small caps need air. */
  --mf-tracking-tighter: -0.035em;
  --mf-tracking-tight:   -0.02em;
  --mf-tracking-normal:  0;
  --mf-tracking-wide:    0.06em;
  --mf-tracking-wider:   0.1em;

  --mf-weight-normal:   400;
  --mf-weight-medium:   500;
  --mf-weight-semibold: 600;
  --mf-weight-bold:     700;

  /* --- Spacing -------------------------------------------------------------
     4px base. These are the only gaps that should appear in the app; an
     arbitrary 13px or 27px is how layouts stop lining up across pages. */
  --mf-space-0:  0;
  --mf-space-1:  4px;
  --mf-space-2:  8px;
  --mf-space-3:  12px;
  --mf-space-4:  16px;
  --mf-space-5:  24px;
  --mf-space-6:  32px;
  --mf-space-7:  48px;
  --mf-space-8:  64px;
  --mf-space-9:  96px;
  --mf-space-10: 128px;

  /* --- Radii ---------------------------------------------------------------
     Softened from the old scale. 22px on a small card reads as a toy; the large
     radius is now reserved for genuinely large surfaces. */
  --mf-radius-xs:   6px;
  --mf-radius-sm:   8px;
  --mf-radius-md:   12px;
  --mf-radius-lg:   16px;
  --mf-radius-xl:   24px;
  --mf-radius-2xl:  32px;
  --mf-radius-full: 999px;

  /* --- Elevation -----------------------------------------------------------
     Two-part shadows: a tight contact shadow that defines the edge, plus a wide
     soft one that lifts it. A single huge blur (the old `0 20px 60px`) makes
     everything look like it is floating a foot off the page. */
  --mf-shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.4);
  --mf-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4), 0 2px 6px -2px rgba(0, 0, 0, 0.45);
  --mf-shadow-md: 0 1px 2px rgba(0, 0, 0, 0.4), 0 8px 24px -8px rgba(0, 0, 0, 0.5);
  --mf-shadow-lg: 0 2px 4px rgba(0, 0, 0, 0.4), 0 24px 56px -16px rgba(0, 0, 0, 0.6);
  --mf-shadow-xl: 0 4px 8px rgba(0, 0, 0, 0.4), 0 40px 80px -20px rgba(0, 0, 0, 0.7);
  --mf-shadow-brand: 0 4px 16px -4px rgba(124, 92, 255, 0.55);

  /* --- Motion --------------------------------------------------------------
     One entrance curve, one exit curve, three durations. Interfaces feel
     coherent when everything decelerates the same way. */
  --mf-ease:      cubic-bezier(0.2, 0.8, 0.2, 1);
  --mf-ease-out:  cubic-bezier(0.16, 1, 0.3, 1);
  --mf-ease-in:   cubic-bezier(0.4, 0, 1, 1);
  --mf-dur-fast:   120ms;
  --mf-dur-normal: 200ms;
  --mf-dur-slow:   320ms;

  /* --- Type families ------------------------------------------------------- */
  --mf-font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
                  Helvetica, Arial, sans-serif;
  --mf-font-mono: "JetBrains Mono", "Fira Code", "SF Mono", Consolas, Monaco, monospace;

  /* --- Layering ------------------------------------------------------------
     Named z-indexes. Ad-hoc `z-index: 999` is how a modal ends up behind a
     tooltip and nobody can say why. */
  --mf-z-base:     0;
  --mf-z-sticky:   50;
  --mf-z-dropdown: 100;
  --mf-z-drawer:   150;
  --mf-z-modal:    200;
  --mf-z-toast:    300;

  /* ==========================================================================
     2. SEMANTIC LAYER — what the primitives mean.
     Components should reference *these*, not the ramp above. That indirection
     is what makes an alternate theme a matter of overriding one block.
     ========================================================================== */

  --mf-bg:            var(--mf-n-900);
  --mf-bg-sunken:     var(--mf-n-950);
  --mf-surface:       var(--mf-n-850);
  --mf-surface-raised: var(--mf-n-800);
  --mf-surface-hover: var(--mf-n-750);
  --mf-surface-active: var(--mf-n-700);

  --mf-border:        rgba(255, 255, 255, 0.07);
  --mf-border-strong: rgba(255, 255, 255, 0.13);
  --mf-border-accent: rgba(124, 92, 255, 0.42);

  /* Text: the old --text-secondary (#a6adbb) sat right on the edge of 4.5:1 on
     the raised surfaces and failed on the sunken ones. These clear AA on every
     surface defined above — a claim this block made before anything checked it,
     and which --mf-fg-subtle did not actually meet: --mf-n-400 came in at 4.07:1
     on --mf-surface, the commonest card background in the app, and 3.19:1 on
     --mf-surface-active. It is a hex rather than a ramp step because the ramp is
     evenly spaced by construction and there is no rung between 400 and 300 to
     move it to. tests/test_theme.py measures all of these now, in both palettes,
     with store_theme's own WCAG code. */
  --mf-fg:         var(--mf-n-50);
  --mf-fg-muted:   var(--mf-n-300);
  --mf-fg-subtle:  #8a93a6;
  --mf-fg-on-brand: #ffffff;
  /* Text that sits on a *saturated* fill — the brand gradient on .btn-primary, a
     solid green "Copied" chip, the master tag. Near-black in **both** palettes,
     because what it sits on is bright in both. Flipping it with the theme is the
     failure this name exists to prevent: white lettering on #22d3ee is the one
     combination in the app that is illegible in the *light* theme. */
  --mf-fg-on-accent: var(--mf-n-1000);

  --mf-accent:        var(--mf-brand-500);
  --mf-accent-hover:  var(--mf-brand-400);
  --mf-accent-text:   var(--mf-brand-400);
  --mf-accent-wash:   rgba(124, 92, 255, 0.13);
  --mf-accent-wash-strong: rgba(124, 92, 255, 0.22);
  /* A brand tint bright enough to read *on* an --mf-accent-wash chip rather than
     on the page. On dark that means going up the ramp, on light down it, so the
     two cannot be the same token as --mf-accent-text without one of them failing. */
  --mf-accent-text-strong: var(--mf-brand-300);
  --mf-accent-text-bright: var(--mf-brand-100);

  /* The cyan half of the brand gradient, used on its own for sparklines, icon
     strokes, eyebrows and the "template" badge. It needs a name of its own because
     #22d3ee is decoration on near-black and unreadable text on white: 1.7:1. The
     light palette moves it down the ramp and every rule that references this
     follows without being edited. */
  --mf-accent2:        var(--mf-cyan-400);
  --mf-accent2-text:   var(--mf-cyan-400);
  --mf-accent2-wash:   rgba(34, 211, 238, 0.10);
  --mf-accent2-border: rgba(34, 211, 238, 0.28);

  /* The landing page's third decorative hue. Functional UI must not depend on it. */
  --mf-pink-text: var(--mf-pink-400);
  --mf-pink-wash: rgba(255, 82, 154, 0.13);

  --mf-ok-fg:       var(--mf-ok-400);
  --mf-ok-wash:     rgba(52, 211, 153, 0.13);
  --mf-warn-fg:     var(--mf-warn-400);
  --mf-warn-wash:   rgba(251, 191, 36, 0.13);
  --mf-danger-fg:   var(--mf-danger-400);
  --mf-danger-wash: rgba(248, 113, 113, 0.13);
  --mf-info-fg:     var(--mf-info-400);
  --mf-info-wash:   rgba(96, 165, 250, 0.13);

  /* The hairline on a status wash. A wash on its own has almost no edge, and this
     border is the only thing stopping a badge dissolving into the card behind it —
     which is exactly what happened wherever the border was written as a literal
     rgba() that could not follow the palette. */
  --mf-ok-border:     rgba(52, 211, 153, 0.28);
  --mf-warn-border:   rgba(251, 191, 36, 0.28);
  --mf-danger-border: rgba(248, 113, 113, 0.32);
  --mf-info-border:   rgba(96, 165, 250, 0.30);

  /* Saturated status fills: meter segments, version rails, the live dot. These are
     *shapes*, not text, so they keep their saturation in both palettes — a rail
     that goes muddy in the light theme stops being scannable, which is its only
     job. Anything written on top of one uses --mf-fg-on-accent. */
  --mf-ok-solid:     var(--mf-ok-500);
  --mf-warn-solid:   var(--mf-warn-500);
  --mf-danger-solid: var(--mf-danger-500);
  --mf-info-solid:   var(--mf-info-500);

  /* Telemetry / audit severity. Four rungs, and unlike the four status meanings
     above these are read as an ordered scale, so "high" gets its own orange
     between warn and danger. Text weight — the light values are darkened until
     they clear AA on a white card. */
  /* Crit is the -400 tone, not the -500: #ef4444 is 3.87:1 on --mf-surface-active,
     and the one rung that means "drop everything" is not the rung to render in a
     colour somebody might not be able to read. */
  --mf-sev-crit: var(--mf-danger-400);
  --mf-sev-high: #fb923c;
  --mf-sev-med:  var(--mf-warn-400);
  --mf-sev-low:  var(--mf-ok-400);

  /* A changelog "release" post. Cyan, so it reads as informational next to the
     orange/red pair rather than as another level of alarm. */
  --mf-release-fg:   #7dd3fc;
  --mf-release-wash: rgba(34, 211, 238, 0.15);

  /* Inert rails, meter gaps, progress tracks, the ghost step numbers on the
     landing page — the "nothing here yet" grey. On dark it is a step *up* from the
     surface and on light a step *down*, which is why it cannot stay a raw ramp
     value: --mf-n-700 on a white card is a near-black bar. */
  --mf-track:           var(--mf-n-700);
  --mf-track-strong:    var(--mf-n-600);
  --mf-scrollbar:       var(--mf-n-700);
  --mf-scrollbar-hover: var(--mf-n-600);

  /* Overlays. A scrim darkens whatever is behind it rather than painting a colour,
     so it is not part of the palette — but 55% black over a white page is far
     heavier than over a black one, so the light palette lightens the number
     instead of reusing it. */
  --mf-scrim:        rgba(0, 0, 0, 0.55);
  --mf-scrim-strong: rgba(0, 0, 0, 0.60);
  /* The fade at the edge of a .table-scroll that says "there is more sideways". */
  --mf-edge-shade:   rgba(0, 0, 0, 0.40);
  /* The diagonal hatch on a "no preview" theme tile. It has to stay visible
     against --mf-surface-raised, which means white-on-dark one way round and
     ink-on-light the other; as a literal it was invisible in exactly one of them. */
  --mf-hatch:        rgba(255, 255, 255, 0.02);

  /* Focus. A visible, consistent focus ring is not a nicety — without one the
     app is unusable by keyboard, and it was previously left to the browser
     default, which is invisible against a dark background in some engines. */
  --mf-focus-ring: 0 0 0 2px var(--mf-bg), 0 0 0 4px var(--mf-brand-500);
  --mf-focus-color: var(--mf-brand-400);

  /* ==========================================================================
     3. SHARED COMPONENT DECISIONS
     ========================================================================== */

  --mf-layout-sidebar: 248px;
  --mf-layout-topbar:  60px;
  --mf-layout-max:     1280px;
  --mf-layout-max-prose: 720px;

  --mf-control-height:    38px;
  --mf-control-height-sm: 30px;
  --mf-control-height-lg: 44px;
  --mf-control-padding-x: var(--mf-space-4);

  --mf-gradient-brand: linear-gradient(135deg, var(--mf-brand-500) 0%, var(--mf-cyan-400) 100%);
  --mf-gradient-text:  linear-gradient(120deg, #ffffff 8%, var(--mf-brand-300) 52%, var(--mf-cyan-400) 100%);
  /* The ambient glow behind the hero. Kept, but pulled well back: it was strong
     enough to lift the whole page's black point, which is why the landing page
     and the dashboard never quite looked like the same product. */
  --mf-gradient-glow:
    radial-gradient(ellipse 900px 520px at 18% 8%,  rgba(124, 92, 255, 0.20), transparent 60%),
    radial-gradient(ellipse 800px 460px at 84% 4%,  rgba(34, 211, 238, 0.13), transparent 62%),
    radial-gradient(ellipse 900px 500px at 50% 92%, rgba(255, 82, 154, 0.07), transparent 65%);

  /* The three blurred orbs that drift behind the landing hero. Separate tokens from
     --mf-gradient-glow above because they animate, and their strength has to be
     tuned against the page they float over rather than inherited from it. */
  --mf-orb-1: rgba(124, 92, 255, 0.11);
  --mf-orb-2: rgba(34, 211, 238, 0.08);
  --mf-orb-3: rgba(255, 82, 154, 0.06);

  /* "Continue with GitHub" is filled in GitHub's own chrome rather than our brand,
     because people recognise that button without reading it and that is the whole
     value it has. Dark UI gets GitHub's near-black; light UI gets the same #24292f
     GitHub itself uses on a white page, with white lettering — not our --mf-fg,
     which on the light palette would be near-black on near-black. */
  --mf-github-bg:        var(--mf-n-1000);
  --mf-github-bg-hover:  var(--mf-n-950);
  --mf-github-bg-active: var(--mf-n-900);
  --mf-github-fg:        var(--mf-fg);

  /* Diff view. Its own small palette, because a diff is read as add/remove/context
     before it is read as text: those three have to stay distinguishable from each
     *other*, not merely from the background, and inheriting the generic ok/danger
     pair would collapse "a removed line" into "an error". */
  --mf-diff-bg:      rgba(255, 255, 255, 0.02);
  --mf-diff-add-bg:  rgba(16, 185, 129, 0.12);
  --mf-diff-add-fg:  #7ee2b8;
  --mf-diff-del-bg:  rgba(239, 68, 68, 0.12);
  --mf-diff-del-fg:  #ffa5a5;
  --mf-diff-hunk-fg: #7c9cff;
  --mf-file-add-bg:  rgba(16, 185, 129, 0.15);
  --mf-file-mod-bg:  rgba(245, 158, 11, 0.15);
  --mf-file-del-bg:  rgba(239, 68, 68, 0.15);

  /* macOS traffic lights on the terminal mock. A quotation of another product's
     chrome, so they are identical in every palette — recolouring them would not
     theme the quotation, it would make it a wrong quotation. */
  /* The hairline drawn *inside* a theme-preview tile, whose fill comes from the
     submitted theme and not from us. Ink at low alpha in both palettes on purpose:
     the case it exists for is a near-white tile, which needs the same help on a
     white page as on a black one. */
  --mf-tile-edge: rgba(0, 0, 0, 0.14);

  --mf-dot-red:    #ff5f56;
  --mf-dot-yellow: #ffbd2e;
  --mf-dot-green:  #27c93f;

  /* The browser's own calendar-picker glyph is near-black and exposes no colour
     property; filter: invert() is the only handle CSS has on it. So the *amount*
     of inversion is the token: 0.7 lifts it off a dark field, 0 leaves it alone on
     a light one. Hardcoding 0.7 made the glyph invisible in the light theme, which
     is the same bug this rule was written to fix, pointing the other way. */
  --mf-picker-invert: 0.7;

  /* ==========================================================================
     4. COMPATIBILITY ALIASES
     Every custom property the pre-existing stylesheets and templates referenced,
     pointed at the semantic layer. Nothing below introduces a new decision — it
     re-homes an old name so existing rules keep working while the palette
     becomes consistent.

     New rules should use the --mf-* names. These stay so that the migration did
     not have to be all-or-nothing, and so that any rule missed in the sweep
     still renders correctly rather than falling back to an unstyled default.
     ========================================================================== */

  --bg-void:         var(--mf-bg-sunken);
  --bg-base:         var(--mf-bg);
  --bg-surface:      var(--mf-surface);
  --bg-surface-2:    var(--mf-surface-raised);
  --bg-surface-hover: var(--mf-surface-hover);

  --border-subtle:   var(--mf-border);
  --border-strong:   var(--mf-border-strong);
  --border-accent:   var(--mf-border-accent);

  --text-primary:    var(--mf-fg);
  --text-secondary:  var(--mf-fg-muted);
  --text-muted:      var(--mf-fg-subtle);

  --accent-a:        var(--mf-accent);
  --accent-b:        var(--mf-cyan-400);
  --accent-purple:   var(--mf-accent);
  --accent-cyan:     var(--mf-cyan-400);
  --accent-pink:     var(--mf-pink-400);
  --accent-emerald:  var(--mf-emerald-400);

  --gradient-brand:  var(--mf-gradient-brand);
  --gradient-text:   var(--mf-gradient-text);
  --gradient-glow:   var(--mf-gradient-glow);
  --shadow-glow:     var(--mf-shadow-brand);

  --radius-sm:  var(--mf-radius-sm);
  --radius-md:  var(--mf-radius-md);
  --radius-lg:  var(--mf-radius-lg);
  --radius-xl:  var(--mf-radius-xl);
  --shadow-soft: var(--mf-shadow-md);

  --font-sans: var(--mf-font-sans);
  --font-mono: var(--mf-font-mono);

  --sidebar-width: var(--mf-layout-sidebar);
  --topbar-height: var(--mf-layout-topbar);

  /* Changelog entry types. Announcement moves off purple so it stops competing
     with the brand accent, which is also purple and also on screen. */
  --badge-feature-bg:      var(--mf-ok-wash);
  --badge-feature-fg:      var(--mf-ok-fg);
  --badge-fix-bg:          var(--mf-info-wash);
  --badge-fix-fg:          var(--mf-info-fg);
  --badge-announcement-bg: var(--mf-accent-wash);
  --badge-announcement-fg: var(--mf-accent-text);
  --badge-warning-bg:      var(--mf-warn-wash);
  --badge-warning-fg:      var(--mf-warn-fg);
  /* "important" sits one step above "warning": red rather than orange, because on the
     client it is the only post type that interrupts the user with a modal (see
     MediaForge's static/devinfo_important.js). */
  --badge-important-bg:    var(--mf-danger-wash);
  --badge-important-fg:    var(--mf-danger-fg);
  /* "release" announces a version — cyan, so it reads as informational next to the
     orange/red pair rather than as another level of alarm. */
  --badge-release-bg:      var(--mf-release-wash);
  --badge-release-fg:      var(--mf-release-fg);

  /* Telemetry severity. Previously a fifth, unrelated palette; now an alias of the
     --mf-sev-* rung in the semantic layer, so the light theme moves all four at
     once instead of four rules each having their own opinion. */
  --sev-crit: var(--mf-sev-crit);
  --sev-high: var(--mf-sev-high);
  --sev-med:  var(--mf-sev-med);
  --sev-low:  var(--mf-sev-low);

  /* ---------------------------------------------------------------------------
     Names referenced *only* from inline `style="…"` attributes in the telemetry
     dashboard templates, where they were written with hardcoded fallbacks:
     `var(--card-bg, #1a1a1a)`, `var(--input-bg, #111)`, `var(--border-color, #333)`.

     Those fallbacks were always what actually rendered, because nothing ever
     defined the variables — which is why the telemetry pages have a slightly
     different grey and a slightly different border to every other dashboard
     page sitting right next to them. Defining them here fixes all of those
     screens at once without editing a single template, and any future inline
     style that reaches for one now lands on the real palette too. */
  --card-bg:      var(--mf-surface);
  --input-bg:     var(--mf-bg-sunken);
  --border-color: var(--mf-border);
  --text-color:   var(--mf-fg);
  --badge-bg:     rgba(255, 255, 255, 0.07);
}

/* =============================================================================
   Alternate palettes

   The light theme is an override of the **semantic layer only** — not one
   component rule knows it exists. That was the promise this file was built on and
   it held: shipping light meant sweeping the last rules that painted a literal
   instead of a token, and then writing the block below. No selector was renamed
   and no layout changed.

   Why the values and the mapping are separated
   --------------------------------------------
   A theme has to be selectable two ways: explicitly (`data-theme="light"`) and by
   asking the operating system (`data-theme="system"` + `prefers-color-scheme`).
   A media query cannot be part of a selector, so the two cases cannot share a
   rule — the assignment list has to appear twice.

   What must NOT appear twice is a decision. So every light value is written once,
   here, as a `--lt-*` staging property, and the two blocks below contain nothing
   but `--mf-x: var(--lt-x)`. Change a colour in one place; both selectors move.
   A reviewer can also verify the two mappings match by eye, because they are
   mechanically identical.

   `data-theme` is written on <html> by the server (see templates/base.html), so
   the palette is decided in the first byte of the response. There is no
   theme-applying script, therefore no window in which the wrong palette is
   painted and then corrected — the flash of the wrong theme is not mitigated
   here, it is structurally impossible.
   ============================================================================= */

:root {
  --lt-bg: #f7f8fb;
  --lt-bg-sunken: #eef0f6;
  --lt-surface: #ffffff;
  --lt-surface-raised: #f7f8fb;
  --lt-surface-hover: #eef0f6;
  --lt-surface-active: #e4e8f0;

  /* Borders are ink at low alpha, not white at low alpha: a white hairline
     on a white card is not a faint border, it is no border. */
  --lt-border: rgba(10, 14, 25, 0.09);
  --lt-border-strong: rgba(10, 14, 25, 0.16);
  --lt-border-accent: rgba(124, 92, 255, 0.38);

  /* Text. Every one of these is measured against the four light surfaces above
     by tests/test_theme.py using store_theme's own WCAG calculation; the muted
     and subtle steps were darkened from the first draft because #6f778a came in
     at 4.49:1 on white — a fail, by 0.01, which is still a fail. */
  --lt-fg: #0d111c;
  --lt-fg-muted: #4a5265;
  /* Two steps down from the first draft (#6f778a → #5f6778 → here). The last step is
     for the badge chips: their background is ink at 6% over the surface, which eats
     roughly half a point of contrast, and 4.39:1 on an 11px uppercase label is a fail. */
  --lt-fg-subtle: #555d6d;

  /* Accent. The 500 stays the accent *fill*; what changes is the accent as
     *text*, which on white has to come down the brand ramp to be readable. */
  --lt-accent-text: var(--mf-brand-700);
  --lt-accent-text-strong: var(--mf-brand-700);
  --lt-accent-text-bright: var(--mf-brand-700);
  --lt-accent-wash: rgba(124, 92, 255, 0.10);
  --lt-accent-wash-strong: rgba(124, 92, 255, 0.18);
  --lt-accent2: var(--mf-cyan-500);
  --lt-accent2-text: #0b6580;
  --lt-accent2-wash: rgba(6, 182, 212, 0.12);
  --lt-accent2-border: rgba(6, 182, 212, 0.34);
  --lt-pink-text: #9d174d;
  --lt-pink-wash: rgba(255, 82, 154, 0.12);

  /* Status. The washes stay tints of the *saturated* hue — a wash is a
     background and does not need to be readable — while the -fg steps move to the
     darkened tone that clears AA on a white card. */
  --lt-ok-fg: #046b4e;
  --lt-warn-fg: #9a3412;
  --lt-danger-fg: #b91c1c;
  --lt-info-fg: #1d4ed8;
  --lt-ok-wash: rgba(16, 185, 129, 0.10);
  --lt-warn-wash: rgba(245, 158, 11, 0.14);
  --lt-danger-wash: rgba(239, 68, 68, 0.10);
  --lt-info-wash: rgba(59, 130, 246, 0.10);
  --lt-ok-border: rgba(4, 107, 78, 0.30);
  --lt-warn-border: rgba(154, 52, 18, 0.28);
  --lt-danger-border: rgba(185, 28, 28, 0.28);
  --lt-info-border: rgba(29, 78, 216, 0.28);

  /* The severity ladder keeps its four distinguishable hues — red, orange,
     amber, green — and every rung is darkened until it reads on a white card. */
  --lt-sev-crit: #b91c1c;
  --lt-sev-high: #b23c0b;
  --lt-sev-med: #7a5b00;
  --lt-sev-low: #046b4e;
  --lt-release-fg: #0369a1;
  --lt-release-wash: rgba(6, 182, 212, 0.12);

  /* Inert rails and the scrollbar go *down* from the surface here, where on
     dark they went up. Same token, opposite direction — which is the whole
     reason they stopped being raw --mf-n-700 references. */
  --lt-track: #d6dae4;
  --lt-track-strong: #c2c8d6;
  --lt-scrollbar: #c2c8d6;
  --lt-scrollbar-hover: #a9b1c3;

  /* A scrim over a light page needs far less black than over a dark one; 55%
     here would read as a blackout rather than a dimmer. */
  --lt-scrim: rgba(16, 24, 40, 0.38);
  --lt-scrim-strong: rgba(16, 24, 40, 0.48);
  --lt-edge-shade: rgba(16, 24, 40, 0.16);
  --lt-hatch: rgba(10, 14, 25, 0.05);

  /* Shadows. On a light page a shadow is the *only* thing separating a white
     card from a near-white page, so it carries more of the layout than it does
     on dark — but tinted and tight, because a heavy black blur on white reads as
     dirt rather than elevation. */
  --lt-shadow-xs: 0 1px 2px rgba(16, 24, 40, 0.06);
  --lt-shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.06), 0 2px 6px -2px rgba(16, 24, 40, 0.08);
  --lt-shadow-md: 0 1px 2px rgba(16, 24, 40, 0.06), 0 8px 24px -12px rgba(16, 24, 40, 0.14);
  --lt-shadow-lg: 0 2px 4px rgba(16, 24, 40, 0.06), 0 24px 56px -20px rgba(16, 24, 40, 0.18);
  --lt-shadow-xl: 0 4px 8px rgba(16, 24, 40, 0.07), 0 40px 80px -24px rgba(16, 24, 40, 0.22);
  --lt-shadow-brand: 0 4px 16px -6px rgba(124, 92, 255, 0.45);

  /* The gradient headline starts at the ink colour instead of white, or the
     first two words of every hero would be invisible. */
  --lt-gradient-text: linear-gradient(120deg, #0d111c 8%, var(--mf-brand-700) 52%, #0b6580 100%);

  /* The ambient wash and the three drifting orbs behind the landing hero.
     Kept — the page would look flat-packed without them — but the tints are
     raised, because a 7% pink that reads on near-black disappears entirely on
     #f7f8fb and leaves the hero with a visible dead zone where an orb should be. */
  --lt-gradient-glow:
    radial-gradient(ellipse 900px 520px at 18% 8%,  rgba(124, 92, 255, 0.16), transparent 60%),
    radial-gradient(ellipse 800px 460px at 84% 4%,  rgba(6, 182, 212, 0.12), transparent 62%),
    radial-gradient(ellipse 900px 500px at 50% 92%, rgba(255, 82, 154, 0.08), transparent 65%);
  --lt-orb-1: rgba(124, 92, 255, 0.16);
  --lt-orb-2: rgba(6, 182, 212, 0.13);
  --lt-orb-3: rgba(255, 82, 154, 0.10);

  /* GitHub's own light-page button: their near-black fill with white lettering.
     --mf-fg would be near-black on near-black here, which is why the button
     carries its own foreground token rather than borrowing the page's. */
  --lt-github-bg: #24292f;
  --lt-github-bg-hover: #32383f;
  --lt-github-bg-active: #1c2126;
  --lt-github-fg: #ffffff;

  /* Diff. The washes keep their hue and the text goes dark; the three file
     badges stay tints, because they are read as a colour and not as text. */
  --lt-diff-bg: rgba(10, 14, 25, 0.03);
  --lt-diff-add-bg: rgba(16, 185, 129, 0.14);
  --lt-diff-add-fg: #046b4e;
  --lt-diff-del-bg: rgba(239, 68, 68, 0.12);
  --lt-diff-del-fg: #a92828;
  --lt-diff-hunk-fg: #3f51b5;
  --lt-file-add-bg: rgba(16, 185, 129, 0.18);
  --lt-file-mod-bg: rgba(245, 158, 11, 0.22);
  --lt-file-del-bg: rgba(239, 68, 68, 0.16);

  /* The calendar glyph is already near-black; inverting it on a white field is
     what made it invisible. */
  --lt-picker-invert: 0;
  --lt-badge-bg: rgba(10, 14, 25, 0.06);
}

/* An explicit choice. Wins over the OS, by construction: this selector matches
   whatever prefers-color-scheme says. */
[data-theme="light"] {
  color-scheme: light;
  --mf-bg: var(--lt-bg);
  --mf-bg-sunken: var(--lt-bg-sunken);
  --mf-surface: var(--lt-surface);
  --mf-surface-raised: var(--lt-surface-raised);
  --mf-surface-hover: var(--lt-surface-hover);
  --mf-surface-active: var(--lt-surface-active);
  --mf-border: var(--lt-border);
  --mf-border-strong: var(--lt-border-strong);
  --mf-border-accent: var(--lt-border-accent);
  --mf-fg: var(--lt-fg);
  --mf-fg-muted: var(--lt-fg-muted);
  --mf-fg-subtle: var(--lt-fg-subtle);
  --mf-accent-text: var(--lt-accent-text);
  --mf-accent-text-strong: var(--lt-accent-text-strong);
  --mf-accent-text-bright: var(--lt-accent-text-bright);
  --mf-accent-wash: var(--lt-accent-wash);
  --mf-accent-wash-strong: var(--lt-accent-wash-strong);
  --mf-accent2: var(--lt-accent2);
  --mf-accent2-text: var(--lt-accent2-text);
  --mf-accent2-wash: var(--lt-accent2-wash);
  --mf-accent2-border: var(--lt-accent2-border);
  --mf-pink-text: var(--lt-pink-text);
  --mf-pink-wash: var(--lt-pink-wash);
  --mf-ok-fg: var(--lt-ok-fg);
  --mf-warn-fg: var(--lt-warn-fg);
  --mf-danger-fg: var(--lt-danger-fg);
  --mf-info-fg: var(--lt-info-fg);
  --mf-ok-wash: var(--lt-ok-wash);
  --mf-warn-wash: var(--lt-warn-wash);
  --mf-danger-wash: var(--lt-danger-wash);
  --mf-info-wash: var(--lt-info-wash);
  --mf-ok-border: var(--lt-ok-border);
  --mf-warn-border: var(--lt-warn-border);
  --mf-danger-border: var(--lt-danger-border);
  --mf-info-border: var(--lt-info-border);
  --mf-sev-crit: var(--lt-sev-crit);
  --mf-sev-high: var(--lt-sev-high);
  --mf-sev-med: var(--lt-sev-med);
  --mf-sev-low: var(--lt-sev-low);
  --mf-release-fg: var(--lt-release-fg);
  --mf-release-wash: var(--lt-release-wash);
  --mf-track: var(--lt-track);
  --mf-track-strong: var(--lt-track-strong);
  --mf-scrollbar: var(--lt-scrollbar);
  --mf-scrollbar-hover: var(--lt-scrollbar-hover);
  --mf-scrim: var(--lt-scrim);
  --mf-scrim-strong: var(--lt-scrim-strong);
  --mf-edge-shade: var(--lt-edge-shade);
  --mf-hatch: var(--lt-hatch);
  --mf-shadow-xs: var(--lt-shadow-xs);
  --mf-shadow-sm: var(--lt-shadow-sm);
  --mf-shadow-md: var(--lt-shadow-md);
  --mf-shadow-lg: var(--lt-shadow-lg);
  --mf-shadow-xl: var(--lt-shadow-xl);
  --mf-shadow-brand: var(--lt-shadow-brand);
  --mf-gradient-text: var(--lt-gradient-text);
  --mf-gradient-glow: var(--lt-gradient-glow);
  --mf-orb-1: var(--lt-orb-1);
  --mf-orb-2: var(--lt-orb-2);
  --mf-orb-3: var(--lt-orb-3);
  --mf-github-bg: var(--lt-github-bg);
  --mf-github-bg-hover: var(--lt-github-bg-hover);
  --mf-github-bg-active: var(--lt-github-bg-active);
  --mf-github-fg: var(--lt-github-fg);
  --mf-diff-bg: var(--lt-diff-bg);
  --mf-diff-add-bg: var(--lt-diff-add-bg);
  --mf-diff-add-fg: var(--lt-diff-add-fg);
  --mf-diff-del-bg: var(--lt-diff-del-bg);
  --mf-diff-del-fg: var(--lt-diff-del-fg);
  --mf-diff-hunk-fg: var(--lt-diff-hunk-fg);
  --mf-file-add-bg: var(--lt-file-add-bg);
  --mf-file-mod-bg: var(--lt-file-mod-bg);
  --mf-file-del-bg: var(--lt-file-del-bg);
  --mf-picker-invert: var(--lt-picker-invert);
  --badge-bg: var(--lt-badge-bg);
}

@media (prefers-color-scheme: light) {
  /* "System" — the default, and the value the server writes when nobody has
     chosen. The OS answers, the browser applies it, and it keeps following the OS
     if the user flips their setting mid-session without reloading. No JavaScript
     is involved in any of that, which is the point: the choice survives a blocked
     or failed script. */
  [data-theme="system"] {
    color-scheme: light;
    --mf-bg: var(--lt-bg);
    --mf-bg-sunken: var(--lt-bg-sunken);
    --mf-surface: var(--lt-surface);
    --mf-surface-raised: var(--lt-surface-raised);
    --mf-surface-hover: var(--lt-surface-hover);
    --mf-surface-active: var(--lt-surface-active);
    --mf-border: var(--lt-border);
    --mf-border-strong: var(--lt-border-strong);
    --mf-border-accent: var(--lt-border-accent);
    --mf-fg: var(--lt-fg);
    --mf-fg-muted: var(--lt-fg-muted);
    --mf-fg-subtle: var(--lt-fg-subtle);
    --mf-accent-text: var(--lt-accent-text);
    --mf-accent-text-strong: var(--lt-accent-text-strong);
    --mf-accent-text-bright: var(--lt-accent-text-bright);
    --mf-accent-wash: var(--lt-accent-wash);
    --mf-accent-wash-strong: var(--lt-accent-wash-strong);
    --mf-accent2: var(--lt-accent2);
    --mf-accent2-text: var(--lt-accent2-text);
    --mf-accent2-wash: var(--lt-accent2-wash);
    --mf-accent2-border: var(--lt-accent2-border);
    --mf-pink-text: var(--lt-pink-text);
    --mf-pink-wash: var(--lt-pink-wash);
    --mf-ok-fg: var(--lt-ok-fg);
    --mf-warn-fg: var(--lt-warn-fg);
    --mf-danger-fg: var(--lt-danger-fg);
    --mf-info-fg: var(--lt-info-fg);
    --mf-ok-wash: var(--lt-ok-wash);
    --mf-warn-wash: var(--lt-warn-wash);
    --mf-danger-wash: var(--lt-danger-wash);
    --mf-info-wash: var(--lt-info-wash);
    --mf-ok-border: var(--lt-ok-border);
    --mf-warn-border: var(--lt-warn-border);
    --mf-danger-border: var(--lt-danger-border);
    --mf-info-border: var(--lt-info-border);
    --mf-sev-crit: var(--lt-sev-crit);
    --mf-sev-high: var(--lt-sev-high);
    --mf-sev-med: var(--lt-sev-med);
    --mf-sev-low: var(--lt-sev-low);
    --mf-release-fg: var(--lt-release-fg);
    --mf-release-wash: var(--lt-release-wash);
    --mf-track: var(--lt-track);
    --mf-track-strong: var(--lt-track-strong);
    --mf-scrollbar: var(--lt-scrollbar);
    --mf-scrollbar-hover: var(--lt-scrollbar-hover);
    --mf-scrim: var(--lt-scrim);
    --mf-scrim-strong: var(--lt-scrim-strong);
    --mf-edge-shade: var(--lt-edge-shade);
    --mf-hatch: var(--lt-hatch);
    --mf-shadow-xs: var(--lt-shadow-xs);
    --mf-shadow-sm: var(--lt-shadow-sm);
    --mf-shadow-md: var(--lt-shadow-md);
    --mf-shadow-lg: var(--lt-shadow-lg);
    --mf-shadow-xl: var(--lt-shadow-xl);
    --mf-shadow-brand: var(--lt-shadow-brand);
    --mf-gradient-text: var(--lt-gradient-text);
    --mf-gradient-glow: var(--lt-gradient-glow);
    --mf-orb-1: var(--lt-orb-1);
    --mf-orb-2: var(--lt-orb-2);
    --mf-orb-3: var(--lt-orb-3);
    --mf-github-bg: var(--lt-github-bg);
    --mf-github-bg-hover: var(--lt-github-bg-hover);
    --mf-github-bg-active: var(--lt-github-bg-active);
    --mf-github-fg: var(--lt-github-fg);
    --mf-diff-bg: var(--lt-diff-bg);
    --mf-diff-add-bg: var(--lt-diff-add-bg);
    --mf-diff-add-fg: var(--lt-diff-add-fg);
    --mf-diff-del-bg: var(--lt-diff-del-bg);
    --mf-diff-del-fg: var(--lt-diff-del-fg);
    --mf-diff-hunk-fg: var(--lt-diff-hunk-fg);
    --mf-file-add-bg: var(--lt-file-add-bg);
    --mf-file-mod-bg: var(--lt-file-mod-bg);
    --mf-file-del-bg: var(--lt-file-del-bg);
    --mf-picker-invert: var(--lt-picker-invert);
    --badge-bg: var(--lt-badge-bg);
  }
}

/* `[data-theme="dark"]` needs no block at all — :root above *is* the dark palette.
   Naming it anyway would create a second place for a dark value to live, which is
   the drift this file was written to end. */

/* Respect the OS setting for people who have asked for less movement. Applied
   globally so no individual component has to remember to. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* =============================================================================
   Global baseline

   Deliberately small. It sets the things that must be true on every page — box
   model, font rendering, focus visibility, selection colour — and stops. Layout
   and components stay in their own files.
   ============================================================================= */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  font-family: var(--mf-font-sans);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* Inter's tabular figures and contextual alternates. Numbers that change in
     place (counters, live stats) stop jittering as their glyph widths change. */
  font-feature-settings: "cv05" 1, "ss01" 1;
}

/* Numeric UI — stat values, table figures, versions — should not reflow as the
   digits change. Applied by the component files via this utility. */
.mf-tnum,
.stat-value,
.kpi-value {
  font-variant-numeric: tabular-nums;
}

/* Focus. `:focus-visible` rather than `:focus` so a mouse click on a button
   does not leave a ring behind, while keyboard navigation always shows one. */
:focus-visible {
  outline: none;
  box-shadow: var(--mf-focus-ring);
  border-radius: var(--mf-radius-xs);
}

::selection {
  background: var(--mf-accent-wash-strong);
  color: var(--mf-fg);
}

/* A consistent scrollbar. The default light scrollbar on a near-black page is
   the single most jarring piece of chrome in the app. */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--mf-scrollbar) transparent;
}

*::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}

*::-webkit-scrollbar-track {
  background: transparent;
}

*::-webkit-scrollbar-thumb {
  background: var(--mf-scrollbar);
  border: 3px solid transparent;
  background-clip: content-box;
  border-radius: var(--mf-radius-full);
}

*::-webkit-scrollbar-thumb:hover {
  background: var(--mf-scrollbar-hover);
  background-clip: content-box;
}
