/* ═══════════════════════════════════════════════════════════════════════════
   styles/tokens.css — the design system
   ═══════════════════════════════════════════════════════════════════════════

   The page is a drawing sheet. A cool light-steel ground with structural
   gridlines running its full length (see .gl in site.css), steel-dark
   chapters, near-black ink, and one accent: the red from Kingson's own
   wordmark. Square corners throughout — steel is cut, not moulded.

   Every text pairing below was measured, not eyeballed; the ratios are in
   the comments.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── metric-matched fallbacks ─────────────────────────────────────────────────
   Until the two webfonts arrive the page is set in Arial, and Arial's line
   metrics are not Archivo's or Inter's: at the hero's size the headline sat
   0.08em lower in the fallback and jumped when Archivo swapped in (measured
   CLS 0.017 on a fast connection). These faces are Arial with the vertical
   metrics read off the shipped files (fontTools: Archivo 878/210 per 1000,
   Inter 1984/494 per 2048), so the swap changes the letterforms and not the
   line positions. */
@font-face {
  font-family: "Archivo Fallback";
  src: local("Arial"), local("Helvetica"), local("Liberation Sans");
  ascent-override: 87.8%; descent-override: 21%; line-gap-override: 0%;
}
/* The display headings wrap, so their fallback has to wrap where Archivo
   will or the lines below move when it arrives: a service title that fits on
   one Arial line and takes two in Archivo pushed the photograph down 83px.
   Archivo 800 at 125% width measured 1.30–1.34 × Arial Bold across every
   heading on the site, so this is Arial Bold at 132%. It is rarely seen: the
   first sheet's title is held back until Archivo is in (see <head>). */
@font-face {
  font-family: "Archivo Display Fallback";
  src: local("Arial Bold"), local("Arial-BoldMT"), local("Helvetica Bold"), local("Liberation Sans Bold");
  font-weight: 600 900;
  size-adjust: 132%;
  ascent-override: 66.5%; descent-override: 15.9%; line-gap-override: 0%;
}
@font-face {
  font-family: "Inter Fallback";
  src: local("Arial"), local("Helvetica"), local("Liberation Sans");
  size-adjust: 107%;
  ascent-override: 90.5%; descent-override: 22.5%; line-gap-override: 0%;
}

:root {
  /* ── ground ─────────────────────────────────────────────────────────── */
  /* Cinema: the whole site is steel at night, lit by its own photographs.
     Every pairing measured: body ink 17.7:1, muted 10.4:1, faint 6.7:1 on
     the ground; the small-text red 5.8:1. */
  --bg:          #0B0E10;   /* the ground                                  */
  --bg-soft:     #12161A;   /* an alternating band                         */
  --surface:     #181D21;   /* a panel, a field, a table                   */
  --bg-dark:     #07090A;   /* the deepest bands: title block, footer      */
  --bg-dark-2:   #12161A;

  /* ── ink ────────────────────────────────────────────────────────────── */
  --fg:          #F3F5F6;
  --fg-muted:    #B6BFC4;
  --fg-faint:    #8F999F;
  --fg-on-dark:  #F3F5F6;
  --fg-on-dark-muted: #B6BFC4;

  /* ── lines ──────────────────────────────────────────────────────────── */
  --line:        #252C31;
  --line-strong: #3A444A;
  --line-dark:   #252C31;

  /* ── the structural grid ─────────────────────────────────────────────── */
  --gl-ink:      rgba(243, 245, 246, .055);
  --gl-ink-dark: rgba(243, 245, 246, .055);

  /* ── brand ──────────────────────────────────────────────────────────────
     Measured off the logo artwork Kingson supplied, not invented. The red is
     a fortunate mid-tone: it clears 4.72:1 both as text on white and as the
     ground under white text, so the actual brand colour can carry the buttons
     instead of an approximation of it. The darker step is for small text,
     where more headroom is worth having.
     Green is the tagline colour and stays a secondary signal — it marks what
     Kingson has confirmed, and nothing else. */
  --red:       #E21E25;   /* brand · buttons, the mark          4.72:1 both ways */
  --red-ink:   #EF5A5F;   /* small red text on the dark ground   5.8:1           */
  --red-hover: #C4181E;
  --red-soft:  #2A1214;
  --red-on:    #ffffff;

  /* The brand red is 3.9:1 on steel — fine for a large figure, not for a
     12px label. This is the same hue lifted until it clears 4.5:1 on every
     dark ground the page uses: 5.5:1 on steel, 4.5:1 on the darkest panel. */
  --red-on-dark: #EF5A5F;

  --green:     #039647;   /* brand tagline                                      */
  --green-ink: #02803C;   /* green as text                      5.05:1 on white  */
  --green-soft:#E8F5EC;
  /* Green on the green ground. --green-ink is measured against white and lands
     at exactly 4.50:1 on --green-soft, which is the threshold rather than a
     margin. This step is 6.03:1 there and 6.78:1 on white. */
  --green-on-soft: #016A2C;

  --mark:      #2B2A28;   /* the drawn mark in the logo                          */

  /* kept as aliases so component CSS reads by role, not by colour */
  --accent:       var(--red);
  --accent-hover: var(--red-hover);
  --accent-ink:   var(--red-ink);
  --accent-soft:  var(--red-soft);
  --accent-on:    var(--red-on);
  --accent-on-dark: var(--red-on-dark);

  /* the eyebrow dots */
  --dot-a: var(--red);
  --dot-b: var(--green);

  /* ── shape ──────────────────────────────────────────────────────────── */
  /* Square. Only the grid bubbles and the step balloons are round, because
     on a drawing those are circles. */
  --r-sm: 0;
  --r:    0;
  --r-lg: 0;
  --r-xl: 0;
  --pill: 9999px;

  --shadow-sm: none;
  --shadow:    0 1px 0 rgba(12, 15, 17, .06);
  --shadow-lg: 0 24px 48px -28px rgba(12, 15, 17, .45);

  /* ── measure ────────────────────────────────────────────────────────── */
  /* The page runs wide. Long text is held to its own measure (ch) inside
     it; what the width buys is scale for the type and the photographs. */
  --margin: clamp(20px, 4.4vw, 64px);
  --page:   1440px;
  --gut:    clamp(16px, 2.2vw, 32px);
  --ease:   cubic-bezier(.22, 1, .36, 1);

  /* ── type ───────────────────────────────────────────────────────────── */
  --t-body:  16px;
  --t-lede:  clamp(17px, 1.3vw, 20px);
  --t-h1:    clamp(44px, 6.4vw, 104px);
  --t-h2:    clamp(38px, 5.9vw, 96px);
  --t-h3:    clamp(19px, 1.5vw, 22px);
  --t-ui:    15px;
  --t-small: 13.5px;
  --t-label: 12px;
}

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 92px;      /* the sticky header */
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: Inter, "Inter Fallback", ui-sans-serif, system-ui, sans-serif;
  font-size: var(--t-body);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

img { display: block; max-width: 100%; border-style: none; }
button, input, select, textarea { font: inherit; color: inherit; }
p, h1, h2, h3, h4, figure, ul, ol, li, dl, dd { margin: 0; padding: 0; list-style: none; }
a { color: inherit; text-decoration: none; }

/* Headings are Archivo at 700 — the reference's bold system sans, with better
   letterfit. Not the -.045em display tracking the previous build used. */
/* Archivo's width axis carries the identity: headlines expanded, like the
   lettering stamped on a steel plate; large figures condensed, like the
   dimension text on a drawing (see .ch-big and the card figures). One family
   doing the work a second typeface usually would. */
.display {
  font-family: Archivo, "Archivo Display Fallback", "Archivo Fallback", sans-serif;
  font-weight: 800; font-stretch: 125%;
  letter-spacing: -.032em;
  line-height: 1.05;
  text-wrap: balance;
}

:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.on-dark :focus-visible { outline-color: var(--fg-on-dark); }
::selection { background: var(--accent); color: #fff; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

.skip {
  position: fixed; top: 10px; left: 10px; z-index: 300;
  padding: 12px 20px; background: var(--fg); color: #fff;
  border-radius: var(--r); font-weight: 500; transform: translateY(-160%);
}
.skip:focus { transform: none; }

[hidden] { display: none !important; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important; animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   the motion vocabulary — see scenes/reveal.js
   ═══════════════════════════════════════════════════════════════════════════

   Every rule below is scoped to `html.reveal-ready`, which only exists once
   the script has an observer in hand. With no JavaScript the page is simply
   visible, which is the correct default.

   The easings are the point. `--ease-heavy` decelerates hard and never
   overshoots — steel has inertia and does not bounce. `--ease-exact` is nearly
   linear out: a laser head does not ease into position, it arrives.
   ═══════════════════════════════════════════════════════════════════════════ */

:root {
  --ease-heavy: cubic-bezier(.16, .84, .24, 1);
  --ease-exact: cubic-bezier(.3, .9, .4, 1);
  --ease-power: cubic-bezier(.22, .68, .18, 1);
  --t-plate: 1100ms;
  --t-rise:   820ms;
  --t-settle:1400ms;
  --t-draw:   900ms;
  --t-swing: 1500ms;
}

html.reveal-ready [data-reveal] { will-change: clip-path, transform, opacity; }
html.reveal-ready [data-reveal].is-revealed { will-change: auto; }

/* ── plate: a sheet slid into place ───────────────────────────────────────────
   NOT clip-path on the observed element. `clip-path: inset(0 0 0 100%)` clips
   the box to zero area, and an element with zero area never reports as
   intersecting — so the IntersectionObserver that is supposed to reveal it can
   never fire, and it stays hidden for ever. That deadlock cost four laser
   photographs and a chapter aside before it was caught.

   Instead the element keeps its full box and a cover retracts off it. The
   cover has to match the ground it sits on, which is what --plate-cover is
   for; each chapter sets it. */
html.reveal-ready [data-reveal="plate"] { position: relative; overflow: hidden; }
html.reveal-ready [data-reveal="plate"]::after {
  content: ""; position: absolute; inset: 0; z-index: 3;
  background: var(--plate-cover, var(--bg));
  transform: scaleX(1); transform-origin: 100% 50%;
  transition: transform var(--t-plate) var(--ease-heavy);
}
html.reveal-ready [data-reveal="plate"][data-from="left"]::after  { transform-origin: 0 50%; }
html.reveal-ready [data-reveal="plate"][data-from="below"]::after {
  transform: scaleY(1); transform-origin: 50% 100%;
}
html.reveal-ready [data-reveal="plate"].is-revealed::after { transform: scaleX(0); }
html.reveal-ready [data-reveal="plate"][data-from="below"].is-revealed::after { transform: scaleY(0); }

/* ── rise: type out from behind a datum ────────────────────────────────── */
html.reveal-ready [data-reveal="rise"] > * {
  transform: translateY(102%);
  transition: transform var(--t-rise) var(--ease-heavy);
}
html.reveal-ready [data-reveal="rise"] { overflow: hidden; }
html.reveal-ready [data-reveal="rise"].is-revealed > * { transform: none; }

/* ── settle: a camera coming to rest ──────────────────────────────────── */
html.reveal-ready [data-reveal="settle"] > img,
html.reveal-ready [data-reveal="settle"] > picture > img {
  transform: scale(1.045);
  transition: transform var(--t-settle) var(--ease-power);
}
html.reveal-ready [data-reveal="settle"].is-revealed > img,
html.reveal-ready [data-reveal="settle"].is-revealed > picture > img { transform: none; }

/* ── draw: fast and exact ─────────────────────────────────────────────── */
html.reveal-ready [data-reveal="draw"] [data-draw-path] {
  stroke-dasharray: var(--len, 1000);
  stroke-dashoffset: var(--len, 1000);
  transition: stroke-dashoffset var(--t-draw) var(--ease-exact);
}
html.reveal-ready [data-reveal="draw"].is-revealed [data-draw-path] { stroke-dashoffset: 0; }
html.reveal-ready [data-reveal="draw"] [data-draw-fade] {
  opacity: 0; transition: opacity 420ms linear 420ms;
}
html.reveal-ready [data-reveal="draw"].is-revealed [data-draw-fade] { opacity: 1; }

/* ── lift: items in a list, in the order they happen ──────────────────────────
   The sixth of the vocabulary, and the only one that moves a group rather
   than a thing.

   `rise` is for one line of type coming out from behind a datum: it masks its
   container and translates a single child. Pointed at a list it clips each row
   against the next and they slide over one another. A list needs its own
   motion, and the honest one for a list whose ORDER is its meaning — five
   commitments in sequence, the specification groups, the ways to reach the
   workshop — is arrival in that order.

   Short and small on purpose: 14px and a fade, not a slide across the screen.
   The stagger does the talking. scenes/reveal.js sets the per-child delay from
   `data-reveal-stagger`, so the markup says how far apart, not how far.       */
html.reveal-ready [data-reveal="lift"] > * {
  opacity: 0; transform: translateY(14px);
  transition: opacity var(--t-rise) var(--ease),
              transform var(--t-rise) var(--ease-power);
}
html.reveal-ready [data-reveal="lift"].is-revealed > * {
  opacity: 1; transform: none;
}

/* ── swing: slow and powerful, about a pivot ──────────────────────────── */
html.reveal-ready [data-reveal="swing"] {
  transform: rotate(-3.2deg) translateY(3%);
  transform-origin: 12% 100%;
  transition: transform var(--t-swing) var(--ease-power);
}
html.reveal-ready [data-reveal="swing"].is-revealed { transform: none; }

@media (prefers-reduced-motion: reduce) {
  html.reveal-ready [data-reveal],
  html.reveal-ready [data-reveal]::after,
  html.reveal-ready [data-reveal] > *,
  html.reveal-ready [data-reveal] [data-draw-path],
  html.reveal-ready [data-reveal] [data-draw-fade] {
    clip-path: none !important; transform: none !important;
    opacity: 1 !important; stroke-dashoffset: 0 !important;
  }
  html.reveal-ready [data-reveal="plate"]::after { display: none !important; }
}
