/* tokens.css — the only file that sets a literal colour, duration or radius.
 *
 * Everything else references var(--…). That is what makes the tint toggle a
 * two-line change instead of a hunt through five stylesheets, and it is why
 * the dark block below restates the palette without touching a component.
 *
 * The type scale is fixed integers, not clamp(). Departure Mono is a bitmap
 * face and blurs at fractional sizes; the rounded face is set on the same scale
 * so the two never disagree about what "14px" means.
 */

@font-face {
  font-family: 'M PLUS Rounded 1c';
  src: url('../fonts/MPLUSRounded1c-Regular.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'M PLUS Rounded 1c';
  src: url('../fonts/MPLUSRounded1c-Medium.woff2') format('woff2');
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'M PLUS Rounded 1c';
  src: url('../fonts/MPLUSRounded1c-ExtraBold.woff2') format('woff2');
  font-weight: 800; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Departure Mono';
  src: url('../fonts/DepartureMono-Regular.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
}

:root {
  /* --- ground and surfaces ---
   * Black and white, no hue anywhere. The greys that remain are the ones text
   * sits on, so they are the only values here that can fail a contrast pair —
   * the pure ends are 20:1 and take care of themselves.
   *
   * The ground is a MID grey, not near-white, and that is load-bearing rather
   * than taste: the cards are paper now, and paper on a near-white ground is
   * separated only by its border. Darkening the ground buys a real 1.5:1
   * luminance step between card and ground while every text pair on the ground
   * keeps its headroom (ink lands at 11.4:1, the dimmest grey at 3.5:1). It is
   * also what the reference actually shows — light cards on a grey board. */
  --ground:      #cbcbc9;
  --ground-lift: #d9d9d7;
  --ground-edge: #bcbcba;

  /* graph paper: a fine rule, a heavier one every fifth, and a dot where the
     heavy ones cross. Three values so the whole texture retunes from here. */
  --grid-fine:   rgb(20 24 28 / .06);
  --grid-heavy:  rgb(20 24 28 / .15);
  --grid-node:   rgb(20 24 28 / .40);
  --grid-step:   34px;
  --tile:        #ffffff;
  --tile-2:      #f4f4f4;
  --tile-edge:   #0a0a0a;   /* borders are ink here, not a hairline */

  --ink:    #0a0a0a;
  --ink-2:  #3d3d3d;
  --ink-3:  #606060;
  --rule:   #d4d4d4;

  /* --- selection ---
   * There is no accent colour to reach for, so emphasis is carried by polarity
   * instead: --sel is ink, and anything sitting on it flips to paper. */
  --sel:      #0a0a0a;
  --sel-deep: #0a0a0a;   /* links and switches — must read against the GROUND */
  --sel-ink:  #ffffff;   /* text sitting ON --sel */

  --row-active: linear-gradient(180deg, #f2f2f2 0%, #e6e6e6 100%);
  --badge-ring: #ffffff;

  --ok:   #0a0a0a;
  --warn: #3d3d3d;

  /* the ring and its halo — has to stay visible on any ground, including the
     black boot screen and the pale active row */
  --focus-core: #ffffff;
  --focus-halo: #0a0a0a;

  /* --- shape --- */
  --r-tile:  20px;   /* properly rounded, like the reference's channel tiles */

  /* The card's aspect ratio, as TWO multiply-only tokens rather than one that
     gets divided. `calc()` dividing by a variable is legal and widely supported
     and still the kind of clever that fails quietly on the one browser you did
     not test, and the grid's whole size derivation hangs off it. They are
     reciprocals — 12/5 and 5/12 — and check-contrast.mjs asserts that, because
     editing one and forgetting the other produces tiles that are subtly the
     wrong shape rather than an error anyone would notice. */
  --ar:      2.4;      /* width  ÷ height */
  --ar-inv:  .4166667; /* height ÷ width  */

  --stage-pad:   clamp(10px, 2.5vw, 28px);  /* the stage's inline padding */
  --stage-pad-y: clamp(8px, 1.6vh, 16px);   /* and its vertical padding — the
                        grid's maths subtracts exactly these two, so a change
                        here cannot silently push the bottom row under the bar */
  --r-panel: 26px;
  --r-sm:     9px;
  --r-row:   18px;
  --r-pill:  999px;

  --shadow-tile: 0 1px 1px rgb(22 26 22 / .05), 0 3px 6px rgb(22 26 22 / .09);
  --shadow-lift: 0 4px 8px rgb(22 26 22 / .10), 0 12px 26px rgb(22 26 22 / .16);
  --shadow-panel: 0 16px 60px rgb(22 26 22 / .30);
  --bevel: inset 0 1px 0 rgb(255 255 255 / .9);
  --scrim: rgb(0 0 0 / .55);

  /* All three faces are the same. They used to be two inked and one paper —
     polarity as the thing telling them apart — and that odd one out is exactly
     what read as wrong. The icon and the name do the distinguishing now, the
     way the reference does it.
     Kept as three tokens rather than one because panels.js reads
     var(--card-<id>) for each panel badge. */
  --card-projects: #f6f6f4;
  --card-builds:   #f6f6f4;
  --card-resume:   #f6f6f4;
  --card-projects-ink: #0f1214;
  --card-builds-ink:   #0f1214;
  --card-resume-ink:   #0f1214;
  /* Near-flat paper with the faintest top lift. The old face was a four-stop
     brushed sweep running to #9a9d9f — a lot of gradient for something the
     3px rule was already doing the work of. */
  --card-sheen: linear-gradient(180deg, #ffffff 0%, #f6f6f4 46%, #ececea 100%);
  --card-rule:  #0f1214;

  /* --- the pixel field ---
   * The background canvas. Dark theme gets white stars on black; light theme
   * gets the same code with the ink inverted, so it reads as dust settling on
   * the board rather than a starfield printed on paper. */
  --star-ink:    #14181c;
  --star-alpha:  .34;
  --star-streak: #14181c;


  /* --- motion --- */
  --d-fast:  120ms;
  --d-mid:   220ms;
  --d-slow:  420ms;
  --stagger:  38ms;
  --ease: cubic-bezier(.2, .8, .3, 1);
  --ease-pop: cubic-bezier(.34, 1.42, .48, 1);

  /* --- type --- */
  /* Departure Mono is the face of the whole site now — every label, every
     paragraph. --font-ui survives for the one page that must not use a bitmap
     monospace: resume.html is what goes on job applications, gets printed, and
     gets parsed by applicant tracking systems, and this face makes all three
     materially worse.
     The scale below is integers ON PURPOSE and it matters more than it used to:
     Departure Mono is a bitmap face and smears at fractional sizes, so any
     clamp() on text has to be stepped at breakpoints instead. */
  --font-ui:  'M PLUS Rounded 1c', 'Hiragino Maru Gothic ProN', 'Varela Round',
              -apple-system, 'Segoe UI', system-ui, sans-serif;
  --font-pix: 'Departure Mono', ui-monospace, SFMono-Regular, Menlo, monospace;

  --fs-micro: 11px;
  --fs-small: 12px;
  --fs-body:  15px;
  --fs-lead:  17px;
  --fs-h3:    16px;
  --fs-h2:    20px;

  --dock-h: 100px;  /* MEASURED. The bar's real height is 99px now — the pixel
                       face has different label metrics and pushed it past the
                       96 it sat at for months. The grid subtracts this token to
                       derive its own height, so a token 3px under the truth
                       gives the wall 3px it does not have and the bottom row
                       clips at the tightest viewport first. */
  --hud-h:  64px;   /* MEASURED, not guessed: with the portrait and the role line
                       gone the header needs 60px, and the extra 4 is clearance
                       for the chip's hover ring. It has been 102, then 86, then
                       this — once per redesign of the chip, because the grid
                       subtracts this token to derive its own height and a token
                       that drifts from the real header clips the bottom row in
                       silence. That is the bug the fit gate exists to catch. */

  /* Everything in the stage that is NOT the grid: the pitch line, the level
     title, the closing CTA. Written by grid.js from a ResizeObserver rather
     than guessed at here, because the pitch wraps to two lines on a narrow
     window and a hardcoded value would under-count exactly when it matters and
     push the last row under the dock. 0 is the correct value before JS runs. */
  --stage-chrome: 0px;
}

/* ── DARK ─────────────────────────────────────────────────────────────────
 * A monochrome design has no natural second theme, so dark is the literal
 * inverse: paper becomes ink and ink becomes paper, including the three faces.
 * The two tiles that were black are now white and the one that was white is
 * black, so the polarity that tells them apart survives the flip. */
:root[data-theme='dark'] {
  --ground:      #0d0d0d;
  --ground-lift: #171717;
  --ground-edge: #000000;

  --grid-fine:   rgb(255 255 255 / .05);
  --grid-heavy:  rgb(255 255 255 / .12);
  --grid-node:   rgb(255 255 255 / .34);
  --tile:        #141414;
  --tile-2:      #1c1c1c;
  --tile-edge:   #f5f5f5;

  --ink:    #f5f5f5;
  --ink-2:  #c4c4c4;
  --ink-3:  #9a9a9a;
  --rule:   #2e2e2e;

  --sel:      #f5f5f5;
  --sel-deep: #f5f5f5;
  --sel-ink:  #0a0a0a;

  --row-active: linear-gradient(180deg, #202020 0%, #171717 100%);
  --badge-ring: #0a0a0a;

  --card-projects: #34383a;
  --card-builds:   #34383a;
  --card-resume:   #34383a;
  --card-projects-ink: #f2f3f4;
  --card-builds-ink:   #f2f3f4;
  --card-resume-ink:   #f2f3f4;
  --card-sheen: linear-gradient(180deg, #3d4144 0%, #34383a 46%, #2b2f31 100%);
  --card-rule:  #f2f3f4;

  /* white stars on black, at the opacity the ink version cannot use */
  --star-ink:    #ffffff;
  --star-alpha:  .82;
  --star-streak: #ffffff;

  --ok:   #f5f5f5;
  --warn: #c4c4c4;

  --focus-core: #ffffff;
  --focus-halo: #000000;

  --shadow-tile:  0 1px 1px rgb(0 0 0 / .40), 0 3px 8px rgb(0 0 0 / .45);
  --shadow-lift:  0 4px 10px rgb(0 0 0 / .45), 0 14px 30px rgb(0 0 0 / .55);
  --shadow-panel: 0 18px 64px rgb(0 0 0 / .65);
  --bevel: inset 0 1px 0 rgb(255 255 255 / .06);
  --scrim: rgb(0 0 0 / .72);
}

@media (prefers-contrast: more) {
  :root { --ink-2: #333; }
  :root[data-theme='dark'] { --ink-2: #dcdcdc; }
}

/* One source of truth for motion: zero the durations and every transition and
   animation that reads them stops. The JS stagger reads the same signal. */
@media (prefers-reduced-motion: reduce) {
  :root { --d-fast: 0ms; --d-mid: 0ms; --d-slow: 0ms; --stagger: 0ms; }
}
:root[data-motion='off'] {
  --d-fast: 0ms; --d-mid: 0ms; --d-slow: 0ms; --stagger: 0ms;
}
