/* ============================================================================
 * DESIGN TOKENS — the decided winners, transcribed.
 *
 * Source of record: the /admin/design-review console (`DesignDecision` rows,
 * read off production 2026-07-30). The adoption manifest —
 * `app/models/design_tokens.rb` — records WHICH decision each token encodes and
 * ON WHOSE AUTHORITY, and `test/models/design_tokens_test.rb` derives the
 * expected value from the variant key Kevin actually clicked in `DesignCatalog`
 * and compares it against THIS FILE. So the two halves have independent authors:
 * edit a value here without moving the decision and the suite reds.
 *
 * `bin/rails design:verify_adoptions` runs the third leg against the live rows.
 *
 * WHAT THIS FILE IS NOT: a migration. Adding a token changes nothing on screen —
 * call sites move onto `var(--…)` in small stages, never big-bang (El Hefe,
 * 2026-07-30). A hex that a surface deliberately diverges from the token (the
 * softened `#f87171` miss accent, Kevin 2026-07-26) stays a literal, and stays
 * commented where it lives.
 * ========================================================================= */

:root {
  /* ---- Foundations · Color ----------------------------------------------
   * All five are Kevin's picks. `--color-ink` is the navy that doubles as the
   * brand/action colour: the recommendation here was #1e293b slate with navy
   * reserved for actions, and he chose the other way — one ink, not two. */
  --color-ink: #1e3a5f;
  --color-success: #15803d;
  --color-error: #dc2626;
  --color-warning: #d97706;
  --color-info: #06b6d4;

  /* ---- Explain / grammar accent -----------------------------------------
   * ⚖️ ADDED 2026-08-05 (El Hefe ruling, dispatch 19:5x 🎨), and it is NOT a
   * transcribed DesignDecision — it is a CONSOLIDATION. The hue already ships
   * as a hardcoded hex in 9 views (the examples-modal register pill, the
   * phrases register pill, the stars source pill, the reflexive-infinitives
   * label), so the ruling was "the hue is approved, no change — but this one is
   * a SYSTEM, not a view: add one token and use it rather than 6 more literals."
   *
   * ONE token, deliberately. The wash / edge / hover tints the construction
   * walkthrough needs are derived at the call site with color-mix() against
   * this value, so a second and third undecided hex do not enter the file that
   * exists to hold decided ones. Moving the 9 existing literals onto it is a
   * separate, staged pass — adding a token changes nothing on screen (see the
   * header), and this file's own rule is that call sites migrate in stages. */
  --color-explain: #6d28d9;

  /* ---- Foundations · Typography ------------------------------------------
   * Size + weight (+ colour where the decision names one). Deliberately NOT a
   * font shorthand: the drills that need to converge differ on size and weight
   * independently, and a shorthand would force line-height and family into a
   * decision nobody made. */
  --type-h1-size: 28px;
  --type-h1-weight: 700;
  --type-h1-color: #1e3a5f;

  --type-h2-size: 20px;
  --type-h2-weight: 600;

  /* The most visible drift in the app — five prompt specs across the drills. */
  --type-question-size: 20px;
  --type-question-weight: 500;

  --type-answer-size: 20px;
  --type-answer-weight: 700;
  --type-answer-color: #1e3a5f;

  /* ---- Foundations · Interaction — the tap floor --------------------------
   * ⚖️ ADDED 2026-08-08 (El Hefe ruling, dispatch 23:5x ②), and like
   * `--color-explain` above it is a CONSOLIDATION, not a transcribed
   * DesignDecision. It carries no `DesignTokens::ADOPTIONS` row because there is
   * no `DesignCatalog` element for it, so there is no winner key to derive it
   * from — exactly `--color-explain`'s situation, and the manifest's
   * "accounts for every reviewable element" test is indifferent either way.
   * Its authority is El Hefe's 2026-08-05 ③ ruling — "44px is ABSOLUTE on the
   * action path" — and its value is pinned by
   * `test/views/tap_floor_token_test.rb`, which types the 44 itself rather than
   * reading it back out of this file.
   *
   * 🔴 THIS PARAGRAPH FIRST SHIPPED CITING THE z-SCALE AS THE SAME CASE AND THAT
   * WAS FLATLY WRONG — `design-system-reviewer` caught it post-push and it is
   * corrected here rather than left standing. `component.overlay_zindex` IS a
   * reviewable element (`design_catalog.rb:1217`) and DOES have a manifest row
   * (`design_tokens.rb:86`, `winner: nil, authority: HEFE, derive: :rec`). So
   * the z-scale's real pattern is the opposite of what was written: a token
   * ratified by El Hefe rather than Kevin gets a row WITH `winner: nil`, which
   * is what keeps it showing on the console as still his to overturn. Left
   * uncorrected, the next agent adding a Hefe-authority token would have read
   * this comment and skipped a row the manifest wanted.
   *
   * 🔴 WHY THIS ONE IS A HARDER CONSTANT THAN A HUE, WHICH IS THE WHOLE
   * ARGUMENT FOR TOKENISING IT. A hex is a taste a surface may legitimately
   * diverge from (the softened `#f87171` miss accent stays a literal, see the
   * header). 44 is a claim about what a thumb can hit — a product decision, the
   * same one on every surface — so a per-view copy of it is not divergence, it
   * is drift. It ships today as ~66 sizing declarations across 32 files.
   *
   * ⚠️ AND ADDING IT CHANGED FIVE OF THOSE SIXTY-SIX, DELIBERATELY. Per this
   * file's own rule — "call sites move onto var(--…) in small stages, never
   * big-bang" — the migrated set is every instance of the ABSOLUTELY-POSITIONED
   * `::after` HIT-OVERLAY RECIPE, in application.css + flashcards. The seam is
   * the recipe, not a usage count: that shape exists only to deliver the floor,
   * so the number in it is never a per-surface taste. A `min-height: 44px`
   * sizing a real element is a call site and migrates in a later stage.
   *
   * ⚠️ THE FIRST WORDING HERE SAID "one rule serving many surfaces" AND THAT
   * OVERCLAIMED — corrected after `design-system-reviewer` counted the classes.
   * Three of the five are genuinely multi-surface (`.howto-pill` 8 views,
   * `.cj-chip` 3, `.vp-close` 3 incl. a shared partial); TWO ARE SINGLE-VIEW
   * (`.cj-card-custom-btn` → verb_drill/_card_grid only, `.vocab-card-conj-btn`
   * → flashcards/index only). They belong in this stage on the recipe test, not
   * the headcount one — and a later stager applying "many surfaces" literally
   * would have mis-scoped stage 2 off the old sentence.
   *
   * ▶️ CONCRETE STAGE-2 TARGETS, named so they are not lost in "~60 more":
   * `shared/_drill_quit.html.erb:52` (`.drill-quit`, a shared partial reaching 6
   * surfaces) and `shared/_examples_modal_styles.html.erb:206` (`.ex-tab`, 3
   * views, and its own comment already says "declared once, for the class").
   * Both size the element natively rather than via an overlay, which is why they
   * are outside THIS commit's recipe-scoped set rather than an oversight.
   *
   * 🔴 IT IS A FLOOR, SO EDITING IT DOWNWARD IS NOT A STYLE CHANGE. Every rule
   * in `TapFloorTokenTest::MIGRATED` resizes at once from this line — that is
   * the point, and it is also why the value is asserted against a hand-typed
   * 44 in a file with a different author than this one. */
  --tap-floor: 44px;

  /* ⚖️ THE OTHER HALF OF THE SAME RULING, ADDED 2026-08-08 (@hefe). The
   * 2026-08-05 ruling names TWO numbers and only one of them was ever written
   * down where a machine could read it:
   *
   *   "absolute on the action path — listening, drilling, navigating a lesson,
   *    answering — best-effort in dense reference tables, with ≥24px and NO
   *    OVERLAPPING HIT REGIONS as the non-negotiable floors under both"
   *
   * So `--tap-floor` above is the TARGET, which a dense browse surface is
   * explicitly allowed to miss, and this is the FLOOR, which nothing may miss.
   *
   * 🔴 THE ASYMMETRY IS WHY THIS TOKEN EXISTS, AND IT COST US THE CLASS BELOW.
   * Because only the 44 was tokenised, every guard, sweep and test we own asks
   * about the 44 — so a control that misses the 24 by seven pixels is reported
   * in exactly the same words as one that misses the 44 by nine, on a surface
   * where missing the 44 is PERMITTED. The permitted miss is the common case, so
   * the forbidden one reads as noise inside it. `.star-btn` rendered at 17×17 on
   * the vocab card back and 29×19 on the phrases card back — under the
   * non-negotiable floor, on a shared component, on two of the three surfaces
   * that render it — and `script/measure_tap_targets.mjs:294` had the number
   * written down, in a comment, as an illustration of a different point.
   *
   * ⚠️ IT IS A HIT-REGION FLOOR, NOT A SIZE FLOOR — the distinction is the whole
   * reason the fix is affordable. A 24px BOX on a dense card back would relayout
   * 577 cards; a 24px HIT REGION is an `::after` overlay that changes not one
   * rendered pixel. The gate is `script/measure_star_button_floor.mjs`, and it
   * asks the layout engine (`elementFromPoint` on the corners of the required
   * square) rather than reading a width, so it stays true whichever way a future
   * author reaches the floor.
   *
   * 🔴 AND IT IS A FLOOR, SO EDITING IT DOWNWARD IS NOT A STYLE CHANGE. The
   * value is pinned by `test/views/tap_floor_token_test.rb` and independently
   * re-typed by the probe, so lowering this line reds both rather than quietly
   * shrinking every hit region that reads it. */
  --tap-floor-min: 24px;

  /* ---- Components --------------------------------------------------------- */
  --radius-card: 12px;

  --btn-radius: 8px;
  --btn-padding: 10px 20px;

  --input-border-width: 1px;
  --input-radius: 10px;

  /* Height is fixed; the FILL is semantic on purpose — "the token for what it
   * measures". A bar measuring something other than success overrides
   * --progress-fill locally rather than hardcoding a second green. */
  --progress-h: 8px;
  --progress-fill: var(--color-success);

  --modal-overlay: rgba(15, 23, 42, .55);
  --modal-radius: 16px;

  --badge-radius: 999px;
  --badge-font-size: 10px;
  --badge-font-weight: 600;
  --badge-padding: 2px 8px;

  --ghost-bg: #fff;
  --ghost-fg: #475569;
  --ghost-border: 1px solid #e2e8f0;
  --ghost-bg-hover: #f1f5f9;

  /* ---- Overlay stacking — the 4-step scale --------------------------------
   * ⚖️ RATIFIED BY EL HEFE (dispatch 2026-07-30 08:1x), NOT by Kevin, and its
   * `winner` column on the console is deliberately left EMPTY so it keeps
   * showing as his to confirm or overturn. That column means a human chose and
   * there is no second authority column; code-adopted + console-open is honest
   * drift. See `DesignTokens::ADOPTIONS`.
   *
   * Measured before ratifying: 21 overlays across 10 distinct z values
   * (80·85·90·100·110·120·1000·9000·9100·9999), each chosen locally. The four
   * tiers cover all 21 with order preserved on the only pair that genuinely
   * shares a page — the verb drill's reference sheet (90 → --z-popover) above
   * its host dialog (85 → --z-overlay).
   *
   * 🔴 LOAD-BEARING AND INVISIBLE: collapsing bug-report (9100) and ask (9000)
   * into one --z-global makes them TIE, so which paints on top is decided by DOM
   * order alone. In `layouts/application.html.erb` `.bug-report-overlay` renders
   * AFTER `.ask-overlay`, both after `yield`, so the intended precedence
   * survives — but it survives on source order. Move either block and the tie
   * breaks the other way, silently.
   *
   * 🔴 --z-toast SITS ABOVE --z-blocking, AND THAT IS NOT A HOLE IN "nothing may
   * cover session expired". El Hefe amended the scope of his own rule to license
   * it (dispatch 2026-07-31 11:5x): that rule was written about surfaces which
   * can RECEIVE A CLICK, and all five toasts are `pointer-events: none` — a
   * measurement, not a description. A layer that cannot receive a click cannot
   * take one away from the lock-out's Log in button, so the only failure mode a
   * toast has left is INVISIBILITY, and invisibility is the silent one: a toast
   * painting over a dialog's edge shows up in any screenshot, while a toast the
   * learner never sees produces no symptom and no bug report. So the tier is
   * placed to keep toasts visible, and `pointer-events: none` is the premise the
   * placement rests on — pinned by ZStackingAuditTest (give any toast
   * `pointer-events: auto` and the inversions it licenses come straight back),
   * never by this comment. 1100 rather than 1001 so the step stays legible on a
   * 100/200/900/1000 scale.
   *
   * 🔴 AND THE PREMISE ABOVE WENT VOID ON 2026-08-07 — AMENDED, MEASURED ON
   * PROD, NOT ARGUED. `b112aa6` gave `.node-toast` an Undo button, and a button
   * inside a click-through toast has to be `pointer-events: auto` or it is
   * decorative. So one of the five toasts CAN receive a click, and the sentence
   * the placement rests on ("a layer that cannot receive a click cannot take one
   * away from the lock-out's Log in button") stopped being true of it.
   *
   * The pin this comment names FAILED, which is the part worth carrying:
   * `ZStackingAudit#non_interactive?` looked up the toast's OWN class name, and
   * the `auto` lives on `.node-toast-action` — a different name — so the licence
   * evaporated in silence. Fixed by ZStackingAudit::INTERACTIVE_DESCENDANTS,
   * which is closed by default so the NEXT one reds instead.
   *
   * Measured on production before choosing the cure
   * (script/measure_node_toast_over_modal.mjs, 390px, both arms): with the
   * bug-report modal OPEN, `elementFromPoint` at the Undo's own centre returned
   * `button.node-toast-action` — the toast's button was taking a click inside a
   * modal's box, live. The control arm (no modal) proves the probe could hit it
   * at all.
   *
   * ⚖️ THE CURE IS THE TIER, NOT THE PROPERTY. Taking `auto` off the Undo would
   * keep the tidy invariant and break the affordance. An ACTIONABLE toast simply
   * is not what --z-toast describes: it contends for input, so it obeys "nothing
   * may cover session expired" like every other surface that can. --z-toast-action
   * is that rung. At 850 it sits under all three layers that co-render with it
   * (--z-global 900, --z-blocking 1000) and above the page, so the three
   * inversions do not need a licence — they stop existing. Granting an exemption
   * would have left the live defect and written down that we meant it.
   *
   * ⚠️ THE COST, STATED: a plain `.node-toast` fired while a modal is open is
   * now hidden rather than painting over it, and invisibility is the silent
   * failure this very comment warns about. Accepted because the class is applied
   * at the CLASS level on purpose — a z that depended on whether THIS toast
   * happened to carry an action would be runtime state no static audit can read,
   * and going blind again is the failure that produced this amendment.
   *
   * ⚖️ --z-shell ADDED 2026-08-01 (El Hefe ruling, dispatch 13:5x), and it is the
   * one rung that goes DOWNWARD. @agent1's phone-only drill modal asked to land on
   * --z-overlay and the audit refused 8 ties for it (4 real pairs, doubled — see
   * the scanner note below). The ties were the symptom; the tier was the mistake.
   *
   * A HOST is not a feature dialog. `.ex-drill-shell.is-drill-modal` is not a peer of
   * `.explore-overlay` — it is the mobile PRESENTATION of the page's own primary
   * content, and explore, more-like-this, the conjugation reference, chat-about,
   * ask and bug-report all open FROM INSIDE it. Every one of them must paint over
   * it. On --z-overlay that precedence exists only as a tie broken by DOM order,
   * and `.explore-overlay` is injected at runtime by
   * `exercise_controller#_loadExplore` (`insertAdjacentHTML("beforeend")` on
   * `.ex-page`), so the order carrying it is an INSERTION order, not even a source
   * order somebody could read off a template. The failure it buys is silent and
   * phone-only: tap Explore inside a drill and the panel opens underneath the
   * drill you opened it from. A rung below --z-overlay makes that precedence
   * arithmetic instead of etiquette, and takes the tie count to zero.
   *
   * WHY 25, MEASURED RATHER THAN PICKED. The tier has a floor and a ceiling and
   * both are real. FLOOR: it must cover the topic-steps / filters / actions layer
   * it exists to replace — that furniture tops out at `.ex-cancel` (2) on
   * `exercises/show`, and tree-wide the highest non-tiered furniture below the
   * launchers is `.cj-verb-search-results` (30), a dropdown that SHOULD sit over a
   * host. CEILING: the 50-band, which is `.bug-report-link.is-corner` — the fixed
   * 🐛 launcher — and the burger's `.lt-nav-links`. 🔴 THE CEILING IS THE HALF THAT
   * MATTERS AND IT IS A PRODUCT RULE, NOT A NUMBER: putting the shell OVER the 🐛
   * would delete the bug-report affordance from the newest surface in the app, on
   * phones only, with a clean console — precisely the failure CLAUDE.md records
   * from the sore-spot card, where the 🐛 and ❓ were dead and nothing said so. The
   * shell sits UNDER the launcher so the existing relationship (🐛 floats over
   * every page) is PRESERVED rather than newly broken here. 25 clears the floor by
   * an order of magnitude and the ceiling by 2×.
   *
   * 📌 THE 8 TIES WERE 4, AND THE DOUBLING IS AN AUDIT DEFECT WORTH KNOWING BEFORE
   * IT BITES SOMEONE ELSE. `declarations` reads `sel.scan(/\.[-\w]+/)`, so a
   * COMPOUND selector registers a layer for EVERY class in it: `.ex-drill-shell.is-drill-modal`
   * became `.ex-drill-shell` AND a phantom `.is-drill-modal`, each paired against the same
   * four neighbours. The phantom is harmless here (both land on --z-shell and both
   * are strictly below everything they meet), but the name is not: `.is-drill-modal` is
   * generic enough that a second component adopting it would silently share one
   * audit identity with this one. Named, not fixed — the model change is
   * tree-wide (`.vocab-page.is-selecting` has the same shape) and does not belong
   * inside a ruling that unblocks a held feature. */
  --z-shell: 25;      /* full-viewport HOST surfaces that feature dialogs open ON TOP OF */
  --z-overlay: 100;   /* feature dialogs */
  --z-popover: 200;   /* reference sheets that open INSIDE a dialog */
  --z-global: 900;    /* ask / bug-report / celebration */
  --z-blocking: 1000; /* session expired — nothing may cover it */

  --z-toast-action: 850; /* transient confirmations that CAN take a click — under --z-global, see above */

  --z-toast: 1100;    /* transient confirmations — pointer-events:none, see above */
}
