/* ============================================================
   Episquire Design System — flat export for Episquire Web
   EPQ-DS-000 · REV F · generated 2026-07-27
   Source of truth: the Episquire Design System project (tokens/*.css).
   Do not hand-edit. Regenerate from the DS when it changes.

   REV F — type scale rebuilt on FIBONACCI (13 · 21 · 34 · 55 · 89) with weight
   tokens, and the DOC/REV/FIELD placard RETIRED. Migration for consumers:
   ·  Delete stamp() and its markup. Its status line becomes a notice band
      BELOW the nav; doc/rev values go in the footer.
   ·  Controls move to the body tier: nav links, buttons and inputs are 21px,
      not 13/15. Text you touch is not chrome.
   ·  Replace every font-weight literal with an --epq-w-* token. Nothing under
      34px above 600.

   REV E — absorbs Episquire Web's upstream (see guidelines/upstream-reply-2026-07-27.md):
   ·  --epq-cta, a fixed CTA role colour split from the page field, and
      --epq-on-field for reversed type on a field mark.
   ·  The page-load reveal, now opt-in via class="epq-reveal" on <html>, with
      no duplicated hexes (pigments come from --epq-src-*) and no white flash
      on the dark theme (it exposes from --epq-expose, the ground on cyanotype).
   ·  --epq-plate-shadow, so the plate flash can be cancelled on a WORD.
   ·  Nine components absorbed into episquire-components.css.

   REV D — accessibility pass:
   ·  A real type scale (--epq-t-*). Sizes come from roles, never px.
      13px floor; tracking loosens as size grows, not as it shrinks.
   ·  Muted ink darkened #5A5C50 -> #4A4C40 (4.68:1 -> 6.0:1).
   ·  Dark mode, as the cyanotype reversal. See THEME below.

   HOW TO USE IN Episquire Web
   1. Save as src/episquire.css (or public/episquire.css and <link> it).
   2. In each template's <style>, DELETE the duplicated :root token block,
      the *{box-sizing} reset, and the body/.doc/.wm/.stamp/nav rules that
      this file already ships. Keep only page-specific layout.
   3. Keep setting the page field the way you already do:
        :root{ --field: ${c.field} }
      The bridge at the end maps it onto --epq-field, so every DS helper
      (.epq-tint, .epq-zebra, .epq-hair-field, …) picks it up automatically.
   4. THE REVEAL is opt-in: add class="epq-reveal" to <html>. Then DELETE the
      appended reveal block from src/episquire.css — it is upstream now and no
      longer needs re-appending after a regeneration.
   5. DARK MODE. Ship epq-theme-toggle.js, place <epq-theme-toggle> in the
      stamp or nav, and put this in <head> BEFORE the first stylesheet:
        <script>try{var m=localStorage.getItem("epq-theme");if(m==="light"||
        m==="reverse")document.documentElement.setAttribute("data-epq-theme",m)}
        catch(e){}</script>
      Skip it and the page paints manila, then flips — a full-brightness
      flash delivered to the users who asked for the dark theme.
   6. Template font-sizes: replace every hard px value with its role
      (--epq-t-display / -statement / -title / -lede / -body / -detail /
      -label). Any leftover px size is how the sizes drifted apart before.
   ============================================================ */

@import url("https://fonts.googleapis.com/css2?family=Archivo:wdth,wght@62..125,400..900&family=IBM+Plex+Mono:wght@400;500;600;700&family=IBM+Plex+Sans:wght@400;500;600;700&display=swap");

/* ---------- TOKENS · COLOUR ---------- */

:root {
  /* ---- Source values ----
     The literals, and the ONLY place they appear. Never consume --epq-src-*
     directly in a component; use the live token below it. These exist so a
     value can be re-declared as a function of itself without a cycle. */
  --epq-src-ink:      #16180F;
  --epq-src-ink-70:   #4A4C40;
  --epq-src-manila:   #D9D6C7;
  --epq-src-vellum:   #EFEDE3;
  --epq-src-slate:    #3D5A6C;
  --epq-src-capture:  #FF5C00;
  --epq-src-explorer: #00B6E8;
  --epq-src-drift:    #E6007E;
  --epq-src-verify:   #C4F000;
  --epq-src-agents:   #6B4EFF;
  --epq-src-cy-ground: #0D2233;
  --epq-src-cy-panel:  #123048;
  --epq-src-cy-line:   #5E8AA6;
  --epq-src-cy-ink:    #DDE9F0;
  --epq-src-cy-ink-70: #8FAFC4;
  /* Dark-ground (-rev) fields are DESATURATED as well as lightened. The earlier
     revision only raised lightness for contrast, which held the WCAG budget but
     left the hues at full light-mode chroma — so on the cyanotype ground they
     vibrated (max chroma against min luminance is where saturated accents buzz
     and halo). Chroma is pulled ~22% in OKLCH with L and H held, which calms the
     harshness without shifting the hue or dropping below the audit's floor.
     Explorer and verify gained -rev variants in this pass so every field is
     treated the same on a dark ground; on the LIGHT theme they still resolve to
     the base pigments (see epq-site.css — their light-mode ink bar already
     passes, so nothing there changes). */
  --epq-src-capture-rev:  #F68147;
  --epq-src-explorer-rev: #4CB3DB;
  --epq-src-verify-rev:   #C9ED61;
  --epq-src-drift-rev:    #EA69A4;
  --epq-src-agents-rev:   #8A80D8;
  /* What sits ON a field mark. Ink by default; a view whose field is agents or
     drift sets this to #fff. Named as a source for the same reason the pigments
     are — the reveal re-declares the live token as a function of it. */
  --epq-src-on-field: var(--epq-src-ink);

  /* ---- Substrate (the factory floor) ---- */
  --epq-ink:      var(--epq-src-ink);   /* text + ALL rules */
  --epq-ink-70:   var(--epq-src-ink-70);   /* captions, meta, secondary · 6.0:1 on manila, 6.9:1 on vellum
                                 (was #5A5C50 = 4.68:1 — technically AA, but it was
                                 carrying every caption and label in the system) */
  --epq-manila:   var(--epq-src-manila);   /* page ground */
  --epq-vellum:   var(--epq-src-vellum);   /* document field / cards */
  --epq-slate:    var(--epq-src-slate);   /* engineering grid, focus ring */

  /* ---- Module field colours (ONE per view) ---- */
  --epq-f-capture:  var(--epq-src-capture); /* EPQ-01 · ink on field   · 5.8:1  AA   */
  --epq-f-explorer: var(--epq-src-explorer); /* EPQ-02 · ink on field   · 7.6:1  AAA  */
  --epq-f-drift:    var(--epq-src-drift); /* EPQ-03 · white on field · 4.5:1  AA large only —
                                  sits ON the 4.5 threshold, so it is labelled
                                  large-only deliberately: do not promote it. */
  --epq-f-verify:   var(--epq-src-verify); /* EPQ-04 · ink on field   · 13.5:1 AAA  */
  --epq-f-agents:   var(--epq-src-agents); /* EPQ-05 · white on field · 5.1:1  AA   */

  /* ---- Cyanotype reversal (dark ground — NOT inverted values) ---- */
  --epq-cy-ground:    var(--epq-src-cy-ground); /* wet-process blue ground */
  --epq-cy-panel:     var(--epq-src-cy-panel); /* raised panel on ground */
  --epq-cy-line:      var(--epq-src-cy-line); /* grid + hairlines on ground. A mid-tone: neither
                                  end of this palette clears AA on it (cy-ink 2.7:1,
                                  cy-ground 4.4:1, white 3.7:1). Text on it must be
                                  --epq-ink, 4.8:1 — but prefer not to set text on a
                                  line colour at all. */
  --epq-cy-ink:       var(--epq-src-cy-ink); /* text on ground · 13.1:1 */
  --epq-cy-ink-70:    var(--epq-src-cy-ink-70); /* captions/meta on ground · 7.0:1.
                                  NOT --epq-cy-line: that is a 4.35:1 hairline
                                  colour and fails AA as small text. Lines and
                                  muted text are different jobs on dark. */
  --epq-f-agents-rev: var(--epq-src-agents-rev); /* Agents re-specified: #6B4EFF is 2.5:1 on the ground.
                                    Desaturated to #8A80D8 it is 4.76:1 there — clears the
                                    4.5 the clause number needs, but knife-edge as body: don't
                                    set running text on it. */
  --epq-f-drift-rev:  var(--epq-src-drift-rev); /* Drift re-specified: magenta is 3.6:1 on the ground;
                                    #EA69A4 is 5.45:1 there. */
  --epq-f-explorer-rev: var(--epq-src-explorer-rev); /* Explorer, desaturated for the ground: 6.79:1 on it,
                                    5.69:1 on the panel. Base #00B6E8 already cleared the ground,
                                    so this is a chroma calm, not a contrast fix. */
  --epq-f-verify-rev:   var(--epq-src-verify-rev); /* Verify, desaturated for the ground: 12.19:1 on it. The
                                    lime was the loudest accent on dark; this is the biggest
                                    chroma cut and still AAA. */
  --epq-f-capture-rev: var(--epq-src-capture-rev); /* Capture re-specified: #FF5C00 is 4.4:1 on the raised
                                   panel, and the clause NUMBER — the system's primary
                                   anti-monochrome carry — sits on that panel at 18px/600,
                                   which is not WCAG large text (that needs 24px normal or
                                   18.66px bold), so 4.5 is required and 4.4 misses. Desaturated
                                   to #F68147 it is 5.28:1 on the panel and 6.30:1 on the ground.
                                   Ink on it is 6.96:1, so the CTA still works. */

  /* ---- Semantic aliases ---- */
  --epq-text:         var(--epq-ink);
  --epq-text-muted:   var(--epq-ink-70);
  --epq-surface-page: var(--epq-manila);
  --epq-surface-card: var(--epq-vellum);
  --epq-field:        var(--epq-f-capture); /* default view field; override per view */

  /* ---- CTA: a ROLE colour, not a field (upstream §1, founder 2026-07-27) ----
     Capture orange on every pressable control, on every page, and on nothing
     else. Exempt from one-field-per-view for the same reason the plates are:
     it is fixed across every view, so it cannot carry view identity. The
     moment it starts varying per page it has become a second field.
     The cost is recorded plainly: capture is SPENT. No view can take #FF5C00
     as its field without colliding with every button, so EPQ-01 loses its
     field colour and the palette drops from five module fields to four. */
  --epq-cta:          var(--epq-src-capture);
  /* What sits on the CTA. Sourced and revealed like every other foreground —
     a literal ink label on a fill that mixes to the exposure colour is
     invisible for the whole hold. */
  --epq-src-on-cta:   var(--epq-src-ink);
  --epq-on-cta:       var(--epq-src-on-cta);

  /* ---- What sits ON the field ----
     REV C specified ink-or-white per field by contrast ratio but carried the
     choice nowhere, so templates assumed ink — safe only while every field in
     use was capture or explorer. Ink measures 3.5:1 on agents violet, which
     fails AA for the stamp's status cell and the roundel's ring type.
     Default is ink; a view whose field needs white sets --epq-on-field to
     #fff (agents and drift). The reveal (reveal.css) re-declares this so it
     is ink at reveal 0 and the named colour at reveal 1 — line art first,
     reversed type after, rather than white-on-white for the first 1.36s. */
  --epq-on-field:     var(--epq-src-on-field);

  /* ---- What sits ON each state fill ----
     A Tag encoding state paints its own fill, so it needs its own on-colour —
     and that colour is NOT a constant. Drift and agents are re-specified
     LIGHTER on the cyanotype ground so the fill reads against it, and
     lightening a fill inverts the contrast of whatever sits on top: white on
     #E6007E is 4.5:1 but white on #FF4DA6 is only 3.1:1, while ink on it is
     5.9:1. So the on-colour flips with the theme (see theme.css). Never
     hardcode #fff on a state fill — that is theme-blind and fails on the
     ground exactly where the tag is doing load-bearing work.

     Sourced, like every other pigment, so reveal.css can wrap them: a
     foreground and its background must sit on the SAME SIDE of the reveal. A
     raw literal here converges with its own fill during the unexposed hold —
     white-on-white for 1.12s, which is the hazard --epq-on-field already
     documents. theme.css remaps the SOURCE, never the live token, so the
     reveal's higher specificity cannot revert it. */
  --epq-src-on-f-capture:  var(--epq-src-ink);
  --epq-src-on-f-explorer: var(--epq-src-ink);
  --epq-src-on-f-verify:   var(--epq-src-ink);
  --epq-src-on-f-drift:    #FFFFFF; /* 4.5:1 on #E6007E */
  --epq-src-on-f-agents:   #FFFFFF; /* 5.1:1 on #6B4EFF */

  --epq-on-f-capture:  var(--epq-src-on-f-capture);
  --epq-on-f-explorer: var(--epq-src-on-f-explorer);
  --epq-on-f-verify:   var(--epq-src-on-f-verify);
  --epq-on-f-drift:    var(--epq-src-on-f-drift);
  --epq-on-f-agents:   var(--epq-src-on-f-agents);
  --epq-focus-ring:   var(--epq-slate);
  --epq-line:         var(--epq-ink);    /* every rule, hairline + hard shadow */
  --epq-grid-line:    var(--epq-slate);  /* the engineering grid under the ground */

  /* ---- Press plates (MOTION ONLY — see foundations.css) ----
     Not fields, not decoration: three semi-transparent process plates that
     splay out of register for a few frames when an element is pressed or
     lands. The reference is offset-litho MISREGISTRATION, not lens
     chromatic aberration — same paperpunk press vocabulary as the hard ink
     shadow. Fixed CMY assignment; never re-map these to the view's field. */
  --epq-plate-c: color-mix(in srgb, var(--epq-f-explorer) 62%, transparent);
  --epq-plate-m: color-mix(in srgb, var(--epq-f-drift)    62%, transparent);
  --epq-plate-y: color-mix(in srgb, var(--epq-f-verify)   62%, transparent);

  --epq-plate-flash: 180ms; /* @kind other */ /* press / click reward */
  --epq-plate-drift: 320ms; /* @kind other */ /* element landing in an animation */
}

/* ---- Field scopes ------------------------------------------------------
   Set a view's field by scope, not by hand: the readme has referred to
   .epq-field-* since REV C but the system never shipped them, so every
   consumer re-declared --epq-field itself and each one also had to remember
   whether ink or white goes on top. These carry both, so the pairing cannot
   drift apart. Data attributes rather than classes so they register as theme
   scopes; the export also ships .epq-field-* aliases for existing markup.
   Ink on capture/explorer/verify; white on drift/agents (ink is 3.5:1 on
   violet and fails AA). */
[data-epq-field="capture"]  { --epq-field: var(--epq-f-capture);  --epq-src-on-field: var(--epq-src-ink); }
[data-epq-field="explorer"] { --epq-field: var(--epq-f-explorer); --epq-src-on-field: var(--epq-src-ink); }
[data-epq-field="verify"]   { --epq-field: var(--epq-f-verify);   --epq-src-on-field: var(--epq-src-ink); }
[data-epq-field="drift"]    { --epq-field: var(--epq-f-drift);    --epq-src-on-field: #FFFFFF; }
[data-epq-field="agents"]   { --epq-field: var(--epq-f-agents);   --epq-src-on-field: #FFFFFF; }

/* ---- Field tints (body-level carry; same hue, lower energy) ----
   Declared on `*`, NOT :root, deliberately. A custom property that mixes
   var(--epq-field) is substituted at computed-value time on the element that
   declares it — declaring these once on :root would freeze them to the root's
   field (Capture orange) and a `.epq-field-verify` descendant could never
   change them, silently putting two field colours in one view. Declaring per
   element makes each one recompute against its own INHERITED --epq-field.
   Ink stays AAA on all three. */
* {
  --epq-field-tint: color-mix(in srgb, var(--epq-field) 18%, var(--epq-vellum)); /* callout / panel ground */
  --epq-field-wash: color-mix(in srgb, var(--epq-field) 8%,  var(--epq-vellum)); /* zebra rows, wide bands */
  --epq-field-edge: color-mix(in srgb, var(--epq-field) 55%, var(--epq-ink));    /* hairline accents on vellum */
}

/* Class aliases for the field scopes, for markup that predates the data
   attribute. Identical pairings; pick one form per project. */
.epq-field-capture  { --epq-field: var(--epq-f-capture);  --epq-src-on-field: var(--epq-src-ink); }
.epq-field-explorer { --epq-field: var(--epq-f-explorer); --epq-src-on-field: var(--epq-src-ink); }
.epq-field-verify   { --epq-field: var(--epq-f-verify);   --epq-src-on-field: var(--epq-src-ink); }
.epq-field-drift    { --epq-field: var(--epq-f-drift);    --epq-src-on-field: #FFFFFF; }
.epq-field-agents   { --epq-field: var(--epq-f-agents);   --epq-src-on-field: #FFFFFF; }

/* ---------- TOKENS · TYPE ---------- */

:root {
  --epq-font-display: "Archivo", sans-serif;
  --epq-font-body:    "IBM Plex Sans", system-ui, sans-serif;
  --epq-font-mono:    "IBM Plex Mono", monospace;

  /* ---- The five sizes --------------------------------------------------
     REV: body and title travel — 17→21 and 21→34 across the 400–1200px
     viewport range — so phones read the compressed register and desktop is
     unchanged. Rationale and the departure this implies live with the fluid
     ends below. */
  --epq-t-label:   13px; /* @kind font */ /* labels, eyebrows, tags, captions, cells */
  --epq-t-body:    clamp(17px, 15px + 0.5vw, 21px); /* @kind font */ /* running copy */
  --epq-t-title:   clamp(21px, 14.5px + 1.625vw, 34px); /* @kind font */ /* section titles, taglines */
  --epq-t-stmt:    55px; /* @kind font */ /* in-body statement (ceiling; see --epq-t-statement) */
  --epq-t-hero:    89px; /* @kind font */ /* hero display ceiling, one per view */

  /* ---- Roles ----------------------------------------------------------
     Every role resolves to one of the five. Components reference roles, not
     sizes, so a role can be re-pointed without touching a component. */
  --epq-t-detail:  var(--epq-t-label); /* @kind font */ /* sans captions, meta */
  --epq-t-control: var(--epq-t-body); /* @kind font */ /* buttons, inputs, nav links */
  --epq-t-lede:    var(--epq-t-title); /* @kind font */ /* tagline, standfirst */
  --epq-t-clause:  var(--epq-t-body); /* @kind font */ /* clause header (uppercase + tracked) */

  /* Fluid ends. REV: body and title are now fluid too, and the scale rests
     on a compressed register below ~400px viewport — a deliberate departure
     from the old steps-only-at-rest rule (founder's call; see
     decisions/2026-07-31-mobile-fluid-compressed-type.md). The mobile rest
     points are the geometric means of their Fibonacci neighbours
     (√(13·21)≈17, √(21·34)≈27), so the compressed register keeps the scale's
     ratio even though its members sit between the desktop steps. Label never
     shrinks — it already rests on the 13px floor. The hero is sized in cqw
     and needs container-type: inline-size on its column: a hero's headline
     has viewport-minus-sidebar to work in, and vw overflows it.
     FILED UPSTREAM. */
  --epq-t-display:   clamp(28px, 9cqw, 89px); /* @kind font */ /* hero H1; ~32px in a 358px column */
  --epq-t-statement: clamp(27px, 5vw, 55px); /* @kind font */ /* big statement */

  /* THE ONE SANCTIONED EXCEPTION: RoleRoll self-scales to fit its column, so
     its words land between steps (an em fraction of the ceiling, not a step).
     That is the fitting mechanism, not a size choice — the ceiling it scales
     DOWN from is still a scale value. Nothing else may sit between steps. */

  /* Legibility floor. Nothing in this system sets a size below this — not
     tags, not table headers, not legal lines, not SVG labels. It equals the
     smallest step, so "smaller" is not an option a designer has. */
  --epq-t-min:       13px; /* @kind font */

  /* ---- Weights --------------------------------------------------------
     Four, and the rule is that legibility beats emphasis. Below 34px nothing
     exceeds --epq-w-label: heavier strokes at label size close the counters
     and cost more legibility than the emphasis is worth. */
  --epq-w-body:    400; /* @kind font */ /* running copy */
  --epq-w-medium:  500; /* @kind font */ /* emphasis inside body copy */
  --epq-w-label:   600; /* @kind font */ /* mono labels, controls, table heads */
  --epq-w-title:   700; /* @kind font */ /* titles and statements, 34px and up */
  --epq-w-hero:    800; /* @kind font */ /* the hero only */

  /* Archivo variable-font settings. wdth is load-bearing; wght tracks the
     weight tokens above. */
  --epq-display-vf: "wdth" 118, "wght" 800; /* @kind font */ /* hero H1, one per view */
  --epq-big-vf:     "wdth" 108, "wght" 700; /* @kind font */ /* in-body statement */
  --epq-clause-vf:  "wdth" 100, "wght" 600; /* @kind font */ /* clause / section headers */
  --epq-wm-vf:      "wdth" 112, "wght" 800; /* @kind font */ /* wordmark */
  --epq-btn-vf:     "wdth" 105, "wght" 600; /* @kind font */ /* buttons */

  /* ---- Tracking ----
     Tightens as size grows, loosens as size shrinks — to a point. Above
     ~.12em small uppercase mono stops reading as words and starts reading as
     separated glyphs, so that is the ceiling at label size. Reduced from REV D
     because the labels also got heavier duty: they are the only 13px text left. */
  --epq-track-display: -0.02em;
  --epq-track-clause:  0.12em;
  --epq-track-eyebrow: 0.12em;
  --epq-track-label:   0.08em;
  --epq-track-wm:      -0.012em;

  /* ---- Line-heights ----
     Body is 1.5 at 21px: longer lines need proportionally less leading than
     short ones, so the 1.55 that suited 17px would open the block up too far. */
  --epq-lh-display: 0.9; /* @kind font */
  --epq-lh-tight:   1.12; /* @kind font */ /* titles, statements */
  --epq-lh-lede:    1.25; /* @kind font */
  --epq-lh-body:    1.5; /* @kind font */
}

/* ---------- TOKENS · STRUCTURE + MOTION ---------- */

:root {
  /* ---- Grid ---- */
  --epq-grid-size: 48px; /* engineering grid under the page ground */

  /* ---- Motion (STEPPED, never eased) ---- */
  --epq-press:  transform .06s steps(2), box-shadow .06s steps(2); /* @kind other */

  /* ---- Ambient / attractor motion (upstream §3, confirmed in REV E) ----
     Binding rule 4 says stepped, never eased, and grants NO carve-out for
     ambient motion. The site read that literally — its figure play button
     "breathes" by holding out, HARD-CUTTING to a 2px depress, holding, and
     hard-cutting back. The founder asked for "a light breathing effect"; an
     eased pulse was the obvious reading and was rejected on the rule.
     REV E confirms the literal reading and says so here, because until now the
     ABSENCE of a carve-out was doing load-bearing work by accident:
     ·  Ambient motion is stepped like everything else. Paired keyframe stops
        (0%,46% / 50%,96%) hold each value constant so the only movement is the
        cut between them, and steps(1) makes those cuts hard.
     ·  Transform and shadow move TOGETHER (+2px against 5px→3px) so the far
        edge stays put and it reads as a press, not a drift.
     ·  It stops on :hover, :focus-visible and :active. Once the reader is on
        the control the attractor has done its job, and leaving it running
        fights the real press animation for the same two properties. */
  --epq-breathe: 3200ms; /* @kind other */ /* ambient attractor loop */

  /* ---- Radius ---- */
  --epq-radius: 0; /* square everywhere. no exception. */
}

/* ---- Rules (TWO weights only) + the hard shadow -------------------------
   Declared on `*`, NOT :root, for exactly the reason the field tints are
   (see colors.css): a var() inside a custom property is substituted at
   computed-value time on the element that DECLARES it. Defining these once
   on :root would freeze all three to the root's --epq-line, and a
   [data-epq-theme="reverse"] subtree could never redraw its rules in
   cyanotype line blue — every border in the dark theme would be ink on a
   near-ink ground, i.e. invisible. Declaring per element makes each one
   recompute against its own INHERITED --epq-line. */
* {
  --epq-rule:   2px solid var(--epq-line); /* structural divisions, containers, buttons */
  --epq-hair:   1px solid var(--epq-line); /* internal cells, table rows, tags */
  /* The resting shadow the plate keyframes carry. Separate from --epq-shadow
     on purpose: it is the one shadow a consumer legitimately needs to CANCEL.
     epq-misregister runs with `both` fill and carries this in every keyframe,
     which is right for a panel (the plates splay, the panel keeps the hard
     shadow it already had) and wrong for a WORD — each column ends up with a
     permanent 5px black rectangle behind it and a headline reads as a stack of
     boxes. Setting --epq-plate-shadow: 0 0 0 0 transparent on the plate box
     resolves that fourth shadow away at every frame, so the animation keeps
     its timing, offsets and steps() from the system and only the CMY plates
     paint. (RoleRoll does exactly this — upstream §6 patch 3. An earlier REV D
     pass inlined the literal here and silently made that cancellation inert,
     which is why it is its own token now.) */
  --epq-plate-shadow: 5px 5px 0 0 var(--epq-line); /* @kind other */

  --epq-shadow: 5px 5px 0 0 var(--epq-line); /* hard offset, zero blur.
     CAUTION: write `box-shadow: 5px 5px 0 0 var(--epq-line)` at use sites
     rather than `box-shadow: var(--epq-shadow)`. Two var hops through a
     property declared on `*` do not reliably re-resolve — the shorthand keeps
     its first computed value, and the light theme rendered a cyanotype-blue
     shadow until an unrelated restyle forced it. This token stays for
     authoring clarity; the one-hop form is what ships. */
}

/* Global reset the system depends on */
*, *::before, *::after { box-sizing: border-box; border-radius: 0 !important; }

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}

/* ---- Plate misregistration (MOTION ONLY) ----------------------------------
   Three process plates slip out of register for a few frames, then snap back.
   It is a press artefact, not a glow: zero blur, hard offsets, stepped — the
   same rules as --epq-shadow. Discipline:
   · Motion only. The plates are at 0,0 at rest and at 0,0 when the motion
     ends. A permanently-offset plate is decoration and is out of spec.
   · Under ~340ms. It is a flash that rewards a click, not an effect to watch.
   · Exempt from "one field per view": the plates are a fixed CMY triad, not
     the view's field, and they never persist long enough to read as one.
   · Never behind body text, tables, or form inputs — only pressable controls
     and whole blocks that move. Never on the cyanotype ground (the plates
     read as a screen error on dark).
   · On a WORD rather than a block, cancel the resting shadow with
     --epq-plate-shadow: 0 0 0 0 transparent. See that token above.
   · Reduced motion kills it entirely (rule above), which is the correct
     degradation: the element still presses, it just prints in register. */
@keyframes epq-press-plates {
  0%   { box-shadow: 0 0 0 0 var(--epq-plate-c), 0 0 0 0 var(--epq-plate-m), 0 0 0 0 var(--epq-plate-y), 5px 5px 0 0 var(--epq-ink); }
  30%  { box-shadow: -7px 5px 0 0 var(--epq-plate-c), 7px -4px 0 0 var(--epq-plate-m), 3px 8px 0 0 var(--epq-plate-y), 2px 2px 0 0 var(--epq-line); }
  65%  { box-shadow: -3px 2px 0 0 var(--epq-plate-c), 3px -2px 0 0 var(--epq-plate-m), 1px 3px 0 0 var(--epq-plate-y), 0 0 0 0 var(--epq-line); }
  100% { box-shadow: 0 0 0 0 var(--epq-plate-c), 0 0 0 0 var(--epq-plate-m), 0 0 0 0 var(--epq-plate-y), 0 0 0 0 var(--epq-line); }
}
@keyframes epq-misregister {
  0%   { box-shadow: 0 0 0 0 var(--epq-plate-c), 0 0 0 0 var(--epq-plate-m), 0 0 0 0 var(--epq-plate-y), var(--epq-plate-shadow); }
  25%  { box-shadow: -10px 7px 0 0 var(--epq-plate-c), 10px -6px 0 0 var(--epq-plate-m), 4px 11px 0 0 var(--epq-plate-y), var(--epq-plate-shadow); }
  60%  { box-shadow: -4px 3px 0 0 var(--epq-plate-c), 4px -2px 0 0 var(--epq-plate-m), 2px 4px 0 0 var(--epq-plate-y), var(--epq-plate-shadow); }
  100% { box-shadow: 0 0 0 0 var(--epq-plate-c), 0 0 0 0 var(--epq-plate-m), 0 0 0 0 var(--epq-plate-y), var(--epq-plate-shadow); }
}

/* Pressable control: plates splay while the ink shadow collapses 5→2→0.
   Pair with the usual press transform; the animation owns box-shadow only. */
.epq-plate-press:active {
  animation: epq-press-plates var(--epq-plate-flash) steps(3) 1 both;
  transform: translate(5px, 5px);
}
/* A block landing in an animation: bigger slip, plates trail the move.
   Retrigger from JS by toggling the class (or [data-plates]) off/on. */
.epq-plate-enter,
[data-plates="fire"] {
  animation: epq-misregister var(--epq-plate-drift) steps(3) 1 both;
}

/* Page ground with engineering grid under a 93% veil of the ground colour.
   Apply to a top-level app shell. Every value is a semantic alias, so the
   same class is the manila floor in light and the cyanotype bath in reverse. */
.epq-ground {
  position: relative;
  background: var(--epq-surface-page);
  color: var(--epq-text);
  font-family: var(--epq-font-body);
  font-size: var(--epq-t-body);
  line-height: var(--epq-lh-body);
  background-image:
    linear-gradient(var(--epq-grid-line) 1px, transparent 1px),
    linear-gradient(90deg, var(--epq-grid-line) 1px, transparent 1px);
  background-size: var(--epq-grid-size) var(--epq-grid-size);
}
.epq-ground::before {
  content: "";
  position: fixed;
  inset: 0;
  background: var(--epq-surface-page);
  opacity: .93;
  pointer-events: none;
  z-index: 0;
}

/* ---- Field-carry helpers (binding rule #3: carry the field below the fold).
   These mix at the USE SITE, so they always resolve against the field colour
   inherited at that point in the tree - safe inside any .epq-field-* scope. ---- */
.epq-tint { background: var(--epq-field-tint); }
.epq-wash { background: var(--epq-field-wash); }
.epq-zebra > tbody > tr:nth-child(odd) > * { background: var(--epq-field-wash); }
.epq-field-rule { border-top: 2px solid var(--epq-field); }
/* The hairline that belongs to a tinted region. Field-edge is the ONLY
   sanctioned third line colour, and only at 1px inside a tinted/washed
   surface - a pure-ink hairline there reads as a stray rule from the
   neutral system. Never structural (2px), never on plain vellum/manila,
   never text or fill.
   Declared as the full border-top shorthand on purpose: a bare
   border-color is a no-op without a border-style and loses to any later
   border/border-top shorthand a consumer writes. */
.epq-hair-field { border-top: 1px solid var(--epq-field-edge); }

/* ---------- THEME SCOPES (dark mode = cyanotype) ---------- */

[data-epq-theme="reverse"] {
  --epq-text:         var(--epq-cy-ink);
  --epq-text-muted:   var(--epq-cy-ink-70);
  --epq-surface-page: var(--epq-cy-ground);
  --epq-surface-card: var(--epq-cy-panel);
  --epq-line:         var(--epq-cy-line);
  --epq-grid-line:    var(--epq-cy-line);
  --epq-focus-ring:   var(--epq-cy-ink);
  /* All five fields are re-specified on the ground (see colors.css). Three were
     originally remapped for contrast — magenta was 3.6:1 there, violet 2.5:1,
     and capture 4.4:1 on the raised panel, below the size that would let the
     clause number at 18px/600 pass. Explorer and verify already cleared the
     ground but carried full light-mode chroma onto it, so they are remapped here
     too — for desaturation, not contrast. All five are aliased onto the base
     names so a view written as .epq-field-drift needs no per-theme markup. */
  --epq-f-capture:    var(--epq-f-capture-rev);
  --epq-f-explorer:   var(--epq-f-explorer-rev);
  --epq-f-verify:     var(--epq-f-verify-rev);
  --epq-f-drift:      var(--epq-f-drift-rev);
  --epq-f-agents:     var(--epq-f-agents-rev);
  /* And the on-colours INVERT with them. The -rev variants are lighter by
     design — that is how they read against the ground — so white-on-fill gets
     worse, not better: white drops to 3.1:1 on #FF4DA6 and 3.5:1 on #8B72FF,
     while ink measures 5.9:1 and 5.1:1. Any -rev variant chosen for
     fill-vs-ground legibility inverts whatever sits on that fill.
     Remapped at the SOURCE, not the live token: reveal.css re-declares the
     live --epq-on-f-* at (0,1,1) and would outrank this scope at (0,1,0).

     EPQ-WEB PATCH — the export closes this comment after "on that fill." and
     then leaves the next two lines as bare text terminated by a stray `*/`,
     inside the [data-epq-theme="reverse"] block. CSS error recovery consumes
     a bad declaration up to the next semicolon, which swallowed the
     `--epq-src-on-cta` line below it. Harmless as shipped — line 149 already
     sets that token to the same `var(--epq-src-ink)` — but it would silently
     eat any future declaration written in that slot. Fixed here by closing
     the comment once, at the end. Reported upstream; do not re-apply from a
     regeneration without checking it is fixed at the generator. */
  /* Capture is re-specified lighter here too, so ink stays correct on it. */
  --epq-src-on-cta:       var(--epq-src-ink);
  --epq-src-on-f-drift:   var(--epq-src-ink);
  --epq-src-on-f-agents:  var(--epq-src-ink);
  color-scheme: dark;
}

/* ---- Field tints on the ground -----------------------------------------
   Re-declared for the same computed-value reason as the base tints (see
   colors.css): the mix has to resolve against the CARD surface in scope, and
   the base rule mixes against vellum. Selector is `[attr] *` (0,1,0), which
   beats the base `*` (0,0,0); the scope element itself is listed separately
   because `[attr] *` does not match it. Ratios are higher on dark — a
   pigment at 18% over #123048 is barely a shift, so the panel would read as
   untinted and the field would stop carrying below the fold (rule #3). */
[data-epq-theme="reverse"],
[data-epq-theme="reverse"] * {
  --epq-field-tint: color-mix(in srgb, var(--epq-field) 26%, var(--epq-cy-panel));
  --epq-field-wash: color-mix(in srgb, var(--epq-field) 12%, var(--epq-cy-panel));
  --epq-field-edge: color-mix(in srgb, var(--epq-field) 65%, var(--epq-cy-ink));
}

/* ---- Long-read compensation on the ground ------------------------------
   Light type on a dark ground optically gains weight — strokes bloom into
   the surrounding dark and counters close up, which is most of why users
   report dark themes as "blurry" on long text. The fix is not a smaller
   size: it is less weight and slightly more air. Body copy loses ~30 units
   of weight and gains .006em, and that is the whole treatment. Headings are
   left alone; they are short, and thinning them would cost the hierarchy
   the type scale just established.

   Declared on `body` and left to INHERIT, not listed as `p, li, td` — those
   selectors are (0,1,1) and outrank a single class, so they stripped the
   tracking off every .eyebrow, .lede and .fine on the ground. Inheritance
   gives the same reach and loses to any element that sets its own. */
[data-epq-theme="reverse"] body {
  /* 390, not 370: body is 21px now and larger type carries less of the
     light-on-dark bloom penalty, so the REV D cut over-thinned it. */
  font-weight: 390;
  letter-spacing: .006em;
  -webkit-font-smoothing: antialiased;
}

/* ---- Ink bars on the ground -------------------------------------------
   An ink bar reads because it is DARKER THAN THE DOCUMENT SURFACE it divides.
   That logic transfers to the cyanotype ground unchanged — documents sit on
   the panel (--epq-surface-card is --epq-cy-panel), so the bar takes the bath
   below it. It is not "the raised panel": an earlier version said that and
   substituted --epq-cy-panel, which IS the surface the bar sits on, so the
   band vanished into the page and was marked only by its 2px rule. The clause
   bar is this system's primary structural divider and the host of its primary
   anti-monochrome carry; it has to be a band in both themes or the two stop
   carrying the same structure, which is the one thing a theme scope exists to
   preserve. Handled here rather than in each component so the components stay
   theme-agnostic.

   `.epq-ink-bar` is the marker class and the SINGLE enumeration point: put it
   on anything that paints itself on --epq-ink and this treatment follows. The
   named classes after it are legacy aliases for markup that predates the
   marker. This exists because the previous version listed only
   .epq-clause-bar, so .epq-notice — explicitly an ink bar, and documented as
   getting this treatment — stayed a near-black slab belonging to no surface on
   the dark theme, while the site's own equivalent was covered. Enumerating by
   hand means a new bar has to be remembered; a marker class cannot be missed.
   The site's band tabs are aliased here too. */
[data-epq-theme="reverse"] .epq-ink-bar,
[data-epq-theme="reverse"] .epq-clause-bar,
[data-epq-theme="reverse"] .epq-notice,
[data-epq-theme="reverse"] .rec-tab,
[data-epq-theme="reverse"] .br-tab {
  background: var(--epq-cy-ground);
  color: var(--epq-cy-ink);
  border-top: 2px solid var(--epq-field);
}
/* The band tabs are small chips, not bars — they take the panel but not the
   field rule. */
[data-epq-theme="reverse"] .rec-tab,
[data-epq-theme="reverse"] .br-tab { border-top: 0; }

/* The plates are banned on the ground (foundations.css) — they read as a
   screen fault rather than a press artefact once there is no white paper for
   them to slip against. Enforced here, not just documented there. */
[data-epq-theme="reverse"] .epq-plate-press:active,
[data-epq-theme="reverse"] .epq-plate-enter,
[data-epq-theme="reverse"] [data-plates="fire"] {
  animation: none;
}

/* The switch itself must not animate. A crossfade between two grounds spends
   its whole duration in a low-contrast mud state — unreadable, and for the
   light-sensitive users this toggle exists for, worse than either end. The
   toggle sets this attribute for two frames while it flips, so the
   suppression lasts only for the change and normal press transitions
   survive. It is also the honest behaviour for a system whose motion is
   stepped: the press changes state in two steps, so should the substrate. */
[data-epq-theme-switching] *,
[data-epq-theme-switching] *::before,
[data-epq-theme-switching] *::after {
  transition: none !important;
  animation: none !important;
}

/* ---------- PAGE-LOAD REVEAL (opt-in: class="epq-reveal") ---------- */

@property --epq-reveal {
  syntax: "<number>";
  inherits: true;
  initial-value: 1;
}

@keyframes epq-colourise {
  from { --epq-reveal: 0; }
  to   { --epq-reveal: 1; }
}

/* The colour every pigment mixes toward while unexposed, and the ink that is
   already visible on that first frame. Both are per-theme: on manila the
   unexposed page is white paper with black ink; on the ground it is the bare
   bath with its own light ink. Getting the second one wrong is what made the
   stamp's status cell ink-on-ground — unreadable for the whole 1.12s hold. */
:root { --epq-expose: #FFFFFF; --epq-expose-ink: var(--epq-src-ink); }
[data-epq-theme="reverse"] { --epq-expose: var(--epq-src-cy-ground); --epq-expose-ink: var(--epq-src-cy-ink); }

/* ---- The reveal ---------------------------------------------------------
   TIMING (founder, 2026-07-26): 1s delay, 360ms, steps(3, jump-end).
   jump-end emits 0 through its first interval, so the visible hold is
   delay + duration/3 = 1120ms, then jumps at 1120 / 1240 / 1360ms. The long
   hold is the point: the placard is fully laid out and readable throughout,
   so it reads as a drawn page being printed, not as a page that has not
   loaded. RETIMING: set the delay to the hold you want MINUS duration/3.

   Opt in per page with class="epq-reveal" on <html>. The site had this on
   :root unconditionally; as a design-system default that would impose a 1.4s
   monochrome hold on every consumer, including documents and decks where it
   is meaningless.

   NO !important ANYWHERE. The prefers-reduced-motion block in foundations.css
   already wins via !important on a universal selector and kills the
   animation, leaving --epq-reveal at its declared 1 — full colour, no stuck
   blank state. An !important here would flip that comparison and strand
   reduced-motion readers on a permanently empty page. */
html.epq-reveal {
  --epq-reveal: 1; /* @kind other */ /* 0 = unexposed, 1 = full colour */
  animation: epq-colourise 360ms steps(3, jump-end) 1s backwards;

  /* --epq-ink is absent on purpose: ink is the one thing already black on the
     first frame, and mixing it would leave nothing visible at all.
     --epq-ink-70 mixes toward INK, not toward the exposure colour, so
     secondary text starts black and settles to grey rather than materialising
     out of an empty page. */
  --epq-ink-70: color-mix(in srgb, var(--epq-src-ink-70) calc(var(--epq-reveal) * 100%), var(--epq-expose-ink));
  --epq-manila: color-mix(in srgb, var(--epq-src-manila) calc(var(--epq-reveal) * 100%), var(--epq-expose));
  --epq-vellum: color-mix(in srgb, var(--epq-src-vellum) calc(var(--epq-reveal) * 100%), var(--epq-expose));
  --epq-slate:  color-mix(in srgb, var(--epq-src-slate)  calc(var(--epq-reveal) * 100%), var(--epq-expose));

  /* The whole module palette, not just the page field: figures paint their
     marks from --epq-f-* directly, so leaving these raw puts violet, lime and
     cyan on an otherwise black-and-white first frame. */
  --epq-f-capture:  color-mix(in srgb, var(--epq-src-capture)  calc(var(--epq-reveal) * 100%), var(--epq-expose));
  --epq-f-explorer: color-mix(in srgb, var(--epq-src-explorer) calc(var(--epq-reveal) * 100%), var(--epq-expose));
  --epq-f-drift:    color-mix(in srgb, var(--epq-src-drift)    calc(var(--epq-reveal) * 100%), var(--epq-expose));
  --epq-f-verify:   color-mix(in srgb, var(--epq-src-verify)   calc(var(--epq-reveal) * 100%), var(--epq-expose));
  --epq-f-agents:   color-mix(in srgb, var(--epq-src-agents)   calc(var(--epq-reveal) * 100%), var(--epq-expose));
  --epq-cta:        color-mix(in srgb, var(--epq-src-capture)  calc(var(--epq-reveal) * 100%), var(--epq-expose));

  /* Reversed type on a field mark has to arrive AFTER the field it sits on,
     or it is white-on-white for the first 1.36s — the inverse of the reveal's
     premise. Ink at 0, the named colour at 1. */
  --epq-on-field: color-mix(in srgb, var(--epq-src-on-field) calc(var(--epq-reveal) * 100%), var(--epq-expose-ink));

  /* Every foreground that sits on a REVEALED fill must be revealed with it.
     Left as literals these converge with their own background during the hold:
     white on a fill that has mixed to white is 1:1 for 1.12s. The endpoint is
     --epq-expose-ink, so a state tag reads as line art first and takes its
     reversed type after — same treatment, same reason, as --epq-on-field. */
  --epq-on-cta:        color-mix(in srgb, var(--epq-src-on-cta) calc(var(--epq-reveal) * 100%), var(--epq-expose-ink));
  --epq-on-f-capture:  color-mix(in srgb, var(--epq-src-on-f-capture)  calc(var(--epq-reveal) * 100%), var(--epq-expose-ink));
  --epq-on-f-explorer: color-mix(in srgb, var(--epq-src-on-f-explorer) calc(var(--epq-reveal) * 100%), var(--epq-expose-ink));
  --epq-on-f-verify:   color-mix(in srgb, var(--epq-src-on-f-verify)   calc(var(--epq-reveal) * 100%), var(--epq-expose-ink));
  --epq-on-f-drift:    color-mix(in srgb, var(--epq-src-on-f-drift)    calc(var(--epq-reveal) * 100%), var(--epq-expose-ink));
  --epq-on-f-agents:   color-mix(in srgb, var(--epq-src-on-f-agents)   calc(var(--epq-reveal) * 100%), var(--epq-expose-ink));

  /* A view that sets its field as a raw hex bypasses the reveal entirely, so
     the field is mixed here too. Both endpoints stay exact (the exposure
     colour at 0, the field at 1); a page that sets no field mixes twice, which
     only makes the two intermediate steps slightly paler. */
  --epq-field: color-mix(in srgb, var(--field, var(--epq-f-capture)) calc(var(--epq-reveal) * 100%), var(--epq-expose));
}

/* On the cyanotype ground the pigments above are not the surfaces — the
   semantic aliases point at --epq-cy-*, so those are what must expose. Same
   driver, same timing; only the sources differ. --epq-cy-ink is the ink of
   this theme and is absent for the same reason --epq-ink is. */
html.epq-reveal[data-epq-theme="reverse"] {
  --epq-cy-ink-70: color-mix(in srgb, var(--epq-src-cy-ink-70) calc(var(--epq-reveal) * 100%), var(--epq-expose-ink));
  --epq-cy-panel:  color-mix(in srgb, var(--epq-src-cy-panel)  calc(var(--epq-reveal) * 100%), var(--epq-expose));
  --epq-cy-line:   color-mix(in srgb, var(--epq-src-cy-line)   calc(var(--epq-reveal) * 100%), var(--epq-expose));

  /* All five fields re-specified for this ground must be re-asserted HERE, or
     the reveal silently reverts them. `html.epq-reveal` is (0,1,1) and
     `[data-epq-theme="reverse"]` is (0,1,0), so the block above — which mixes
     from the base --epq-src-* pigments — outranks theme.css's remap onto the
     -rev variants. Every revealed page on the dark theme would ship the
     un-remapped fields: the contrast ones (magenta 3.6:1, violet 3.2:1) plus the
     re-saturated ones (the raw lime and cyan), defeating the exact decisions
     colors.css documents. The reveal must mix from the -rev sources on this
     theme, not the base ones. */
  --epq-f-capture:  color-mix(in srgb, var(--epq-src-capture-rev)  calc(var(--epq-reveal) * 100%), var(--epq-expose));
  --epq-f-explorer: color-mix(in srgb, var(--epq-src-explorer-rev) calc(var(--epq-reveal) * 100%), var(--epq-expose));
  --epq-f-verify:   color-mix(in srgb, var(--epq-src-verify-rev)   calc(var(--epq-reveal) * 100%), var(--epq-expose));
  --epq-f-drift:    color-mix(in srgb, var(--epq-src-drift-rev)    calc(var(--epq-reveal) * 100%), var(--epq-expose));
  --epq-f-agents:   color-mix(in srgb, var(--epq-src-agents-rev)   calc(var(--epq-reveal) * 100%), var(--epq-expose));
  --epq-cta:        color-mix(in srgb, var(--epq-src-capture-rev)  calc(var(--epq-reveal) * 100%), var(--epq-expose));
}

/* The legacy `--field` hop Episquire Web needs lives in the export's bridge
   block, not here — it is a consumer concern and not a design-system token. */

/* ============================================================
   BRIDGE — Episquire Web's unprefixed names <-> the DS's --epq-*.
   Lets the existing templates keep their markup and their per-page
   --field while gaining the field-carry system. Remove this block once
   the templates are migrated to --epq-* throughout.
   ============================================================ */
:root{
  /* the site sets --field per page from content JSON; adopt it as THE field */
  --epq-field: var(--field, var(--epq-f-capture));
}

/* Declared on `*`, NOT :root — the same computed-value trap as the field tints
   and the rule weights. --rule and --hair resolve var(--epq-line); pinned to
   :root they would freeze to ink, and EVERY legacy border on the cyanotype
   ground would be ink on near-ink, i.e. invisible. The pigment aliases sit
   here too so the whole bridge behaves consistently. */
/* Feed the revealed field back into the site's own --field name. The templates
   paint the hero panel, the stamp cell and the band tiles from var(--field)
   directly rather than through the bridge, so revealing only --epq-field leaves
   every field-coloured surface at full strength from the first frame — which is
   the whole bug the reveal exists to fix.

   It has to happen on the shell and NOT on :root: --field re-declared on :root
   as a mix of var(--field) is a self-reference, and a custom property that
   references itself is a cycle no matter what an ancestor holds. One level down
   it is not a cycle, because --epq-field is inherited from :root where it was
   already mixed from :root's own literal --field. Exposed once, and only once. */
[data-epq-ground], .epq-ground { --field: var(--epq-field); }

*{
  --ink: var(--epq-ink);
  --ink-70: var(--epq-text-muted);
  --manila: var(--epq-surface-page);
  --vellum: var(--epq-surface-card);
  --slate: var(--epq-focus-ring);
  --rule: var(--epq-rule);
  --hair: var(--epq-hair);
}

/* Note what those four aliases now mean: --manila and --vellum are the themed
   SURFACES, not the pigments, and --ink-70 is the themed muted text. So a
   legacy `background:var(--vellum)` re-grounds itself for free. The one that
   cannot is `color:var(--ink)` — ink is a pigment and stays #16180F — so text
   colours still have to move to --epq-text by hand. That is the whole
   remaining grep. */

