/* ============================================================================
   TeamKeys — design tokens
   Owner: ATELIER. This file is the only home for graphic values. Nobody
   hardcodes a colour, a size, a duration or a radius anywhere else. If a token
   is missing, ask for it — do not invent it in a component.

   SOURCES (every value below is traceable to one of these)
     [b:NN]  app/_baseline/TeamKeys.dc.html, line NN — the committente's
             approved app design.
     [m]     the committente's player-report mockup, image1.png inside
             TEAMKEYS_Questionario.docx, sha256:36a63764bb7181fb — registered
             in warroom/FONTI-ASSET.md.
     [d]     derived by ATELIER. Every [d] carries its reason inline.

   SHAPE CONTRACT (do not break — the app ships as one .dc.html and this file
   may be pasted verbatim inside <helmet><style>):
     one :root{} block · no selectors · no @import · no @media · comments only.

   COLOUR SPACE. Values are written as hex so they render on every engine.
   The authoritative definition of each derived colour is the oklch() triple in
   its comment: that is what app/design/tokens.test.html asserts against.

   THREE COLOUR NAMESPACES, AND THE RULE THAT KEEPS THEM APART
     identity  glyph + preview     which of the seven exercises this is [D-042]
     family    --tk-domain-*       which half of the profile an axis belongs to [m]
     state     --tk-state-*        what just happened, inside an exercise
   Colour NEVER encodes level — not here, not anywhere. The committente's own
   report proves the rule: its radar has no red/amber/green bands, and its two
   colours mark the cognitive/mental families, not how good the score is.
   Family colours never appear inside an exercise; state colours never appear
   on the ME screen. Keeping them in separate views is what stops green from
   meaning "mental domain" and "you got it right" in the same glance.
   ============================================================================ */

:root{

  /* --- 1 · SURFACES ------------------------------------------------------ */
  /* Six distinct darks in the baseline. They are not interchangeable: the
     frame is a vertical gradient and the carousel edge masks are a seventh
     value that exists only to fade cards out sideways.

     D-042 · VIOLET. The directive asked for a violet-and-black world. The
     measurement that shaped the answer: the six darks were ALREADY on the
     violet axis, at hue 285.8–286.1 — what they lacked was chroma, 0.002 to
     0.006, which is below the threshold where an eye calls a near-black
     anything but black. So this is not a hue change, it is a saturation
     change: hue moves 286 → 300 (blue-violet to violet proper) and chroma
     goes to .030, which measures ΔEok .028 from the old neutral — comfortably
     past the ~.02 where two near-blacks stop being the same near-black.
     Lightness is UNTOUCHED, every value, on purpose (see below).

     WHY LIGHTNESS DOES NOT MOVE. Every contrast figure in this file is
     measured against --tk-surface-frame-top. Holding L fixed keeps those
     figures within a hundredth, so a whole-palette reskin does not become a
     whole-palette re-derivation. Raising chroma at constant L LOWERS relative
     luminance (Y .007070 → .006798 on frame-top), so for every OPAQUE colour
     the ratio can only improve. That rule does NOT extend to the alpha inks
     in §2–3: their composite darkens with the surface, so those move by a
     hundredth in either direction. Measured, not assumed — the figures below
     are recomputed, and three of them went DOWN. */
  --tk-surface-void:        #0c0715;              /* page behind the phone     [b:15,20 + D-042] */
  --tk-surface-void-glow:   #1e172a;              /* top of the page radial    [b:20    + D-042] */
  --tk-surface-frame-top:   #161120;              /* frame gradient 0%         [b:21    + D-042] */
  --tk-surface-frame-mid:   #0d0816;              /* frame gradient 45%        [b:21    + D-042] */
  --tk-surface-frame-base:  #0a0613;              /* frame gradient 100%       [b:21    + D-042] */
  --tk-surface-mask:        #0e0917;              /* carousel edge masks       [b:45,46 + D-042] */
  --tk-surface-nav:         rgba(12,7,21,.7);     /* nav bar, over blur        [b:64    + D-042] */
  --tk-surface-nav-opaque:  #0f0a18;              /* the same nav with NO blur available: the
                                                     composite over the lightest thing that can
                                                     scroll under it. C-5v found the nav had been
                                                     translucent-with-no-fallback since the
                                                     baseline — it was the first thing that
                                                     control caught, before the glass it was
                                                     written for even shipped.  [d, D-044] */
  --tk-blur-nav:            12px;                 /* nav backdrop-filter       [b:64]    */

  /* Kept on one line each: these are pasted verbatim, and a wrapped custom
     property value survives CSS but reads badly in a diff. */
  --tk-surface-frame: linear-gradient(180deg, var(--tk-surface-frame-top) 0%, var(--tk-surface-frame-mid) 45%, var(--tk-surface-frame-base) 100%);   /* [b:21] */
  --tk-surface-void-field: radial-gradient(120% 80% at 50% 0%, var(--tk-surface-void-glow) 0%, var(--tk-surface-void) 60%);                          /* [b:20] */

  /* Game engines mount on a flat surface, not on the frame gradient: a
     stimulus must not change apparent brightness depending on where it is
     drawn. Value is the frame's own mid stop, so nothing new enters. [d] */
  --tk-surface-game:        var(--tk-surface-frame-mid);

  /* Every contrast figure in this file is measured against
     --tk-surface-frame-top, the lightest in-app surface = the worst case. */

  /* --- 2 · INK ----------------------------------------------------------- */
  /* Contrast ratios are WCAG 2.1, computed after alpha compositing.
     4.50:1 is the body-text floor (SC 1.4.3); 3.00:1 the non-text floor
     (SC 1.4.11). Anything below 3.00:1 is decoration and is labelled so. */
  --tk-ink-primary:    #f2f2f2;                   /* 16.53:1                   [b:21,184,189] */
  --tk-ink-strong:     rgba(255,255,255,.85);     /* 13.44:1                   [b:25]        */
  --tk-ink-secondary:  rgba(255,255,255,.55);     /*  6.17:1                   [b:112]       */
  --tk-ink-faint:      rgba(255,255,255,.32);     /*  2.88:1 — NOT for text    [b:189]       */
  /* The baseline also uses rgba(255,255,255,.5) [b:59] for secondary text.
     Merged into --tk-ink-secondary: same role, and .55 buys +0.87:1 of
     contrast at no visual cost (6.17:1 against 5.30:1). [d] */

  /* --- 3 · LINES AND DECORATIVE FILLS ------------------------------------ */
  --tk-line-hairline:  rgba(255,255,255,.07);     /* frame edge, rings, nav top [b:21,42,64] */
  --tk-line-border:    rgba(255,255,255,.14);     /* card outline               [b:106]      */
  --tk-fill-dim:       rgba(255,255,255,.16);     /* inactive dot, inset lift   [b:105,115,184] */
  /* THE TRACK CARRIES THE DENOMINATOR (甜筒, 2026-08-08). --tk-fill-dim was
     doing duty as the progress bar's track and measures 1.51–1.60:1 across the
     run frame: before the first minute fills, the segments are a ghost. The
     fill clears at 3.01:1 and was said to carry the reading — it does not
     carry all of it. A progress bar communicates a FRACTION: the fill shows
     that something grew, the track shows of how much, and in a timed run how
     much is LEFT is the part that matters. So the track is content, not
     decoration, and it gets the 3:1 floor and its own token rather than
     borrowing a decorative one. .40 is the first step that clears on the
     lightest frame stop, which is the worst case. */
  --tk-track-dim:      rgba(255,255,255,.40);     /* 3.82:1 — progress track    [d, 甜筒] */
  --tk-fill-mid:       rgba(255,255,255,.42);     /* emphasised bar             [b:115]      */
  --tk-fill-selected:  rgba(242,242,242,.22);     /* selected nav glyph         [b:191]      */

  /* --- 4 · ACCENT -------------------------------------------------------- */
  /* One accent, three intensities. It marks focus and direction, never state.

     D-042 · the accent takes the world's own hue, 300, at the lightness it
     already had (.723). One violet family top to bottom, which is what
     "overall violet tonality" asks for, and the accent then reads as the
     world lit up rather than as a foreign colour laid on it.

     THE ADJACENCY I FLAGGED AS A RISK WAS ALREADY HERE, AND POINTED THE OTHER
     WAY. I warned that a violet accent might crowd --tk-domain-cognitive.
     Measured: the OLD blue accent sat 4.3° from it (265.4 vs 261.1), ΔEok
     .135 — the two were nearly the same colour, and had been all along. At
     hue 300 the separation is 38.9°, ΔEok .153. The reskin does not create
     that collision, it removes one that shipped. Under protanopia the move is
     a small loss (.140 → .103); neither number governs anything, because the
     accent and a domain family never have to be told apart to read a screen —
     they carry direction and family, not a pair. Stated because it is the one
     figure that moves the wrong way.

     Hue 300 is also game 7's hue. Not a collision: the accent sits at L .723
     against a card face at L .50, ΔEok .231. Lightness separates them, as it
     separates everything else in this system. */
  --tk-accent:         #9e4bff;                   /* oklch(.610 .251 300) — 4.32:1 [b:23,43 + D-042] */
  --tk-accent-glow:    rgba(158,75,255,.10);     /* screen-top halo            [b:23] */
  --tk-accent-line:    rgba(158,75,255,.35);     /* horizon rule               [b:43] */

  /* --- 5 · TYPOGRAPHY ---------------------------------------------------- */
  --tk-font-sans:      Barlow, system-ui, sans-serif;               /* [b:13,20] */
  --tk-font-mono:      "JetBrains Mono", ui-monospace, monospace;   /* [b:13,25] */

  --tk-weight-regular:  400;                                        /* [b:13] */
  --tk-weight-medium:   500;                                        /* [b:13] */
  --tk-weight-semibold: 600;                                        /* [b:13] */
  --tk-weight-bold:     700;                                        /* [b:13] */

  --tk-text-display:   26px;    /* screen title                      [b:38]  */
  --tk-text-title:     21px;    /* active card name                  [b:57]  */
  --tk-text-heading:   19px;    /* card name                         [b:113] */
  --tk-text-item:      17px;    /* questionnaire item — see note      [d]    */
  --tk-text-body:      13.5px;  /* supporting copy                   [b:59]  */
  --tk-text-mono:      13px;    /* numerals, timers                  [b:25]  */
  --tk-text-micro:     9.5px;   /* labels — see warning              [b:68,112] */
  /* --tk-text-item [d]: the baseline's smallest text is also its densest
     screen's only candidate. Eighteen questionnaire items at 13.5px would
     make the longest read in the app the smallest type in it, which is
     backwards. 17px sits inside the baseline's own 19 → 13.5 gap; nothing
     new is introduced above or below the existing range.
     --tk-text-micro WARNING: at --tk-ink-faint it measures 2.89:1 and fails
     SC 1.4.3. Use it only at --tk-ink-secondary or stronger. */

  --tk-leading-tight:  1.15;                                        /* [b:38,113] */
  --tk-leading-snug:   1.2;                                         /* [b:57]     */
  --tk-leading-item:   1.4;   /* between snug and body, for wrapped items [d] */
  --tk-leading-body:   1.55;                                        /* [b:59]     */

  --tk-tracking-tight: -.01em;                                      /* [b:38,113] */
  --tk-tracking-wide:  .1em;                                        /* [b:68]     */
  --tk-tracking-wider: .14em;                                       /* [b:112]    */

  /* --- 6 · SPACE --------------------------------------------------------- */
  --tk-space-1:  4px;                                               /* [b:114]        */
  --tk-space-2:  6px;                                               /* [b:27,33,38]   */
  --tk-space-3:  12px;                                              /* [b:66]         */
  --tk-space-4:  18px;                                              /* [b:37,108]     */
  --tk-space-5:  20px;                                              /* [b:55]         */
  --tk-space-6:  26px;                                              /* [b:25,33,37,55]*/
  --tk-gutter:   var(--tk-space-6);  /* the screen margin, four occurrences in
                                        the baseline — the strongest rhythm it has */

  /* --- 7 · RADIUS -------------------------------------------------------- */
  --tk-radius-xs:     2px;                                          /* [b:28,51]      */
  --tk-radius-sm:     3px;                                          /* [b:29]         */
  --tk-radius-md:     6px;                                          /* [b:67]         */
  --tk-radius-lg:     22px;                                         /* [b:103,105,106]*/
  --tk-radius-frame:  44px;                                         /* [b:21]         */
  --tk-radius-round:  50%;                                          /* [b:67,108]     */
  --tk-radius-pill:   999px;   /* for the tap targets §10 asks for        [d] */

  /* --- 8 · ELEVATION ----------------------------------------------------- */
  --tk-shadow-frame:     0 40px 90px rgba(0,0,0,.65);               /* [b:21]  */
  --tk-shadow-hairline:  0 0 0 1px var(--tk-line-hairline);         /* [b:21]  */
  --tk-shadow-card:      0 24px 50px rgba(0,0,0,.6);                /* [b:105] */
  --tk-shadow-inset-top: inset 0 1px 0 var(--tk-fill-dim);          /* [b:105] */

  /* --- 9 · MOTION -------------------------------------------------------- */
  --tk-duration-swap:    .3s;      /* the only transition in the baseline [b:51] */
  --tk-duration-feedback: .18s;    /* state change inside an exercise      [d]   */
  --tk-pulse-min:        .35;                                       /* [b:17]  */
  --tk-pulse-max:        .9;                                        /* [b:17]  */
  /* Carousel geometry — graphic values, so they live here and not in the shell. */
  --tk-carousel-radius:  460px;                                     /* [b:125] */
  --tk-carousel-arc:     26deg;                                     /* [b:125] */
  --tk-carousel-blur-max: 9px;                                      /* [b:132] */
  --tk-carousel-lerp:    .14;      /* per-frame approach factor          [b:144] */
  --tk-perspective:      1000px;                                    /* [b:44]  */
  /* No prefers-reduced-motion here: this file may carry no @media. Consumers
     must honour it themselves — the carousel's blur and parallax are the
     reason it matters. Covered in the readability note. [d] */

  /* --- 10 · TOUCH TARGETS ------------------------------------------------ */
  /* E-005 · THESE ARE FRAME COORDINATES, AND THE FRAME IS SCALED. app.js fits
     the 390x844 frame into the real viewport with a CSS transform, and a
     transform scales the hit area along with the pixels — so 44 frame px are
     never 44 px on glass: 42.2 at 390x844 (the nominal frame), 38.3 at
     390x750 (a typical Android viewport), 28.8 at 320x568 (the D-039 floor).
     Reaching 44 RENDERED would take an 860px-tall viewport, which no phone
     browser leaves once the address bar is out. Every figure carries its
     viewport because C-5u recomputes each one from the shell's own fit.

     So what is claimed is SC 2.5.8 (AA, 24px), not SC 2.5.5 (AAA, 44px): at
     the D-039 floor the target renders at 28.8px, 4.8px of margin. AAA on
     glass is not a patch, it is a frame that REFLOWS instead of scaling —
     registered as a lever, committente item 12.

     This note lived in screens/vault-index.html until D-042 deleted that
     screen with the index row it described. It belongs to the token, not to
     any one screen that happens to use it — which is where it should have
     been written the first time. */
  --tk-target-min:  44px;
  /* [d] WCAG 2.1 SC 2.5.5 and the Apple HIG both put the floor at 44 CSS px.
     The baseline is below it in two places: the nav column is 20px wide
     [b:66,67] and the carousel dots are 22x3px [b:51]. Both are defects of
     the approved design, not of this file; the fix is a transparent hit area,
     which changes nothing visually. Raised to the CTO in the readability
     note, not fixed unilaterally. */

  /* --- 11 · GAME IDENTITY ------------------------------------------------ */
  /* The baseline gives each card the same lightness and chroma and varies only
     the hue [b:105]. That makes seven cards weigh the same, which is good, and
     it makes them indistinguishable to a dichromat, which is not: at identical
     L and C, hue 148 and hue 12 sit 1.02:1 apart after deuteranopia simulation.
     Hue is therefore identity's SECOND channel, never its only one.
     The FIRST channel (decided 2026-08-05): a MONOLINE GLYPH per game plus its
     index numeral "01"–"07", both drawn in ink, never in the game's hue, so
     they survive any colour vision unchanged. Canonical drawings:
     app/design/glyphs.svg. Demonstrated in app/design/card-anatomy.html,
     including a grayscale row that proves the seven cards stay tellable with
     hue removed entirely. Sizing tokens in §15. */
  /* D-042 REVISED · THE SEVEN HUES ARE RETIRED AS THE CARD FACE. The user's
     decision, and the measurements agree with it — which is worth recording,
     because I would have argued for keeping them and the numbers say I would
     have been defending very little.

     WHAT THE SEVEN HUES WERE ACTUALLY WORTH, measured at the face values that
     shipped (L .50, C .080), as ΔEok between the closest pair of the seven:
       normal vision        .028   (games 4 and 6, hues 32 and 12)
       after deuteranopia   .009   (games 1 and 7, hues 222 and 300)
     A just-noticeable difference is around .02. So the closest pair was ALREADY
     at the edge of tellable for a trichromat, and already past it for everyone
     else — and the pair that collapsed was not the one D-008 documented. Seven
     hues were never seven identities. Nothing that worked is being given up.

     The face is now one recipe for all seven: a semi-transparent grey-black
     over the frame, lifted by a violet glow rising from the bottom edge. The
     glow is DECORATION and must never carry state (D-008): it is identical on
     every card, so it cannot encode anything by construction — which is the
     strongest form of that guarantee, stronger than a rule anyone must follow.

     THE CONSTRAINT THAT GOVERNS, and it is not the one I was given. The brief
     named the index numeral at 4.50:1. The numeral is ink on a dark face and
     clears by nearly 3x — 12.18:1 at the brightest point of the glow. What
     actually binds is --tk-ink-secondary, the supporting text, which at glow
     .30 sits at 5.17:1 and would cross the 4.50 floor at glow ≈ .42. The
     ceiling on the glow is set by the card's quiet text, not by its numeral.

     Identity now rests entirely on the channels measured to carry it: the large
     monoline glyph and the animated preview. Both are ink, so both survive any
     colour vision unchanged — which is what the hue never did. */
  /* r10fx · THE RICHNESS BUDGET OF THE CARD, AND WHO IS HOLDING IT.
     The product asks for two things at once: a per-game backdrop BEHIND the
     cards, and a card face of purple metallised glass. Both spend the same
     budget, because the face is TRANSLUCENT: --tk-card-fill is .72 alpha, so
     whatever is behind a card composites into the card's own ground, and a
     "metallised" sheen is a second translucent layer on top of that. Eight
     backdrops mean eight different figures for the same printed text.
     Measured, all compositing on non-linear sRGB, floor 4.50:1:

       binding ink                          brightest backdrop   strongest sheen
       --tk-ink-secondary (.55)  as today   #666 (L .133)        alpha .08
       --tk-ink-strong    (.85)             #fff (L 1.000)       alpha .29
       nothing quiet printed there          #fff (L 1.000)       alpha .32

     And spending BOTH at once, which is what was actually asked: at .55 the
     pair (backdrop #2a2a2a, sheen .08) ALREADY FAILS at 4.32:1 — meaning the
     mandate is arithmetically impossible while that ink stands. At .85 the
     worst corner of the same table is 5.19:1, with room to spare.

     WHERE THAT INK ACTUALLY LIVES, and it is the whole point: on the card,
     --tk-ink-secondary is printed exactly once, on the DENOMINATOR of the index
     numeral — the shell writes `03 <small>/ 08</small>` and only the <small>
     wears it. So the entire richness of every card is held down by a
     two-character label at 55% white, sitting inside a numeral whose own
     numerator is already at 85%. §11 said the ceiling was set by the card's
     quiet text rather than by its numeral; this is that sentence with a price
     attached.
     RECOMMENDED, not decided: the denominator joins its numerator at
     --tk-ink-strong. It costs one whisper of hierarchy on two characters and it
     is the difference between the mandate being impossible and being
     comfortable. The use site is the shell's, so the change is theirs to make
     and the CTO's to route — the numbers are what this file owes them. */
  /* r10fx · IL VETRO VIOLA METALLIZZATO. Il riempimento era rgba(14,11,19,.72),
     cioe' quasi-nero: e' letteralmente il motivo per cui il prodotto la chiama
     grigia. Ora porta chroma vera. Ma il numero che conta non e' la tinta, e'
     l'ALFA, e la ragione e' §11 in una forma che non mi aspettavo.

     LA FACCIA E' TRASLUCIDA, QUINDI GLI OTTO FONDALI LA ATTRAVERSANO. Misurata
     la variazione della faccia fra il primo e l'ottavo fondale della rampa
     ratificata, in oklab, anche dopo simulazione dicromatica:
       alfa .72 (quella di prima)  la faccia varia 0.180  = NOVE volte la JND
       alfa .90 (questa)           la faccia varia 0.063  = un decimo di quanto
                                   varia il mondo dietro (0.606)
       alfa .97                    varia 0.018, sotto la JND: non e' piu' vetro,
                                   e' vernice
     A .72 ogni card sarebbe di un colore diverso a seconda del gioco — cioe'
     D-042 che rientra dalla finestra con un altro nome, dopo essere stato
     ritirato MISURANDO che sette tinte non erano sette identita'. A .90 la card
     resta UNA MATERIA e il mondo cambia dietro di lei, che e' la lettura
     ratificata del brief: card costante, fondale variabile.
     LA TENSIONE, detta perche' e' reale: il vetro vuole trasparenza, la
     costanza vuole opacita'. Si risolve ricordando che la trasparenza non e'
     l'unico segnale di vetro — lo SPECULARE lo e', ed e' precisamente cio' che
     «metallizzato» vuol dire. La faccia sta opaca e si legge come vetro per
     come RIFLETTE, non per cio' che lascia passare. */
  --tk-card-fill:      rgba(36,16,64,.90);   /* #241040 @.90 — chroma vera, e l'alfa che tiene la card una materia sola [r10fx] */
  --tk-card-glow-from: rgba(158,75,255,.30); /* bottom edge → #3b1f5c      [D-042] */
  --tk-card-glow-to:   rgba(158,75,255,0);   /* same hue at zero alpha, so the
                                                ramp cannot fringe through grey */
  --tk-card-face: linear-gradient(0deg, var(--tk-card-glow-from) 0%, var(--tk-card-glow-to) 62%), var(--tk-card-fill);   /* [D-042] */
  /* r10fx · LO SPECULARE, cioe' la parte che fa «metallizzato». E' una banda
     bianca obliqua: un terzo strato traslucido, quindi spende dallo stesso
     budget del fondale e va misurata dove il testo e' piu' in difficolta'.
     E QUI HO CORRETTO UN MIO NUMERO. Il primo conto diceva .27, misurando la
     faccia SENZA il glow. Ma il glow e' un sollevamento viola dal bordo BASSO
     che svanisce al 62%, e il NOME della card sta a 44px dal fondo, cioe'
     DENTRO il glow: e' lui il punto che decide, e non stava nel calcolo. E'
     la stessa classe del budget rimasto stale quattro giri (C-5ad) — una cifra
     giusta per un pezzo, citata per un altro.
     Rifatto in DUE punti, sui due estremi della rampa dei fondali, pavimento
     il pavimento del testo (il denominatore e' salito a --tk-ink-strong,
     leva D-094):
       sheen .24   alto 6.41 / 5.40   basso, nel glow, 5.31 / 4.65   -> tiene
       sheen .27   alto 5.82 / 4.93   basso, nel glow, 4.85 / 4.28   -> il nome
                                                                        cede
     Il tetto e' quindi .24, ed e' il NOME a fissarlo, non il numerale — la
     stessa forma della frase che §11 aveva gia' scritto per il glow. */
  --tk-card-sheen-from: rgba(255,255,255,.24);   /* il picco dello speculare: oltre, il nome nel glow scende sotto il pavimento del testo [r10fx] */
  --tk-card-sheen-to:   rgba(255,255,255,0);     /* stesso bianco ad alfa zero, cosi' la rampa non frangia [r10fx] */
  --tk-card-sheen: linear-gradient(105deg, var(--tk-card-sheen-from) 0%, var(--tk-card-sheen-to) 58%);   /* [r10fx] */
  --tk-game-gradient-angle: 160deg;                                 /* [b:105] */

  /* r10fx · I FONDALI PER-GIOCO — e prima di tutto la loro PORTATA, che e' una
     decisione e non un dettaglio di montaggio.
     IL FONDALE STA DIETRO LE CARD, NON AL POSTO DEL CAMPO. Misurato prima di
     disegnare: se questa rampa prendesse tutto lo schermo, SEI inchiostri del
     vault su sei morirebbero, e non di poco — il nome del gioco passa da 11.26
     a 1.02, la tagline da 5.08 a 1.16, i bordi dei controlli da 3.31 a 1.10.
     Il vault e' un mondo CHIARO con inchiostri scuri; la rampa e' scura. Sono
     due mondi, e sovrapporli e' la stessa forma del difetto che tre giri di
     C-5ag hanno inseguito — una cifra misurata su un terreno e usata su un
     altro — solo che qui arriverebbe per scelta invece che per distrazione.
     Il verbatim diceva gia' «sfondo DIETRO LE CARD»: la misura e il brief
     dicono la stessa cosa. Il fondale vive nella regione del palco; caption,
     nav e titolo restano sul campo e tengono tutte le cifre gia' misurate.
     Il bordo fra le due regioni e' un salto, e il «blur 3D lieve» chiesto dal
     prodotto e' esattamente cio' che lo dissolve: il fondale sfuma a
     trasparente ai suoi margini invece di tagliare il campo.

     LA RAMPA. Otto tinte non sono otto identita' se stanno tutte alla stessa
     lightness: e' la lezione che D-042 ha gia' pagato per le sette facce, e
     l'ho rimisurata su questa proposta prima di disegnarla. Simulazioni
     Machado 2009 e Vienot 1999, protanopia + deuteranopia + tritanopia,
     coppia piu' debole del set, JND circa 0.020:
       otto tinte a lightness piatta              0.0058  — sotto la JND di
                                                  QUATTRO volte: per un
                                                  dicromatico e' una stanza
                                                  otto volte
       la lightness porta, la tinta decora        0.0209  — passa per un 4%
       questa: stessa rampa, tinte tutte fuori
       dall'asse rosso-verde                      0.0848  — 4.2 volte la JND
     E la coppia piu' debole SI SPOSTA fra i modelli invece di essere sempre lo
     stesso collasso: e' il segno che la separazione e' strutturale e non
     fortunata. La rampa arriva fino a L .795 perche' la leva D-094 ha alzato
     il tetto; sotto il tetto vecchio si fermava a .500 e il set scendeva a
     0.0427 — sicuro anche li', ma in una banda quasi nera.
     L'ordine e' la rampa, non un elenco di giochi: la mappa indice-gioco vive
     nel registro dello shell, come viveva quella delle sette tinte. */
  --tk-stage-backdrop-1: #1d0736;   /* oklch(.200 .085 300) [r10fx] */
  --tk-stage-backdrop-2: #292151;   /* oklch(.285 .085 288) [r10fx] */
  --tk-stage-backdrop-3: #343a6c;   /* oklch(.370 .085 276) [r10fx] */
  --tk-stage-backdrop-4: #3e5586;   /* oklch(.455 .085 264) [r10fx] */
  --tk-stage-backdrop-5: #49719f;   /* oklch(.540 .085 252) [r10fx] */
  --tk-stage-backdrop-6: #558fb6;   /* oklch(.625 .085 240) [r10fx] */
  --tk-stage-backdrop-7: #65accb;   /* oklch(.710 .085 228) [r10fx] */
  --tk-stage-backdrop-8: #79cadf;   /* oklch(.795 .085 216) [r10fx] */
  /* r10fx · LE TINTE-SCENA DELLE COPERTINE (D-114: geometria = identita',
     colore = scena). Prima ho provato la strada economica — la scena di una
     copertina e' il FONDALE del suo gioco, zero token nuovi, e la separabilita'
     gia' ratificata ereditata in blocco. Misurata: UN gradino su otto si vede
     sulla faccia. Il motivo e' strutturale e non sfortuna — la rampa parte
     dentro la famiglia di colore della faccia, ed e' lo stesso muro che rende
     --tk-accent invisibile li' (1.57:1). Idea morta alla misura, prima del
     disegno.
     La faccia e' scura, quindi cio' che si vede sopra e' CHIARO: misurato
     l'inviluppo, ogni tinta e' utilizzabile da L .75 in su, con chroma da .116
     a .200 — pastelli veri, non grigi slavati.
     SONO QUATTRO E NON DIECI di proposito: sotto D-114 il colore NON distingue
     una copertina dall'altra, quindi non gli serve un'identita' per gioco. Dieci
     tinte sarebbero dieci cose da tenere allineate per un lavoro che nessuna di
     loro deve fare. Si riusano.
     Ognuna e' misurata sul caso peggiore della faccia (sotto la banda speculare,
     sul fondale piu' chiaro) contro il pavimento delle grafiche non testuali. */
  /* IL CASO PEGGIORE DELLA FACCIA, COME TOKEN — perche' una cifra ha bisogno di
     un fondo NOMINABILE, e il fondo di una copertina e' un composito (il
     riempimento sul fondale, piu' la banda speculare sopra), non un token.
     Senza questo, ogni figura disegnata sulla card resterebbe una cifra che
     nessuna guardia puo' ricontrollare. E' lo stesso mestiere che
     --tk-glass-surface fa gia' per il vetro: «worst-case composite = the
     fallback fill». Qui e' il riempimento sul fondale piu' CHIARO della rampa,
     sotto la banda: dove una figura chiara ha meno aria di ovunque. */
  --tk-card-face-worst: #5f577a;   /* [d] il composito peggiore su cui una copertina puo' finire [r10fx] */
  --tk-cover-scene-warm:  #ff9c40;   /* oklch(.780 .156 60)  — 3.22:1 on --tk-card-face-worst [r10fx] */
  --tk-cover-scene-fresh: #5ecb43;   /* oklch(.750 .200 140) — 3.23:1 on --tk-card-face-worst [r10fx] */
  --tk-cover-scene-cool:  #0dc3eb;   /* oklch(.755 .136 220) — 3.20:1 on --tk-card-face-worst [r10fx] */
  --tk-cover-scene-soft:  #c4a4fd;   /* oklch(.780 .128 300) — 3.22:1 on --tk-card-face-worst [r10fx] */
  --tk-stage-backdrop-blur: 22px;   /* [d] il «blur 3D lieve»: abbastanza da togliere ogni bordo al fondale, non tanto da spegnerlo [r10fx] */
  --tk-stage-backdrop-fade: 18%;    /* [d] quanto della sua altezza il fondale spende a dissolversi nel campo, sopra e sotto [r10fx] */
  /* The seven per-game hue tokens are GONE with the faces they coloured.
     app/shell/games.js still carries a hueIndex per game: that field now has no
     consumer in this file, and removing it is 毛线's call on their own file,
     not a silent edit from here. Flagged, not touched. */

  /* --- 12 · DOMAIN FAMILIES --------------------------------------------- */
  /* From the committente's report mockup [m], which colours the eight radar
     axes by family: navy #1f3864 = oklch(.345 .083 260.9) for the four
     cognitive domains, sage #5f7161 = oklch(.529 .033 148.2) for the four
     mental ones. Those literals are for white paper and die on this app:
     the navy measures 1.58:1 on --tk-surface-frame-top. Hue is preserved
     exactly; lightness is moved until both are legible on dark, and their
     ordering (navy darker than sage) is preserved too. */
  --tk-domain-cognitive: #6180b6;   /* oklch(.60 .09 260.9) — 4.64:1 [m + d] */
  --tk-domain-mental:    #96b499;   /* oklch(.74 .05 148.2) — 8.18:1 [m + d] */
  /* These two differ in hue AND in lightness (ΔL .14) on purpose: at equal
     lightness their separation after deuteranopia simulation is ΔEok .108;
     the split raises it to .189. Colour is still not the only channel — the
     mockup already places the four cognitive axes on one half of the radar
     and the four mental on the other, and every axis carries its own label. */

  /* --- 13 · CHART CHROME ------------------------------------------------- */
  --tk-chart-grid:   var(--tk-line-hairline);  /* rings — the baseline's own
                                                  dashed circle value [b:42] */
  --tk-chart-axis:   var(--tk-line-border);                        /* [b:106] */
  --tk-chart-track:  var(--tk-fill-dim);       /* unfilled gauge arc [m + b:184] */
  --tk-chart-plot-ink:    var(--tk-ink-primary);
  --tk-chart-plot-fill:   rgba(242,242,242,.10);

  /* Weights, measured off the committente's mockup [m] and scaled. In that
     image the plot circle is 473px across and carries: rings 2px, the data
     polygon 5px, a vertex dot 11px. As ratios of the plot diameter that is
     0.42%, 1.06%, 2.33% — the numbers below are those ratios at the ~240px
     plot the 390px frame allows once the eight axis labels have their room.
     If the plot ends up a different size, scale by the ratios, not by eye.
     RENAMED: what used to be --tk-chart-plot-stroke was a COLOUR wearing a
     width's name — the one exception in a file where -stroke means width
     three other times, and the reason a consumer had to stop and ask. It is
     --tk-chart-plot-ink now, and the suffix rule is enforced by the suite. */
  --tk-chart-grid-stroke:  1px;    /* rings and spokes; also the baseline's own
                                      dashed ring weight [b:42]              */
  --tk-chart-plot-stroke:  2.5px;  /* the eight-axis polygon              [m] */
  --tk-chart-vertex-size:  6px;    /* the dot at each axis value          [m] */
  /* [d] DEVIATION FROM THE MOCKUP, flagged for the CTO: the mockup draws the
     radar polygon in navy — the same navy that means "cognitive family" three
     centimetres away in the same picture. On dark the polygon is neutral here,
     so it claims no family while crossing all eight axes. One token flips it
     back if the CTO prefers the mockup exactly. */

  /* --- 14 · STATE -------------------------------------------------------- */
  /* Two states, and they differ in LIGHTNESS as well as hue (D-008).
     Why that is not optional, measured at the baseline's own card values —
     same L, same C, hue 148 against hue 12:
       normal vision            contrast 1.10:1   ΔEok .185
       after deuteranopia       contrast 1.02:1   ΔEok .019 (Machado 2009)
                                contrast 1.02:1   ΔEok .021 (Viénot 1999)
     That is the same grey. A player would not know whether they had just got
     it right. Every simulated figure in this file names its model, because the
     two disagree in the second decimal and a number without its condition is
     not a number.

     WHAT IT COSTS TO KEEP THE COLOUR. An earlier version of this comment
     claimed 3.00:1 was unreachable. That was wrong — it reported the edge of
     the grid that had been searched, not the edge of what exists. Measured
     properly, at the approved hues 148 and 12 with both inks held at or above
     4.50:1, the ceiling moves with the chroma floor and nothing else:

       chroma ≥ .00 → 4.03:1     ≥ .06 → 3.48:1     ≥ .12 → 3.02:1
       chroma ≥ .02 → 3.85:1     ≥ .08 → 3.30:1     ≥ .14 → 2.92:1
       chroma ≥ .04 → 3.65:1     ≥ .10 → 3.19:1     ≥ .16 → 2.76:1
                                              (Viénot 1999; Machado within .02)

     So 3.00:1 IS reachable — by draining the colour out of the colours. Every
     solution above 3.5:1 is a near-white against a near-grey. The real trade
     is chroma against separation, and this system spends chroma, because a
     state that reads as grey has already lost the channel it was meant to add.

     The consequence for the shape of this section: the pair that carries the
     meaning is the MARK pair, and text gets its own lighter red.

       success vs error (marks) : 4.00 normal · 5.48 protan · 3.44 deutan  (Viénot)
                                              · 5.18 protan · 3.43 deutan  (Machado)
     The mark pair sits at L .88 against L .54 (D-020). An earlier draft put the
     error at L .56: that bought a wider margin on the surface (3.66:1) and paid
     for it at the dichromat gate (3.15:1). L .54 gives 3.37:1 and 3.43:1, which
     maximises the SMALLER of the two margins — the binding constraint is always
     the narrower one, so that is the one worth widening.

     THERE IS NO ERROR TEXT COLOUR, and an earlier version of this file was
     wrong to ship one. It was an "error ink" variant, and it existed so that
     error WORDS could reach the 4.50:1 that body text needs. It cannot exist:
     searched across
     hue 12 ±10°, no colour is simultaneously ≥4.50:1 on the frame and ≥.30 in
     lightness from success — the two requirements cross between L .58 and
     L .60 and never overlap, so the count of candidates is zero, not few.
     The shipped one sat at 2.46:1 from success for a deuteranope and the
     control was not comparing it, which is how it survived.

     So error words are set in --tk-ink-primary and carry a glyph. That is the
     rule this whole section has been converging on from three directions:
     NO state is ever signalled by colour alone. Glyph, position, or words,
     always — enforced by app/design/tests/tokens.test.js. */
  --tk-state-success:   #94f1a2;  /* oklch(.88 .14 148) — 13.53:1  [d, from b:83] */
  --tk-state-error:     #b04559;  /* oklch(.54 .14 12)  —  3.38:1  [d, from b:84] */
  /* ΔL(success, error) = .34 — the C-5 threshold is .30.
     --tk-state-success doubles as its own ink: at 13.53:1 it needs no variant.

     THERE IS DELIBERATELY NO THIRD STATE, UNDER ANY NAME. Amber from the
     approved palette, oklch(.75 .14 32) = #f98a74, sits 2.10:1 from the error
     mark and 1.50:1 from success under Machado (2.09 / 1.51 under Viénot).
     A third colour-coded state would be a lie for roughly one boy in twelve.

     This is not enforced by watching for a particular token name — a check
     that watches names is bypassable by choosing another one, which is how a
     third state, named "caution", got past the first version of the control.
     (Written that way on purpose: no token name appears in this paragraph.
     A --tk- name inside a comment reads as a token that exists, and one did
     get picked up and used from a sentence exactly like this one.)
     The test enumerates EVERY --tk-state-* token in this file by pattern,
     groups the -ink variants with their mark, and requires every cross-group
     pair to hold ΔL ≥ .30 and 3.00:1 under both simulation models (D-013.3).

     Precisely: the test forbids a state that COLLAPSES with another, not a
     third state as such. A third mark far enough away in lightness would pass
     it honestly. The amber does not — it lands .131 in ΔL from success. The
     decision to ship two states and no more is a design decision and lives in
     this comment; the test enforces the property, which is the part that can
     be checked.

     If a third state is needed it must arrive on a non-colour channel — ask,
     and it will be added as a shape or a motion token, not as a hue. */

  /* --- 15 · IDENTITY AND STATE MARKS ------------------------------------- */
  /* The non-colour channels made physical. Game glyphs occupy the exact
     footprint of the baseline's key motif [b:108], so the card composition
     the committente approved does not move. Marks are drawn in ink tokens
     (§2) or state tokens (§14) by their consumer; these set only geometry. */
  --tk-card-glyph-size:    56px;   /* [d] anchored at the key-motif position [b:108]
                                      but larger than its 34px. Inherited with a
                                      reason and no measurement; measured since, on
                                      the true card under the carousel's own blur.
                                      Worst-pair distance across the seven cards:
                                        34px  .0230 / .0011 / .0002 / .0001
                                        56px  .0416 / .0030 / .0006 / .0004
                                       120px  .0958 / .0133 / .0026 / .0015
                                      Monotonic — bigger is strictly better — and
                                      bounded by composition: past ~150px the glyph
                                      reaches the index numeral at right:18. 56px is
                                      2.8x the baseline motif one card out and leaves
                                      the approved composition where it is. Going
                                      further is a change to the committente's
                                      layout, so it is a proposal, not a token. */
  --tk-card-glyph-stroke:  2px;    /* [d] monoline, constant at every size — the
                                      glyph shapes carry vector-effect:
                                      non-scaling-stroke, so 56px does not fatten
                                      the line into a poster weight */
  --tk-outcome-mark-size:  44px;   /* [d] = --tk-target-min: the state mark is the
                                      largest single signal on the outcome screen */
  --tk-outcome-mark-stroke: 2.5px; /* [d] heavier than the card glyph: it is seen
                                      once, at glance distance, often mid-motion */
  --tk-gauge-stroke:       12px;   /* [d] from the report mockup [m], arc weight
                                      scaled to the 390px frame */
  --tk-card-preview-w:    200px;   /* the game preview box on the card: under the
                                      glyph, above the name. 5:3, which is what the
                                      seven previews are drawn in.          [d] */
  --tk-card-preview-h:    120px;   /* [d] */
  --tk-text-score:         40px;   /* [d] the two /100 numerals on ME; mockup [m]
                                      sets them at roughly 3× the label size */

  /* --- 16 · THE STIMULUS PANEL ------------------------------------------- */
  /* What ships: the Go/No-Go draws its four symbols on a small light panel —
     a 220px square, 17.4% of the game area — that appears and disappears over
     a dark starfield, 144 times in 180 s. Its spec §2 asks for a white ground
     and a black stroke, and this is that, in miniature and in motion.
     (History, one line: this was a full-screen persistent whiteboard until
     D-030 replaced it with the starfield. The values did not change; the
     reason did, and the old reason is below so nobody re-derives it.)

     No new colours enter for this. The panel is the app's own ink and its own
     darkest surface, swapped: --tk-ink-primary becomes the ground and
     --tk-surface-frame-base becomes the stroke.

     WHY NOT PURE #ffffff ON #000000. That pair reaches 21.00:1 against the
     17.87:1 below, and the 3.13 given up used to buy relief from staring at a
     lit rectangle for three minutes. That argument went with the full-screen
     board — but the replacement is stronger, not weaker: a panel that flashes
     144 times is a repeated onset, not a steady glare, and repeated onsets are
     what tire an eye in a dark room. A ground 11% dimmer than paper white
     costs nothing the measurement needs. The onset step against the frame is
     16.43:1 either way — unmistakable as a temporal marker, which is the job
     it now has. What the spec protects is that the luminance does not CHANGE
     while the panel is up, not that it is maximal. */
  --tk-board-surface: #f2f2f2;   /* the panel — 16.53:1 against the frame it
                                    appears over, which is the onset  [b:21]  */
  --tk-board-ink:     #08080a;   /* the stroke — 17.87:1              [b:21]  */

  /* NO STATE COLOUR IS DEFINED FOR THIS BOARD, and the omission is the design.
     Three independent reasons, in order of how badly each one bites:

     1. It would corrupt the measurement it sits inside. The exercise reports a
        Reaction-to-Error Index built on spontaneous post-error slowing. Flash a
        colour after a false alarm and what gets measured is the player reacting
        to the app, not to their own error monitoring. The index would still
        produce a number, which is the dangerous kind of wrong.
     2. It is not reachable anyway. Searched exhaustively at hue 148 and hue 12:
        a state pair that is visible on the board (≥3:1), split in lightness
        (ΔL ≥ .30) and separated for a dichromat (≥3:1 under both models) has
        ZERO solutions once the marks must also stay ≥2:1 from the stimulus
        stroke — at every chroma floor tried, .12 down to .02. The conflict is
        structural: separating for a dichromat needs a wide lightness spread,
        and on a white ground the dark end of that spread lands on top of the
        stroke, where it reads as more stimulus.
     3. Any coloured overlay changes the board's luminance, which is the one
        property the white ground exists to hold still.

     Feedback belongs to the screens that bracket the run — the learning screen
     before the clock and the outcome screen after — and both sit on the dark
     frame, where §14 already works and C-5 already covers it. On the board
     itself, GO and NO-GO are told apart by position and by their labels, which
     is what D-021 asks for in any case. */

  /* --- 17 · THE TACTICAL-MEMORY COURT ------------------------------------ */
  /* Same idea as §16 — a light ground inside the dark frame — but its OWN
     context, not a reuse of the board's. The ground is the same value; the
     rules are not. The board must hold its luminance still and carries one
     near-black stroke; the court carries filled regions, five numbered discs,
     a ball and a drawn path, and nothing about it has to stay constant.
     Separate names let the two diverge later without a rename.

     Three tiers, because the court has to say what is the ANSWER and what is
     merely the CONTEXT — the drawn path is the player's answer and must never
     read as part of the printed court:
       tier 1  trace · players · ball      the answer      ≥ 3:1 over everything
       tier 2  court lines                 the context     3.27:1 on the ground
       tier 3  paint · zone hints          passive regions 1.12:1 and 3.24:1
     All fourteen must-not-be-confused pairs measured; the tightest is the
     trace against the court lines at 3.18:1. */
  --tk-court-surface: #f2f2f2;  /* = --tk-board-surface, its own context [b:21] */
  /* CAMPO named four of these without the court- prefix, so their figures name
     their ground explicitly. That is the convention, not a workaround: a
     contrast figure without its background is not a figure. */
  --tk-court-paint:   #e3e6ea;  /* the key — a region, 1.12:1            [d]    */
  --tk-zone-hint:     #798898;  /* drop targets — 3.24:1 on --tk-court-surface  */
  --tk-court-line:    #83868a;  /* markings — 3.27:1                     [d]    */
  --tk-trace:         #1f3864;  /* the drawn path — 10.38:1 on --tk-court-surface,
                                   and 3.18:1 on --tk-court-line so the answer
                                   never reads as the printed court
                                                        [m, the mockup navy]    */
  --tk-player-fill:   #22262c;  /* the five discs — 13.58:1 on --tk-court-surface */
  --tk-player-text:   #f2f2f2;  /* the numeral — 13.58:1 on --tk-player-fill    */
  --tk-ball:          #d46a34;  /* 3.17:1 on --tk-court-surface, 3.27:1 on --tk-trace,
                                   4.28:1 on --tk-player-fill. Shape is its first
                                   channel — it is smaller and unnumbered — and
                                   the hue only reinforces that.         [d]    */

  /* The APP'S SYMBOLS — what the exercise shows you — against --tk-trace, which
     is what YOU drew. Two voices, and they must not be confused the day the
     parachute puts them side by side (D-029).

     A third strong ink for the question does not exist. Searched the whole
     gamut: zero colours clear 3:1 against the ground, the answer, the discs,
     the ball AND the court lines at once — dropping only the ball and the lines
     opens it to 18762, which says exactly where the wall is. The court is full;
     this is the third time the same arithmetic has said so.

     So the question is not separated from the answer by hue. It is separated by
     WEIGHT, STYLE and TIME. During the demonstration the answer does not exist
     yet — the player has not drawn it — so the symbols take the strongest ink
     on the court, the same coach's marker the discs are drawn in. They differ
     from --tk-trace by only 1.31:1, and that is fine precisely because they are
     never both at full strength at once. */
  --tk-court-symbol-line: #22262c;  /* cuts, dribbles, passes — 13.58:1 on
                                       --tk-court-surface, 4.16:1 on --tk-court-line */
  --tk-court-symbol-bar:  #22262c;  /* screens, handoffs, staggers — same ink today.
                                       Two names so either can move later without a
                                       rename, the same reason --tk-court-surface is
                                       not spelled --tk-board-surface.        [d] */
  --tk-court-symbol-ghost: #a2a7ad; /* the reference shown BESIDE the answer, when the
                                       parachute compares them: 4.79:1 on --tk-trace,
                                       which is the only pair that matters there.
                                       DECLARED SHORTFALL: 2.16:1 on the ground, under
                                       the 3:1 a mark should hold. That gate is the one
                                       given up on purpose — a ghost that separated from
                                       the ground as well would land on the court lines
                                       and read as court. It is never the sole carrier:
                                       it appears only next to the answer it is being
                                       compared with, dashed and labelled.      [d] */

  /* NO STATE COLOUR HERE EITHER, and this time the reason is arithmetic rather
     than judgement. Searched hue 12 across 1699 in-gamut candidates: not one
     clears 3:1 against the ground, the paint, the lines, the trace, the discs
     and the ball at once. Dropping the court-line requirement alone still
     leaves zero. The eliminations: lines 91.5%, ball 90.7%, trace 69.0%,
     discs 48.0%. A legible half-court already occupies the whole range between
     a white ground and a near-black answer, so there is no free luminance band
     left for a state to occupy — and lightness is the only channel a dichromat
     keeps.

     Which is the same conclusion §16 reached by a different route, so state it
     once for the whole system: ON A LIGHT SURFACE HERE, COLOUR CANNOT BE THE
     STATE CHANNEL. The Tactical Memory does need error feedback — the spec
     restarts the chain at T0 — but it carries it as motion and words: the
     "Restart!" message on the dark chrome, where §14 works and C-5 covers it,
     plus the reset itself, which is the loudest signal available and costs no
     colour at all. */

  /* --- 19 · GLASS — the light world (D-044, package a) ------------------- */
  /* THE RULING THAT MADE THIS TRACTABLE, AND IT IS NOT A RESKIN. The dark
     surfaces in §1 do not die and are not inverted: THEY BECOME THE RUN. The
     exercise run must hold luminance constant (D-030/D-017, non-negotiable),
     the stimulus board's whole onset signal is its brightness against a dark
     frame (§16), the court is a light object that only reads because a dark
     frame surrounds it (§17), and the state pair in §14 is derived against
     that same dark frame. Inverting §1 would have broken all four at once.
     So the glass world is ADDED, for the shell — vault, ME, briefing,
     questionnaire — and §1 keeps the runs and the screens that bracket them.
     Two worlds, one file, and every figure already measured stays true.

     THE WORST CASE IS THE MEASUREMENT, NOT THE AVERAGE. A translucent surface
     composites with whatever passes under it, so a figure taken against the
     "typical" glass is a figure that fails somewhere on the screen. Every
     contrast below is measured against --tk-glass-surface, which IS the
     composite over the DEEPEST stop of the background gradient — the darkest
     the glass ever gets, and therefore the worst case for the dark ink that
     sits on it. Same class as the alpha-ink correction in §1, one turn of the
     screw tighter: there the foreground darkened with the surface, here the
     surface itself moves under the foreground.

     ONE TOKEN, TWO JOBS, ON PURPOSE. --tk-glass-surface is both the value
     every figure is measured against AND the flat fill for the no-blur path.
     They are the same colour by construction, so the fallback cannot drift
     from the thing it stands in for. */
  --tk-bg-top:   #f1edfe;   /* oklch(.955 .022 296) [D-044] */
  --tk-bg-mid:   #e5d9fc;   /* oklch(.905 .049 302) [D-044] */
  --tk-bg-deep:  #dbbbfa;   /* oklch(.840 .093 308) — the worst case lives here */
  /* Chroma rises as lightness falls in this corner of sRGB, so the gradient
     gets its richness from the deep end, not from pushing the light end. Each
     stop sits at 90% of its own hue's ceiling: 296 tops at .024, 302 at .055,
     308 at .103. Asking for a flat chroma across the three would have clipped
     two of them silently — the mistake §11 recorded for the seven hues, and
     the first draft of this gradient made it again on all three stops. */
  --tk-bg-field: linear-gradient(165deg, var(--tk-bg-top) 0%, var(--tk-bg-mid) 52%, var(--tk-bg-deep) 100%);   /* [D-044] */

  --tk-glass-surface: #efe0fd;                 /* worst-case composite = the fallback fill */
  --tk-glass-paint:   rgba(255,255,255,.55);   /* what you actually paint, with the blur   */
  --tk-glass-blur:    18px;
  --tk-glass-sheen:   linear-gradient(180deg, rgba(255,255,255,.55) 0%, rgba(255,255,255,0) 42%);   /* inner light, from above */
  --tk-glass-border:        rgba(255,255,255,.72);   /* 1.18:1 — decoration, and only legible BECAUSE the blur is there */
  --tk-glass-border-strong: #7f7397;                 /* 3.49:1 on --tk-glass-surface — the no-blur path's real edge */
  --tk-field-border-strong: #6e6383;                 /* 3.31:1 on --tk-bg-deep — the edge for controls standing on the bare field: a border off glass carries its OWN figure, never a borrowed one (C-5ag, D-089) */
  --tk-field-ink-primary:   var(--tk-glass-ink-primary);   /* 11.26:1 on --tk-bg-deep — same ink, figure measured on the field it stands on (C-5ag, D-089; matches D-075) */
  --tk-field-ink-secondary: var(--tk-glass-ink-secondary); /* 5.08:1 on --tk-bg-deep (C-5ag, D-089; matches D-075) */
  /* E-011 · THE STATE INDICATORS THE D-089 SWEEP WALKED PAST. That round
     repointed edges by TOKEN NAME, and these three wear other names, so they
     survived it wearing figures measured somewhere else: the unselected radio
     ring at 2.19:1 and the selected one at 2.55:1 on the field, the inactive
     nav mark at 2.93:1 on the glass. Each is the ONLY thing separating a
     control's state from its other state, so SC 1.4.11 asks 3:1 of all three
     and none of them reached it. Found by RENDERING the running app, which is
     the lesson E-011 records: the perimeter of a contrast fix is measured on
     what is drawn, never on the list of names the defect happened to use.
     Two of these are MEANT to be quiet and one is meant to be the house
     violet, so the value chosen is not the darkest available — it is the
     quietest one that still clears the floor, hue and role held fixed while
     lightness moves. The margin past 3.00 is deliberate: C-5af already taught
     this file that a floor set exactly at the shipped value fails on the very
     thing it protects. Every figure below is computed from the SHIPPED HEX,
     not from the float it came out of. */
  --tk-field-mark-rest:   #6e6978;   /* oklch(.531 .023 301) — 3.16:1 on --tk-bg-deep, the worst stop — a resting control's ring on the field [E-011] */
  --tk-field-mark-chosen: #8f38ed;   /* oklch(.560 .251 300) — 3.16:1 on --tk-bg-deep, the worst stop — the same house violet, legalised as a chosen state's ring [E-011] */
  --tk-glass-mark-rest:   #827d8c;   /* oklch(.599 .023 301) — 3.18:1 on --tk-glass-surface — an inactive tab's mark on the glass [E-011] */

  /* THE BRAND ACCENT SURVIVES AS IDENTITY AND FAILS AS A BUTTON FILL, and the
     two are different jobs. #9e4bff measures 3.42:1 on the worst glass, so it
     carries a mark, a ring, a bar. But white on it is 4.28:1 — under the text
     floor, so a primary button labelled in white is not available at the brand
     value. Dark ink on it is no escape either: 4.42:1, also under. The fix is a
     darker sibling at the same hue, not a different colour: ΔEok .063 from the
     mark, which reads as the same violet and measures as a legible one. */
  --tk-accent-strong: #9418ff;   /* oklch(.560 .288 300) — 4.89:1 under --tk-ink-primary [D-044] */

  --tk-glass-ink-primary:   #140e1d;   /* 15.09:1 on --tk-glass-surface [D-044] */
  --tk-glass-ink-secondary: #4f4a5a;   /*  6.81:1 on --tk-glass-surface [D-044] */
  --tk-glass-ink-faint:     #888392;   /*  2.93:1 on --tk-glass-surface — NOT for text */

  /* FALLBACKS ARE VALUES HERE AND SWITCHES THERE. This file may carry no
     @media and no @supports (shape contract, enforced), so the two paths are
     published as tokens and the consumer picks:
       @supports not (backdrop-filter: blur(1px))  → fill --tk-glass-surface
                                                     flat, border-strong
       @media (prefers-reduced-transparency: reduce) → the same flat pair
     A glass that turns unreadable on an old phone is a C-5 defect, not a
     cosmetic one, which is why the fallback ships as a measured value and not
     as advice. Same precedent as §9's reduced-motion, and cabled the same way. */

  /* --- 18 · BRAND -------------------------------------------------------- */
  /* D-042 · the wordmark. Drawing: app/design/assets/teamkeys-logo.svg, ink
     only, no colour of its own — it inherits currentColor, which is the whole
     reason it was redrawn as a vector instead of shipped as the raster.

     THE PURE WHITE, DECLARED AS THE BRIEF ASKED. The source wordmark is
     #ffffff. It is drawn here in --tk-ink-primary (#f2f2f2) instead, because
     #ffffff appears nowhere else in this system: the baseline itself chose
     #f2f2f2 for its brightest ink, and one token holding the brightest value
     is worth more than two that differ by a step nobody can see. The cost is
     measurable and negligible — 16.53:1 against 18.55:1 on frame-top, both an
     order of magnitude above any floor, and the pair is 1.7 ΔEok apart at a
     size where the mark is a shape, not a texture. If the committente wants
     the literal #ffffff it is one token, not a redraw.

     Sizes are widths, not heights: the drawing is 232x104, so height follows
     from the aspect and a consumer never has to compute it. */
  --tk-logo-w:         136px;   /* vault, in the slot the screen title had  [D-042] */
  --tk-logo-w-compact:  92px;   /* ME header, beside the screen's own title [D-042] */
  /* D-044 · THE WORDMARK WAS A DARK-WORLD ASSET AND THE WORLD MOVED UNDER IT.
     --tk-logo-ink was --tk-ink-primary (#f2f2f2). Every place the mark now
     lives is light — vault, ME, splash — and on the splash gradient #f2f2f2
     measures 1.03:1 on the top stop, 1.50:1 on the deepest. Invisible, three
     times. Found deriving the splash, not by rereading the decision: nothing
     in D-042 was wrong when it was written, the ground moved in D-044.
     This is the token that made the mark survivable — the mark is currentColor
     precisely so a change of world costs one line here and no redraw. */
  /* THE RULE, because two worlds means the mark has two right answers and no
     default: THE MARK TAKES THE INK OF THE WORLD IT SITS IN. It is
     currentColor, so the consumer chooses, and choosing wrong is silent — the
     mark does not break, it disappears. Both answers are tokens so neither is
     a judgement call at the call site:
        light world (--tk-bg-field)      → --tk-logo-ink       11.26:1
        dark  world (--tk-surface-frame) → --tk-logo-ink-dark  16.53:1
     Picking the other one measures 1.50:1 and 1.02:1 respectively.
     This rule is here because package (b) repointed --tk-logo-ink at the light
     world for the splash and, in doing so, silently took the vault to 1.02:1 —
     a screen nobody had edited, broken by a shared token changing meaning
     underneath it. 毛线 caught it from the shell. C-5y now requires every
     screen that shows the mark to name its world and take the matching ink,
     which is the part that was never anyone's job to remember. */
  --tk-logo-ink:       var(--tk-glass-ink-primary);   /* 11.26:1 on --tk-bg-deep [D-044] */
  --tk-logo-ink-dark:  var(--tk-ink-primary);         /* 16.53:1 on --tk-surface-frame-top [D-044] */

  /* THE BOUNDARY BETWEEN THE TWO WORLDS, written down (D-044 package d).
     THE GLASS IS THE HOUSE. THE DARK IS THE MEASUREMENT ROOM.
       glass — vault, briefing, questionnaire, profiles, ME, splash: everything
               the player moves through by choice.
       dark  — the Go/No-Go run, 甜筒's screens, the tactical-memory court:
               every surface whose darkness is a MEASUREMENT REQUIREMENT and
               not a taste, each already on the record (D-030, D-017, §16, §17).
       door  — the junction, ≤ 480 ms, inside 甜筒's 1200 ms adaptation beat.
     The test for which world a surface belongs to is not how it looks: it is
     whether a number depends on its luminance. If one does, it is dark, and no
     directive about tone reaches it. If none does, it is glass.

     WHAT FOLLOWS FROM THIS, and it is the part that bites: INK FOLLOWS ITS
     IMMEDIATE SURFACE, NOT THE WORLD. The vault's cards stay dark faces even on
     a light page — measured, they got BETTER there, card boundary 1.04:1 → 6.10
     to 7.50:1 — so their numerals keep the dark world's light ink while the
     screen around them takes the glass inks. The logo follows the world because
     it sits on the world; a card's contents follow the card.

     A LIMIT I COULD NOT CABLE, DECLARED RATHER THAN FAKED. C-5q measures every
     ink against --tk-surface-frame-top, so in the glass world it is checking
     against the wrong surface and cannot see --tk-ink-secondary sitting at
     1.13:1 on glass. The obvious fix — make C-5q world-aware per file, the way
     C-5y is — does not hold: screens/screen.css legitimately spans BOTH worlds
     in one file, styling the dark desk the phone sits on and the light screen
     inside it. A file-granular guard would fire on a correct line, and a guard
     that fires on correct lines gets switched off. So this stays a declared
     hole, not a guard I would not trust. */

  /* --- 19b · DOMAIN FAMILIES ON GLASS (D-044, package c) ----------------- */
  /* THE COMMITTENTE'S OWN COLOURS COME HOME. §12 lightened navy and sage
     because "those literals are for white paper and die on this app" — the
     navy measured 1.58:1 on the dark frame. The glass world is light paper
     again, and the lightened pair is the one that dies now: on
     --tk-glass-surface the app's cognitive measures 3.18:1 and its mental
     1.80:1, which is invisible. The mockup's originals, drawn for white,
     clear it without being touched. So the ME on glass did not get a new
     palette — it got [m] back, unaltered.

     ⚠ AND THEN D-045 TOOK IT AWAY AGAIN, within the day. The gauges went to
     the violet gradient, the axis labels are ink, and D-045 REVISED put both
     radar strokes on --tk-radar-line. THESE TWO TOKENS NOW HAVE NO CONSUMER.
     They are kept, not deleted, for one reason: they are the committente's own
     measured values and re-deriving them from the mockup later is work already
     done. But nothing on screen uses them today, and a token with no consumer
     is the kind of thing a tidy-up deletes or a hurry reuses for the wrong job.
     Whoever reaches for these should be reaching deliberately.
     (The prose above said "the first time any screen has been able to show the
     committente their own values". It was true when written and stopped being
     true a few hours later. I am correcting it rather than leaving it, because
     I had just told the CTO I would not pretend the colours were still there —
     and the file was pretending.) */
  --tk-glass-domain-cognitive: #1f3864;   /* 9.27:1 on --tk-glass-surface [m] */
  --tk-glass-domain-mental:    #5f7161;   /* 4.17:1 on --tk-glass-surface [m] */
  /* Both clear the 3.00 mark floor; sage sits under the 4.50 text floor, so
     these colour STROKES AND FILLS, never a label. An axis name is ink.

     毛线 asked whether to coin a dark mental so the labels could carry the
     family. The step exists — oklch(.46 .035 148.2) = #4b5e4d holds 5.57:1 on
     the worst glass — and I recommend against it, for two reasons and the
     second is the real one.
     First: sage as text measures 3.11:1 where a panel sits on the deep stop of
     the gradient and 5.05:1 where it sits on the light one. A LABEL WHOSE
     LEGIBILITY DEPENDS ON WHERE THE PANEL HAPPENS TO SIT IS NOT LEGIBLE — it
     is a figure without its condition, in a place the condition moves.
     Second: it would put two greens in one view, the radar's stroke and the
     label's, differing only in lightness. Two greens a step apart do not read
     as a system, they read as a mistake. The family is already carried by the
     stroke and by the stroke STYLE (solid vs dashed), which survives colour
     blindness; the label adds nothing the reader does not already have.
     (毛线 measured 2.94:1; I get 3.11 to 5.05 depending on the stop. I cannot
     reproduce 2.94 against any surface here — it does not change the verdict,
     which is why it is noted rather than argued.) */

  /* --- 19c · THE ME CHART, IN VIOLET (D-045) ----------------------------- */
  /* The user's directive puts the two gauges on a violet gradient, which
     supersedes navy/sage ON THE GAUGES. The families keep the radar, and the
     "the committente's colours came home" finding stays true there — it is
     narrowed, not withdrawn.

     THE ARC IS GRAPHICS, so 3:1 on the panel, and the LIGHT stop is what binds:
     at hue 300 the ceiling of chroma rises as lightness falls, so a lighter
     violet is also a weaker one. Measured on --tk-glass-surface: L .64 gives
     2.98:1 and misses; L .60 gives 3.58:1. So the arc runs from the lightest
     violet that holds to a deep one, and --tk-accent-strong sits between them —
     the gradient is the brand's own axis rather than a new colour.
     The big number and the "/100" stay ink. They are text, and text does not
     get to be decorative because the thing beside it became decorative. */
  --tk-gauge-arc-from: #9c44ff;   /* oklch(.60 .258 300) — 3.58:1 on --tk-glass-surface [D-045] */
  --tk-gauge-arc-to:   #7900d4;   /* oklch(.48 .257 300) — 6.06:1 on --tk-glass-surface [D-045] */

  /* THE RADAR VEIL, AND THE WORST CASE THE BRIEF DID NOT NAME. A translucent
     fill inside the polygons — decoration, and it cannot encode state because
     both polygons get the SAME veil, which is the by-construction guarantee the
     card glow already uses.
     The ceiling is not the grid underneath, which is what I was asked to watch:
     it is the SAGE STROKE, the mental family's own channel, which crosses 3:1
     at veil .22 while the grid is still fine. And the real binding case is that
     THE TWO POLYGONS OVERLAP, so the veil doubles exactly where the chart is
     busiest: at .14 the overlap puts sage at 2.79:1 and breaks it. .09 holds
     3.21:1 even doubled, with the grid at 2.26:1 — dimmer inside the shape than
     outside it, which is a visible consequence and is stated rather than hidden. */
  --tk-radar-veil: rgba(148,24,255,.09);   /* [D-045] */

  /* THE POLYGON STROKE GOES VIOLET (D-045 REVISED), and the obvious candidate
     is the right one — but only just, which is the part worth writing down.
     The stroke crosses six surfaces and the binding two are the DOUBLED veil
     (where the polygons overlap) and the gradient's deep stop:
       #9c44ff — the gauge arc's own light stop — 2.75:1 and 2.67:1. FAILS.
       #9418ff                                   3.37:1 and 3.26:1. holds.
     One step of lightness separates a value that works from one that does not,
     so this is a token and not a literal anyone may nudge.

     WHY IT READS AS SYSTEM AND NOT AS A THIRD VIOLET. The gauge arc is a RAMP
     from L .60 to L .48; this sits at L .56, inside a range the same screen is
     already showing. It is a point on a gradient that is present, not a fourth
     colour beside it — which is the distinction my own rule about "two greens a
     step apart" was reaching for.

     BOTH POLYGONS TAKE IT, and that is what keeps the namespaces clean. Violet
     here says "this is the data figure" — accent work, marking the primary
     thing — rather than becoming a fourth family colour, which is what giving
     it to ONE polygon would have done. Family moves entirely onto the dash.
     VERTICES FOLLOW THE STROKE, not ink. Ink would measure 11.62:1 against the
     doubled veil and be perfectly legible, and would still be wrong: vertices
     in a different colour from their own line read as a second data series.
     They sit 3.45:1 apart, which is enough to look deliberate and therefore
     enough to mislead.

     THE CONSEQUENCE, STATED: --tk-glass-domain-* now has no consumer on ME.
     The gauges took the gradient, the labels are ink, the strokes are violet.
     The committente's own navy and sage, which came home one round ago, have
     no surface left. The INFORMATION survives — dash for family, and the gauge
     labels say the words — and D-008 is satisfied more strictly than before,
     since nothing depends on hue at all now. But the [m] traceability is gone
     from this screen, and that is a loss to declare rather than discover. */
  /* NAMED -line, NOT -stroke, and C-5p is why. In this file -stroke already
     promises a LENGTH — --tk-chart-plot-stroke is 2.5px — while -line has meant
     a colour since --tk-accent-line. I wrote -stroke on reflex and the suffix
     contract caught it in the same run: a consumer reaching for a width would
     have got a violet. */
  --tk-radar-line: var(--tk-accent-strong);   /* 3.26:1 on --tk-bg-deep [D-045 R] */

  /* --- 20 · BUTTONS (D-044, package b) ----------------------------------- */
  /* One system, three ranks, replacing the per-screen buttons. Every state
     carries a NON-COLOUR channel (D-008): elevation, geometry, or a ring.
     Sizes are frame coordinates — remember C-5u, 44 here renders 28.8 at the
     D-039 floor, which clears SC 2.5.8 and never reaches SC 2.5.5. */
  --tk-btn-h:              44px;   /* = --tk-target-min, not a coincidence: same floor */
  --tk-btn-pad-x:          20px;
  --tk-btn-gap:            8px;
  /* THE FILL IS NEVER THE BRAND VALUE. #9e4bff carries no label in either
     direction — white on it is 4.28:1, dark ink 4.42:1, both under the floor —
     so the primary button takes --tk-accent-strong and the brand value stays
     what it always was: a mark, a ring, a bar. (Those two figures are stated
     HERE, in a block attached to no declaration, because C-5j reads a figure
     in a declaration's comment as a claim ABOUT that declaration — and it
     rejected them, correctly, when they sat on the fill.) */
  --tk-btn-primary-fill:   var(--tk-accent-strong);
  --tk-btn-primary-ink:    var(--tk-ink-primary);        /* 4.89:1 on --tk-btn-primary-fill */
  --tk-btn-secondary-ink:  var(--tk-glass-ink-primary);  /* 15.09:1 on --tk-glass-surface */
  --tk-btn-ghost-ink:      var(--tk-glass-ink-primary);
  --tk-btn-ghost-border:   var(--tk-glass-border-strong);
  /* THE FOCUS RING IS INK, NOT ACCENT, AND THAT IS MEASURED NOT TASTE. A ring
     must clear 3:1 against BOTH what it surrounds and what surrounds it. An
     accent-strong ring on an accent-strong button is 1.00:1 — the same colour,
     so the most expected choice is the one that cannot work. The brand accent
     is no better: 1.28:1 on the fill.

     THE RING IS DRAWN INSIDE THE BUTTON, AND THAT IS THE OFFICIAL READING OF
     THIS TOKEN (甜筒's finding, D-044). I shipped the reference with `outline`
     and a positive offset, which puts the ring OUTSIDE — on whatever the
     button happens to be sitting on. Measured there it is 15.09:1 on glass,
     11.26:1 on the splash gradient, and 1.02:1 on the run's dark frame:
     invisible in the measurement room. 甜筒 did not recolour it to cope, which
     would have been adapting in silence; they drew it inside, where the
     documented figure actually applies, and asked.

     Inset is right for a reason that outlives this case: OUTSIDE, THE RING'S
     GUARANTEE DEPENDS ON A SURFACE THE TOKEN CANNOT SEE. Inside, its only
     neighbours are the button's own fills — 3.45:1 on primary, 15.09:1 on the
     glass of secondary — both knowable here, both clearing 3:1. So the answer
     to "does the dark world need its own ring" is no, and that is the point: a
     ring that never touches the world needs no variant per world. One ring,
     both worlds. Same class as the logo defect, arriving from the other side —
     there a figure was true and the world moved; here a figure was true and
     the DRAWING moved. */
  --tk-btn-focus-ring:     var(--tk-glass-ink-primary);   /* 3.45:1 on --tk-btn-primary-fill */
  /* the gap is fill, not page: it keeps the ring's neighbours inside the button */
  --tk-btn-focus-ring-w:   2px;
  --tk-btn-focus-ring-gap: 2px;
  /* CORRECTION, D-049: "one ring, both worlds" was true of the FILLED ranks and
     I wrote it about all three. A GHOST control has no fill, so drawing its ring
     inside puts the ring on the page after all — the very dependency inset was
     chosen to remove. Until D-049 that cost nothing, because every ghost control
     shipped on glass; the five measurement rooms are the first dark screens with
     controls, and there the ring measures 1.04:1 on --tk-surface-game. Invisible,
     which is how a focus ring fails: not by breaking, by disappearing.
     So the ghost rank — and only the ghost rank — takes a variant per world. The
     reasoning in the block above is unchanged and still right; its scope was
     wrong, and the scope is what is fixed here. */
  --tk-btn-ghost-border-dark: var(--tk-fill-mid);      /*  4.04:1 on --tk-surface-game [D-049] */
  --tk-btn-focus-ring-dark:   var(--tk-ink-primary);   /* 17.64:1 on --tk-surface-game [D-049] */
  --tk-btn-press-shift:    1px;    /* geometry: the press is felt, not just seen */
  /* DISABLED IS DIMMER, NEVER ILLEGIBLE. The fill drops to alpha .45 over the
     glass, which composites to #c686fe, and the label stays the dark ink at
     7.42:1 there — a disabled control that cannot be read is not disabled, it
     is broken. The non-colour channel is the loss of elevation: a disabled
     button is flat, and that reads without any hue at all.
     (Figure stated here rather than on the declaration below: .45 is a number,
     not a colour, and C-5j rightly refused to measure a contrast against it.
     Third time this turn the guard corrected where I attached a figure.) */
  --tk-btn-disabled-alpha: .45;

  /* --- 21 · SPLASH (D-044, package b) ------------------------------------ */
  /* Derived from the structure of the parallel project's Splash.tsx, not from
     its CSS, which was not supplied — declared as a derivation, the same way
     the wordmark declares its redraw. If the CSS arrives, a fidelity pass
     follows and the divergences get written down.
     THE ONE KNOB THAT MATTERS IS THE HOLD. 3 s on every load, not just the
     first, is friction multiplied by every opening. It lives in exactly one
     place so the playtest can turn it without a hunt. */
  --tk-splash-hold:     3000ms;
  --tk-splash-fade:      700ms;
  --tk-splash-flash:     420ms;   /* the opening flash, in only */
  --tk-splash-breathe:  2600ms;   /* the glow's cycle — one slow breath, not a pulse */
  --tk-splash-logo-w:    208px;
  --tk-splash-tagline-ink: var(--tk-glass-ink-secondary);  /* 5.08:1 on --tk-bg-deep, the worst stop */
  --tk-splash-line:     linear-gradient(90deg, rgba(148,24,255,0) 0%, rgba(148,24,255,.55) 50%, rgba(148,24,255,0) 100%);
  --tk-splash-particle:   3px;

  /* --- 22 · SCREEN TRANSITIONS (D-044, package b) ------------------------ */
  --tk-duration-screen: 260ms;
  --tk-ease-screen:     cubic-bezier(.4, 0, .2, 1);
  /* THE JUNCTION: light shell → dark run. It must not look like a fault, and
     it must not cost the player time. 甜筒 places 1200 ms of adaptation before
     t0; this transition SPENDS TIME THAT ALREADY EXISTS instead of adding any:
       0–480 ms    crossfade, glass world out / run surface in
       480–1200 ms still, dark, stimulus-free — the adaptation beat itself
     Landing dark well before t0 is the whole point: the eye adapts to a
     CONSTANT field, and a field still changing at t0 would put a luminance
     transient inside the measurement (D-030/D-017). The crossfade is therefore
     capped by the beat, not chosen for feel — if the beat ever shortens, this
     shortens with it and never the other way. */
  --tk-duration-worldshift:  480ms;
  --tk-delay-worldsettle:    720ms;   /* 480 + 720 = 甜筒's 1200 ms beat, exactly */

  /* --- 23 · THE FIVE MEASUREMENT ROOMS (D-049) --------------------------- */
  /* Five exercises enter the five "coming soon" slots: attention (a Stroop),
     switching (a rule-flip), time-pressure (a shot clock), anticipation (a
     freeze-frame on the court), error-recovery (panels lit to a rhythm).
     Engines: 甜筒 takes the first three, 石头 the last two.

     ALL FIVE ARE DARK, AND NOT BECAUSE THEY LOOK BETTER THAT WAY. §19's test
     is whether a number depends on a surface's luminance. In every one of
     these a reaction time is the measurement, so the answer is yes five times,
     and no directive about tone reaches them. The shell around them stays
     glass; the junction is the door §22 already times.

     THE GROUND OF A ROOM IS --tk-surface-game AND IT HOLDS STILL. A stimulus
     may appear, brighten, vanish — that is the signal. The GROUND may not
     move, because the eye adapts to a constant field and a field still
     changing at t0 puts a luminance transient inside the measurement
     (D-030/D-017). This is the invariant the rooms share, and it is the one
     thing two different workers building five rooms could each break
     separately, so it is a control (C-5ab) and not a paragraph.

     THE ARITHMETIC THAT SHAPES EVERY PALETTE BELOW, stated once because it
     caught three of the five. On this ground an ink that must carry TEXT
     needs 4.50:1, which puts its luminance at Y ≥ .1893; the ceiling is 1.0.
     So the whole usable band spans 1.05/.2393 = 4.39:1 end to end. Three
     inks pairwise 3:1 apart would need their extremes 9:1 apart. They do not
     fit — not at any hue, not on any ground, because the text floor squeezes
     every ink into one end of the range wherever the ground sits.
     CONSEQUENCE: a palette of STIMULI cannot be separated by contrast. It is
     separated by ΔEok under simulation, which is the instrument that still
     sees the axis a dichromat keeps. The panels in §23b are not text, which is
     exactly why they DO get a luminance ladder and the Stroop cannot. */

  /* --- 23·0 · THE SHAPE PAIR: FILLED ACTS, OPEN IGNORES ------------------ */
  /* DECLARED HERE BECAUSE TWO FRONTS REACHED IT SEPARATELY. 甜筒 solved the
     Reaction distractor with geometry rather than colour — a filled disc is
     the thing to hit, an open ring is the thing to let alone — and the same
     pair was already carrying the Go/No-Go preview (respond, or do not) and
     the Tracking mark (yours, or one of the others). Three uses, one rule,
     arrived at three times: that is the point at which a convention stops
     being a coincidence and gets written down.

     WHY THIS PAIR AND NOT ANOTHER. FILL is the only channel in this system
     that survives every reduction at once: greyscale, any dichromacy, the
     carousel's blur, a reader who turns transparency off, and a screen at 320
     wide. Opacity fails the last two, hue fails the second, and a dash fails
     the third — measured, and the dash failed it on a real phone in the L2
     playtest before it failed it here.
     It also composes with §14 rather than competing: a state colour may sit ON
     either shape, because the shape says WHICH OBJECT and the state colour
     says WHAT HAPPENED. They are different questions, so they may share a mark.

     The weight is a token because two engines draw this pair independently and
     a ring that is thinner in one exercise than in another is the same drift
     C-5n exists to stop, one level up — in geometry rather than in path data. */
  --tk-stimulus-ring-stroke: 3px;   /* [d, D-073] the open half of the pair. Heavier
                                       than --tk-card-glyph-stroke on purpose: a ring
                                       is read as a SHAPE at a glance, not traced as a
                                       line, and at 2px beside a solid disc it read as
                                       the disc's unfinished twin (the L2 finding). */

  /* --- 23a · THE STROOP INKS (attention) --------------------------------- */
  /* The word lies: it names one colour and is drawn in another. So the ink is
     a STIMULUS, not a state — it is the thing being measured, not a verdict
     about the player — and it does not enter the §14 namespace. Feedback in
     this room carries no colour at all, which keeps every one of these free to
     be itself.

     THE SET IS FROZEN BY THE PROTOCOL, NOT CHOSEN HERE. Three inks, blue and
     orange and yellow, from app/games/attention/attention.spec.js, which is
     their single source: the ids, the words and the token names all come from
     that file and this one only supplies the values. 甜筒's reason for the
     three is the reason the values below are shaped the way they are — a
     Stroop carrying red AND green measures the vision of roughly one boy in
     sixteen instead of his attention, and a low score for that reason is not
     a datum, it is harm.

     WHAT THE BRIEF ASKS, AND WHAT IT REDUCES TO. "No word/ink pair under the
     minimum": a word is text and the ground is constant, so a pair's contrast
     depends only on its ink. Every pair holds iff every ink holds. The
     combinatorial requirement is a per-ink one, and that is the whole of it.

     THE CEILING ON CRITERION (b), AND IT IS ARITHMETIC RATHER THAN PALETTE.
     An ink that carries text on this ground needs 4.50:1, which puts it at
     Y ≥ .1893; the ceiling is Y = 1. So the whole usable band spans
     1.05/.2393 = 4.39:1 end to end, and three inks spaced evenly inside it
     can be at most √4.39 = 2.095:1 apart pairwise. THREE INKS 3:1 APART DO
     NOT EXIST — not at these hues, not at any hues, not on any ground, because
     the text floor squeezes every ink into one end of the range wherever the
     ground sits. Constrained further to inks that still answer to their own
     name (chroma ≥ .16, hue within 10° of the word's anchor) the reachable
     ceiling is 2.00:1, and the three below ship at 1.94:1 — 93% of what the
     arithmetic allows. A control for (b) belongs at 1.80, not at 3.00.

     MEASURED, from the shipped hex, every pair:
                     luminance   red-green ΔEok   tritan ΔEok
       blue/orange     1.94:1         .370            .428
       blue/yellow     3.99:1         .521            .470
       orange/yellow   2.06:1         .202            .247
     Red-green is the worst of four simulations (Machado and Viénot x deutan
     and protan). The JND is about .020, so the weakest pair sits at ten times
     it. AND THE DECLARED RESIDUAL LIMIT DOES NOT BITE: the spec writes off
     tritanopia as uncoverable, correctly on prevalence, but this trio survives
     it anyway at .247 — because blue and yellow are 3.99:1 apart in luminance,
     and luminance is what a tritanope keeps. Stated because a declared limit
     that turns out not to apply should be retired, not inherited.

     WHY THESE HUES AND NOT PURER ONES. Blue sits 4.1° off the hue sRGB blue
     occupies, orange 10.0°, yellow 0.4°. Pure blue is L .452 and measures
     2.4:1 here — it cannot be text on this ground at all, so the blue that
     ships is the darkest one that clears the floor, which is also what makes
     it the bottom rung of the luminance ladder. Loosening chroma to .13 buys
     +.087 of red-green separation and costs 33° of hue on the blue, which
     lands in cyan: a name the player hesitates over adds a naming latency
     that is not interference, in the one number this exercise reports.
     Purple never entered: hue 300 is --tk-accent, and a stimulus wearing the
     colour this system reserves for focus is a namespace collision.

     C-5aa enumerates these BY PATTERN and applies all three of 甜筒's criteria,
     so a fourth ink is measured the day it is written rather than the day
     somebody notices. */
  --tk-stroop-ink-blue:   #1a73fe;   /* oklch(.590 .222 260) —  4.63:1 on --tk-surface-game [D-049] */
  --tk-stroop-ink-orange: #fa9807;   /* oklch(.764 .170 65)  —  8.99:1 on --tk-surface-game [D-049] */
  --tk-stroop-ink-yellow: #ffff52;   /* oklch(.971 .185 109) — 18.50:1 on --tk-surface-game [D-049] */
  /* On --tk-surface-frame-top the three measure 4.34, 8.42 and 17.33, so the
     blue is UNDER the text floor there. That is not a defect and not a
     tolerance: these are MOUNT inks. The engine is handed a flat
     --tk-surface-game precisely so a stimulus cannot change brightness with
     where it is drawn, and nothing may paint them on the frame chrome. The
     figures are quoted here rather than on the declarations because C-5j reads
     a figure on a declaration as the claim about that token's own context. */
  --tk-stroop-word-size:  40px;   /* [d] the stimulus never changes size between trials:
                                     a word that grew or shrank would add a cue the
                                     paradigm does not want. The spec already controls
                                     word LENGTH for the same reason. Largest type in
                                     the file, level with --tk-text-score. */

  /* --- 23b · THE RHYTHM PANELS (error-recovery) -------------------------- */
  /* Panels light to a beat, the chain breaks on a mistake, and what is
     measured is the re-entry. Three states — unlit, lit, errored — and the
     brief asks that they read by LUMINANCE and not by hue. They can, because
     a panel is a filled region and not text: nothing forces these into one
     end of the range, so the ladder that §23a proved impossible is available
     here. That contrast between the two rooms is the reason both paragraphs
     are worth having.

     THE LADDER, from the shipped values, normal vision then the worst of the
     four red-green simulations:
       unlit (the ground) vs errored     3.61:1     2.86:1
       unlit              vs lit        17.64:1    17.61:1
       errored            vs lit         4.88:1     4.35:1
     TWO OF THREE HOLD 3:1 FOR A DICHROMAT AND ONE DOES NOT. The errored panel
     against an unlit one is 2.86:1, under the floor this system holds itself
     to — 0.14 short, and it is not rounded up. So the fill is NOT the channel
     that tells those two apart: THE MARK IS. An errored panel carries a cross;
     an unlit one carries nothing, ever. That is D-008 arriving at the same
     answer §16 and §17 each reached from the other side, and it is enforced
     rather than promised, because the fill is --tk-state-error and C-5h will
     not let a state colour be painted without naming its non-colour channel.

     THERE IS NO FOURTH FILL FOR "hit". --tk-state-success measures 1.22:1
     against the lit panel: the two are the same rung, and a ladder cannot
     carry a fourth step it does not have room for. The confirmation of a good
     hit is the panel going OUT on the beat — extinction is instant, costs no
     colour, and is what a player is already watching for. */
  --tk-panel-lit-fill:  #f2f2f2;   /* 17.64:1 on --tk-surface-game, and it is also the
                                      cross inside an errored panel: 4.88:1 on
                                      --tk-state-error                        [D-049] */
  --tk-panel-off-line:  var(--tk-track-dim);   /* 3.76:1 on --tk-surface-game — an unlit
                                      panel is an OUTLINE, so the grid is legible before
                                      anything lights and a player can see where the
                                      beats will land                          [D-049] */
  --tk-panel-off-stroke: 2px;
  --tk-panel-gap:        10px;   /* [d] the grid's rhythm has one home: reference and
                                    engine are two hands on the same drawing */

  /* --- 23c · THE SHOT CLOCK (time-pressure) ------------------------------ */
  /* THE CLOCK NEVER CHANGES COLOUR AS IT RUNS OUT, and the reason is stronger
     than D-008 here. The obvious design turns the bar red near zero; that is
     a colour carrying a state alone, which §14 forbids — but worse, it is a
     LUMINANCE CHANGE inside a timed trial, which is what D-030/D-017 exist to
     prevent. Urgency travels on LENGTH, which is geometry, and geometry is
     free. If the room wants a second channel it takes a tick or a count, not
     a hue.
     The track is 甜筒's doctrine kept: a bar communicates a FRACTION, so what
     is LEFT has to be as legible as what is spent. --tk-track-dim was derived
     for exactly that and is reused rather than re-derived. */
  --tk-clock-track:  var(--tk-track-dim);     /*  3.76:1 on --tk-surface-game [D-049] */
  --tk-clock-fill:   var(--tk-ink-primary);   /* 17.64:1 on --tk-surface-game [D-049] */
  --tk-clock-stroke: 8px;

  /* --- 23e · THE SIX PANELS (Sequence Recall) ---------------------------- */
  /* A FAMILY OF ITS OWN, NOT THREE MORE STROOP INKS, and the reason is what
     each set is FOR. A Stroop ink exists to be NAMED, so it is held to a hue
     within 10° of the word's anchor and a chroma that keeps the name — and
     that palette is closed by arithmetic anyway (§23a: a fourth ink has
     nowhere to stand). These six are never named by anybody. Borrowing three
     stimuli as three panels would tie a naming constraint to a game that has
     no words in it, and would put six panels at the mercy of an edit made for
     a reason that has nothing to do with them.

     COLOUR HERE IS REINFORCEMENT, AND THAT IS THE WHOLE GATE. The panel's
     POSITION is the primary channel, by construction. So the requirement is
     not "every pair separable under every colour vision" — it is that no pair
     MISLEADS, which is why the six were chosen by maximising the WEAKEST pair
     under the four red-green simulations rather than by picking nice hues.

     THE §11 TRAP, AND THIS TIME IT WAS MEASURED BEFORE IT WAS MADE. Six hues
     at one lightness is exactly the seven card faces again. Measured: six hues
     spread around the wheel at a flat L .70 hold ΔEok .011 at their weakest
     pair under deuteranopia — BELOW the ~.020 JND, i.e. two of them are the
     same colour. The set below holds .140. Twenty times better, and the only
     difference is that lightness is allowed to vary.

     WHAT THE MEASUREMENT DECIDED ON ITS OWN. The house rule says no red/green
     pair; no red is in this set, and nothing told the search to avoid one —
     any red would have collapsed against the green and sunk the objective. The
     rule and the arithmetic agree without being introduced.

     A PANEL MUST NOT WEAR A MEANING THE APP ALREADY SPEAKS. Every one of the
     six stands at least ΔEok .18 from --tk-state-error, --tk-state-success,
     --tk-accent and --tk-accent-strong — a panel that looked like the error
     mark, in a game that shows an error mark, would be a lie by adjacency.
     That floor is not a taste: it is set EQUAL to the weakest distance between
     two panels, so a panel is exactly as far from a meaning as it is from
     another panel. Raising it to .24 costs the weakest pair a third (.179 →
     .118); leaving it at .10 lets a panel sit .118 from the success mark.
     C-5af holds all three clauses, and holds the reserved distances from the
     other side too: if the accent or a state moves, this goes red here rather
     than going wrong in the game.

     THE FLOOR IS 4.50:1 AND NOT THE 3:1 A GRAPHIC WOULD NEED. At 3:1 the
     search returns panels that measure 3.05 and read as smudges beside the
     bright ones — the metric satisfied and the screen wrong, which is the
     failure this front keeps meeting. 4.50 buys "every panel is plainly a lit
     colour" for 22% of the weakest pair (.179 → .140), and it is a step the
     house already uses rather than a fifth number to remember. */
  --tk-recall-1-fill: #2a8c05;   /* oklch(.560 .178 140) —  4.56:1 on --tk-surface-game [D-087] */
  --tk-recall-2-fill: #0f87a9;   /* oklch(.580 .106 224) —  4.76:1 on --tk-surface-game [D-087] */
  --tk-recall-3-fill: #7ea0fd;   /* oklch(.720 .142 268) —  7.84:1 on --tk-surface-game [D-087] */
  --tk-recall-4-fill: #07b89b;   /* oklch(.700 .130 176) —  7.84:1 on --tk-surface-game [D-087] */
  --tk-recall-5-fill: #fdb500;   /* oklch(.820 .170  80) — 11.08:1 on --tk-surface-game [D-087] */
  --tk-recall-6-fill: #f4c4fe;   /* oklch(.880 .094 320) — 13.33:1 on --tk-surface-game [D-087] */
  /* THE SET IS THREE TIERS OF TWO, not a ladder of six, and it is worth
     knowing which way it separates: 1/2 sit at the same luminance, so do 3/4
     and 5/6. Inside a tier the pair is told apart by HUE on the axis a
     red-green dichromat keeps; across tiers by luminance. Strip hue entirely —
     a true monochrome reader — and the six collapse toward three. That is
     acceptable HERE and only here, because position is the primary channel and
     colour was never carrying the answer.

     ONE KNOB WITH ITS LIMIT, for whoever builds the unlit state. If the
     resting panel is this fill at reduced alpha over the ground, the weakest
     of the six crosses the 3:1 graphic floor at alpha 0.752: at 0.80 it is
     still 3.26:1, at 0.70 it is 2.73:1 and the resting grid stops being
     legible. The pair distances go with it — at alpha .60 the weakest
     red-green pair falls from .140 to .092. So the figures above describe the
     fill AT FULL, and a dimmed resting state is a different measurement. */

  /* --- 23d · THE FREEZE-FRAME (anticipation) ----------------------------- */
  /* This room is the tactical-memory court, stopped: the action runs, freezes,
     and the player predicts the continuation. It is a LIGHT object inside a
     dark room, which is §17 exactly, so it inherits §17's palette and §17's
     verdict — THE COURT IS FULL. Searched three times now: no ink clears 3:1
     against the ground, the paint, the lines, the trace, the discs and the
     ball at once. Nothing new is coined for this room, and that is the finding
     rather than an omission.
     The candidate continuations therefore separate by GEOMETRY and by STYLE,
     not by colour: they are drawn in --tk-court-symbol-ghost, dashed and
     numbered, which is the job that token was written for — "the reference
     shown BESIDE the answer". Which candidate is selected is a ring and a
     numeral, never a fill.
     The freeze itself is announced on the DARK chrome around the court, where
     §14 works and C-5h already reaches, and never on the court, where it would
     read as more court. */

}
