/* ——————
   King AI · Static SEO pages — THE TOKEN LAYER

   Loaded before page.css, after each page's inline <style>. Three things:
     1. the self-hosted webfonts and their metric-matched stand-ins;
     2. the design tokens in both themes (`:root` / `html.light`) plus the
        `.on-dark` island pin;
     3. the ledger voice, the outline face, and the shared header controls.

   Everything that paints a component lives in page.css and reads these
   tokens. That split is the point of the port: the ~83 hand-written pages
   carry no theme knowledge, so a change here restyles all of them at once
   and the light theme needs no mirror rules.

   THE RULE THIS FILE EXISTS TO ENFORCE: never author a colour for text or a
   hairline as a literal rgba(255,255,255,a) / rgba(20,21,26,a). Those have no
   light-theme meaning and each one used to need a `:root.light` twin. Go
   through `rgb(var(--c-fg) / a)`, `rgb(var(--c-muted))` and
   `rgb(var(--c-line) / a)`: already correct in both themes, and inside a dark
   island on the light page.
   —————— */

/* ————— Webfonts —————
   Self-hosted, same-origin, mirrored from Google Fonts (latin + latin-ext).
   The pages used to pull the stylesheet from fonts.googleapis.com and the
   faces from fonts.gstatic.com: 57KB across two third-party origins, 61% of a
   page's weight, in front of first paint (cold LCP 3,292ms vs 960ms warm).
   Same four files the React landing ships, already `Cache-Control: public,
   max-age=31536000, immutable`. Inter is the variable file — one request
   covers weights 400-800. */
@font-face {
  font-family: "Bebas Neue";
  font-style: normal;
  font-weight: 400;
  /* `swap`, and the display face can have it because the stand-in below is
     tuned to hold its ADVANCE WIDTHS, not just its baseline and line box.
     That was the real objection to `swap` here and it was correct of the
     mixed-case "Bebas Neue Fallback": a long all-caps headline wrapped to a
     different number of lines and the page jumped when Bebas arrived.
     "Bebas Caps Fallback" is size-adjusted by the ratio measured across six
     real spoke headlines, and with it the line counts agree — CLS across all
     six page families in both themes at 1440 and 390 measures 0.0007, against
     0.0773 before. `block` also fixes the jump, but only by hiding the
     headline until the font lands: it does not prevent the reflow, it just
     moves it in front of first paint, and on a slow connection it costs up to
     three seconds of invisible LCP text. With the widths matched there is
     nothing left to hide. */
  font-display: swap;
  src: url(/fonts/bebas-neue-400-latin.woff2) format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Bebas Neue";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/fonts/bebas-neue-400-latin-ext.woff2) format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url(/fonts/inter-var-latin.woff2) format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url(/fonts/inter-var-latin-ext.woff2) format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ————— Metric-matched fallbacks —————
   A local Arial, size-adjusted to occupy the same box as the webfont it
   stands in for, turning the `display: swap` moment into a zero-shift repaint
   instead of a reflow. Self-hosting alone only makes the swap arrive sooner;
   this is the half that removes the CLS. Values computed from the real font
   metrics via @capsizecss/metrics, identical to the React landing's. Both
   families are named immediately after their real face in --font-body /
   --font-display below. */
@font-face {
  font-family: "Bebas Neue Fallback";
  src: local("Arial"), local("ArialMT");
  ascent-override: 117.316%;
  descent-override: 39.1053%;
  line-gap-override: 0%;
  size-adjust: 76.7159%;
}

@font-face {
  font-family: "Inter Fallback";
  src: local("Arial"), local("ArialMT");
  ascent-override: 90.4365%;
  descent-override: 22.518%;
  line-gap-override: 0%;
  size-adjust: 107.1194%;
}

/* ————— The uppercase stand-in for Bebas —————
   "Bebas Neue Fallback" above is Capsize's MIXED-CASE average — right for the
   SPA, wrong here, where the display face is always uppercase and Bebas's caps
   are far narrower than its lower case. Measured across six real spoke
   headlines, Bebas sets at 74.7% of the mixed-case stand-in's width
   (0.732–0.773 across the set), so a heading wrapped to four lines in the
   stand-in and three in Bebas and the swap took 84px out of the hero: 0.078 of
   layout shift, the whole remaining CLS after the ch→em fix.

   Same Arial, size-adjusted by that measured ratio (76.7159 × 0.7471), the
   vertical overrides scaled to match so the baseline lands in the same place.
   Only advance widths matter for the shift — every Bebas rule on these pages
   sets a UNITLESS line-height, so the line box is font-size × ratio whichever
   face is in it and this stand-in only decides how many boxes there are.
   "Bebas Neue Fallback" stays declared, verbatim, behind it. */
@font-face {
  font-family: "Bebas Caps Fallback";
  src: local("Arial"), local("ArialMT");
  ascent-override: 157.03%;
  descent-override: 52.35%;
  line-gap-override: 0%;
  size-adjust: 57.3119%;
}

/* —————— Tokens —————— */
:root {
  /* Canvas + ink. Channel triplets, not colours, so every call site can take
     its own alpha: `rgb(var(--c-fg) / 0.55)`. Same values as
     landing/src/index.css. */
  --c-base: 8 8 8;
  --c-surface: 20 20 20;
  --c-surface-2: 30 30 30;
  --c-fg: 255 255 255;
  /* The ONE muted step: 7.7:1 on film, 5.4:1 on paper. Anything quieter fails
     AA at the sizes these pages use, so there is deliberately no third step —
     a paragraph is either --c-fg or --c-muted. */
  --c-muted: 160 160 160;
  --c-line: 255 255 255;

  --font-display: "Bebas Neue", "Bebas Caps Fallback", "Bebas Neue Fallback", Inter, "Inter Fallback", ui-sans-serif, system-ui, sans-serif;
  --font-body: Inter, "Inter Fallback", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-mono: ui-monospace, "SF Mono", SFMono-Regular, "JetBrains Mono", Menlo, Consolas, monospace;

  --ease: cubic-bezier(0.16, 1, 0.3, 1);
  --shadow-panel: 0 26px 70px -32px rgba(0, 0, 0, 0.9);
  --shadow-accent: 0 18px 50px -12px rgba(209, 254, 23, 0.4);

  /* A contact sheet is frames, and a frame has square corners. Pills stay
     pills (999px); anything holding a picture or a block of copy uses this. */
  --r-frame: 2px;
  --film-opacity-static: 0.045;

  /* ————— The one accent, in two roles —————
       FILL — a solid lime plate, IDENTICAL in both themes, always with
              near-black type on it. Lime is a LIGHT colour: white on it is
              1.2:1.
       INK  — the accent for TYPE, ICONS and HAIRLINES. It must darken on the
              light canvas; #D1FE17 type on white is 1.4:1.
     Filling a badge with the ink value gives an army-green object, so never
     swap the two. */
  --accent-fill: #d1fe17;
  --accent-fill-hover: #dcfe45;
  --on-accent: #14151a;
  --accent-ink: #d1fe17;
}

html.light {
  --c-base: 244 245 247;
  --c-surface: 255 255 255;
  --c-surface-2: 235 236 239;
  --c-fg: 20 21 26;
  --c-muted: 98 100 108;
  --c-line: 20 21 26;

  /* A lime-tinted shadow on a light page is a yellow stain, not a glow. */
  --shadow-panel: 0 22px 50px -30px rgba(20, 21, 26, 0.22);
  --shadow-accent: 0 14px 38px -12px rgba(20, 21, 26, 0.24);

  /* #5C7305, not the #677F05 the design doc names. Measured: #677F05 is
     4.55:1 on pure white — which is where that value was chosen — but only
     4.17:1 on this canvas (#F4F5F7) and 3.9:1 on --c-surface-2, so accent
     TYPE at body size failed AA on every light spoke page. #5C7305 is the
     same hue at 4.92:1 on the canvas, 4.55:1 on --c-surface-2 and 5.4:1 on
     white — the brightest value in the family that clears AA against every
     light surface these pages actually use. */
  --accent-ink: #5c7305;
}

/* A subtree that sits on film in BOTH themes — the closing CTA band, the media
   plates. Re-pins the core tokens to their dark values, so a caption, hairline
   or vignette authored on --c-* resolves dark inside the island while the page
   around it is paper. Pinning --c-base too is what stops a vignette or grain
   plate resolving to off-white in the light theme. */
.on-dark {
  --c-base: 8 8 8;
  --c-surface: 20 20 20;
  --c-surface-2: 30 30 30;
  --c-fg: 255 255 255;
  --c-muted: 160 160 160;
  --c-line: 255 255 255;
  --accent-ink: #d1fe17;
  color-scheme: dark;
}

/* A dark island still casts its shadow onto the LIGHT canvas around it, so
   its elevation follows the page theme, not the island. */
html.light .on-dark {
  --shadow-panel: 0 22px 54px -30px rgba(20, 21, 26, 0.32);
}

/* ————— The film layer —————
   Ported from landing/src/styles/film.css and deliberately thinner: reading
   pages get no HIGH tier, so the grain never steps or blends and there is no
   gate weave, letterbox travel, dither or scan line. What survives carries the
   identity — the ledger voice, the outlined display face, and the 512px grain
   plate, which page.css tiles straight onto the two surfaces that are islands
   in both themes (`.band`, `.mock`) rather than through a `.film` element no
   page's markup could contain.

   Light-theme rule, in one place: nothing here paints a lime FILL, and every
   mark takes the ink family through the --c-* tokens — white on film,
   near-black on paper, white again inside an island on a light page.

   Never here: `filter:` animations, glow halos, runtime chromatic aberration.
   See docs/superpowers/specs/systems/typefilm.md. */

/* ————— The LEDGER voice —————
   Every eyebrow, breadcrumb, caption, counter, frame number, table head and
   colophon row. Colour is inherited on purpose — the call site chooses fg or
   muted. The single strongest signal that a spoke page and the homepage were
   set by the same hand. */
.slug,
.eyebrow,
.breadcrumb,
.chip,
.article-meta,
.post-meta,
.crosslink .k,
.post-card .k,
.proof-item figcaption,
.proof-cap,
.toc-title,
.plan-name,
.plan-credits,
.pack-label,
.badge-lg,
.cmp thead th,
footer.site .foot-cols h4,
.mobile-menu .menu-group h3,
.nav-dropdown .col-title {
  font-family: var(--font-mono);
  font-size: clamp(11px, 0.625vw, 13px);
  font-weight: 400;
  line-height: 1.35;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1, "zero" 1;
}

/* ————— Outlined display type —————
   The one typographic gasp, spent twice per page: the big step numerals
   (page.css) and the colophon wordmark shell.js appends. Transparent fill,
   stroke in the foreground family at alpha 0.55 — high enough to clear the
   3:1 large-text threshold in both themes, which is the only contrast a
   stroked glyph can be judged on. The `var()` fallbacks matter: without them
   a browser that cannot register the custom properties drops the whole
   declaration and paints nothing. */
.outline-type {
  color: transparent;
  -webkit-text-stroke: var(--stroke-w, 0.022em) rgb(var(--c-fg) / var(--stroke-a, 0.55));
  paint-order: stroke fill;
}

/* —————— Shared header controls —————— */

.bar-actions {
  display: flex;
  align-items: center;
  gap: 10px;
}

.brand-icon {
  width: 34px;
  height: 34px;
  border-radius: 26%;
  object-fit: cover;
  display: block;
  border: 1px solid rgb(var(--c-line) / 0.16);
}

.seo-lang {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  border: 1px solid rgb(var(--c-line) / 0.16);
  background: transparent;
  border-radius: 999px;
  padding: 8px 13px;
  transition: border-color 200ms var(--ease);
}

.seo-lang:hover,
.seo-lang:focus-within {
  border-color: rgb(var(--c-line) / 0.4);
}

.seo-lang > svg {
  width: 13px;
  height: 13px;
  color: rgb(var(--c-fg) / 0.55);
  flex-shrink: 0;
  pointer-events: none;
}

.seo-lang select {
  appearance: none;
  -webkit-appearance: none;
  border: 0;
  background: transparent;
  color: rgb(var(--c-fg));
  padding: 0;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 400;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  cursor: pointer;
}

/* Native dropdown menus render on a light surface on most platforms, so
   keep option text dark regardless of the page theme. */
.seo-lang select option {
  color: #14151a;
  background: #ffffff;
}

/* The button ships EMPTY — shell.js writes its icon and label — so without a
   reserve it paints at 28×18 and then grows, shoving every control right along
   the bar, and 28×18 also failed WCAG 2.2's 24px minimum until the script
   arrived. The reserve fits the longest label in all five locales. */
.seo-theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-width: 98px;
  min-height: 32px;
  padding: 8px 13px;
  border-radius: 999px;
  border: 1px solid rgb(var(--c-line) / 0.16);
  background: transparent;
  color: rgb(var(--c-fg));
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 400;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  cursor: pointer;
  transition: border-color 200ms var(--ease);
}

.seo-theme-toggle:hover {
  border-color: rgb(var(--c-line) / 0.4);
}

/* shell.js writes the icon and label. Until it does — and for ever if the
   script is blocked — the button is a reserved but empty pill sitting in the
   bar. `:empty` stops matching the moment the innerHTML lands, so this hides
   it exactly while there is nothing in it. Higher specificity than the
   `display: inline-flex` the xl media query in page.css sets, so it wins
   whatever the order. */
header.site .seo-theme-toggle:empty,
.mobile-menu .seo-theme-toggle:empty {
  display: none;
}

.seo-theme-toggle svg {
  width: 13px;
  height: 13px;
  color: rgb(var(--c-fg) / 0.55);
  flex-shrink: 0;
}

