/* tokens.css - the only file that assigns a colour, a size step, or a duration.
   No selector here other than :root, [data-page], [data-accent] and the reduced-motion
   block. That block also owns the view-transition pseudo-elements, because `*` and
   `*::before` do not match `::view-transition-old()` and friends. */

:root {
  /* surfaces */
  --bg:      #141414;
  --bg2:     #1b1b1b;
  --panel:   #202020;
  --panel2:  #252525;
  --stroke:  #2e2e2e;
  --stroke2: #3a3a3a;

  /* ink */
  --text:   #f2f2f2;
  --muted:  #b7b7b7;
  --muted2: #9a9a9a;   /* 5.0:1 on --panel; do not darken, small text uses it */

  /* accent - every page overrides --accent below. --on-accent is the ink that sits ON it. */
  --accent:    #00aaff;
  --on-accent: #041018;

  /* geometry */
  --radius:  14px;
  --radius2: 22px;
  --pill:    999px;
  --ui-border: 2px solid var(--stroke2);
  --ui-border-accent: 2px solid var(--accent);
  /* One 6px step off each corner. Small enough to read as a cut block rather than as a
     chamfer, and it is the only place the Minecraft silhouette is spelled out in geometry. */
  --notch: polygon(6px 0, 100% 0, 100% calc(100% - 6px), calc(100% - 6px) 100%, 0 100%, 0 6px);

  /* type roles.
     Jersey 25  - display. blocky, tight.
     Silkscreen - UI labels. 12px HARD FLOOR: below that its C closes into an O
                  ("CLIENTS" reads "OLIENTS"). Weight 700 is broken at every size. 400 only.
     VT323      - body. renders ~25% small, so its sizes look large in source. */
  --display: "Jersey 25", "Courier New", monospace;
  --ui:      "Silkscreen", "Courier New", monospace;
  --body:    "VT323", "Courier New", monospace;
  /* YouTube titles carry emoji and CJK that no pixel face has; this stack keeps the
     fallback deliberate instead of landing on Times. */
  --body-mixed: "VT323", "Segoe UI Symbol", "Apple Color Emoji", "Courier New", monospace;

  --fs-ui:    12px;   /* the floor. */
  --fs-ui-lg: 13px;
  --fs-body:  19px;
  --fs-body-sm: 17px;

  --track-ui: 1px;

  /* rhythm */
  --gap:  18px;
  --pad:  16px;

  /* ---- motion ladder ----
     Five bands and one ease, so nothing on the site has to invent a duration. A component
     picks the band that matches what it is saying, never a number:

       tap     press, copy confirm, anything that must feel like contact
       fast    border colour, chip fill, the burger bars
       ui      thumbnail scale, the dropdown, the lightbox entrance
       enter   a section arriving as you reach it
       feature the hero sequence, the stat count-up and the heatmap sweep

     The bands were roughly doubled on 2026-08-24: at 520ms an entrance was over before it
     had been noticed, and the heatmap sweep in particular finished inside 450ms, which read
     as the board simply appearing. Feedback bands (tap, fast) grew far less than arrival
     bands (enter, feature), because a hover that takes a second is not smooth, it is late.

     --dur and --dur-slow are the names the existing components already reference; they are
     aliases now rather than separate values, so every transition lands on a band. */
  --dur-tap:     120ms;
  --dur-fast:    280ms;
  --dur-ui:      460ms;
  --dur-enter:  1000ms;
  --dur-feature: 1500ms;
  --dur:      var(--dur-fast);
  --dur-slow: var(--dur-ui);

  /* One primary ease for everything that arrives: fast out of the gate, long settle. The
     rail is the single exception and uses a spring, in js/motion.js. */
  --ease-out: cubic-bezier(.22, 1, .36, 1);
  --ease: var(--ease-out);

  /* The overshoot. Anything that ARRIVES at a resting position travels a little past it and
     comes back; the peak is about 1.14, so a 20px entrance overshoots by under 3px. It is
     for entrances only. A hover, a border colour or a press has no resting position to
     overshoot, and giving one an overshoot reads as a glitch rather than as weight. */
  --ease-back: cubic-bezier(.34, 1.56, .64, 1);

  /* 110ms between siblings, and js/motion.js never staggers more than six of them. */
  --stagger: 110ms;

  /* The heatmap sweep, which is the longest single piece of choreography on the site: three
     year rows lighting January to December, about 1.9s end to end. Two steps rather than one
     because the board is read as rows of months, so a month has to follow its neighbour
     faster than a year follows the year above it or the sweep reads as noise. */
  --heat-row-step:   200ms;
  --heat-month-step:  45ms;

  /* The rotating word in the closing call to action, and nothing else on the site.
     3.4s is long enough to finish reading the sentence before it changes and short enough
     that a visitor who is looking at it sees it happen. 24ms between characters reads as
     one word turning over rather than as five letters doing separate things. */
  --dur-rotate: 3400ms;
  /* The first turn after the block scrolls into view comes sooner than the rest. At a flat
     3.4s the component reads as a static highlight to anyone who glances at the closing
     line and moves on, which is most people: the first turn is what says it is alive. */
  --dur-rotate-first: 1200ms;
  --rotate-stagger: 45ms;
  /* Enough vertical room that the block reads as a placed slab rather than as a tight
     highlighter stroke, and enough for a character to be fully inside it before it moves. */
  --slab-pad: .1em .3em;
}

/* Two pages, two accents, both bright enough to read as text and both taking dark ink.
   The timeline purple was #894bdd with light ink: 3.61:1 against the page, under the
   4.5:1 that 12px needs, and it is used as text on the eyebrows, the month headings, the
   feedbar year and the footer links. #a878f5 measures 5.85:1 there, 5.18:1 on a panel,
   and 6.11:1 for the dark ink on top. Every heatmap level clears AA as well: 9.12, 5.33,
   5.24.
   The work orange was #f0593a. Brightened to #ff7a45 on request: the dark ink on the
   fill goes 5.67:1 -> 7.43:1 and the fill on the page goes 5.43:1 -> 7.12:1. White ink
   would have looked brighter and measured 3.39:1, under the 4.5:1 that 12px needs. */
/* legal.html takes the work orange because it is reached from the work page and has no
   identity of its own to express. It gets its own page name rather than borrowing
   data-page="work", so the topbar does not mark WORK as the page you are currently on
   while you are reading the mentions legales. */
[data-page="work"],
[data-page="legal"],
[data-page="404"]      { --accent: #ff7a45; }
[data-page="timeline"] { --accent: #a878f5; --on-accent: #041018; }

/* per-destination accents, so a nav link carries the colour of where it goes */
[data-accent="work"]     { --accent: #ff7a45; --on-accent: #041018; }
[data-accent="timeline"] { --accent: #a878f5; --on-accent: #041018; }

/* Not shortened, skipped. Someone who asks for no motion gets the finished page on the
   first frame, not a faster version of the same movement. js/motion.js reads the same
   query and returns early rather than running a 0s animation. */
@media (prefers-reduced-motion: reduce) {
  :root {
    --dur-tap: 0s;
    --dur-fast: 0s;
    --dur-ui: 0s;
    --dur-enter: 0s;
    --dur-feature: 0s;
    --stagger: 0s;
    --heat-row-step: 0s;
    --heat-month-step: 0s;
  }
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
  /* The cross-document transition is not matched by the universal selector above: its
     pseudo-elements live outside the document tree. */
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}
