/* ============================================================================
   EPISQUIRE WEB — the site layer.

   Three stylesheets load, in this order, and the split is what stops the type
   scale drifting back to twenty-three sizes:

     episquire.css             tokens, themes, the reveal, the ground.  DS.
     episquire-components.css  one class per component (.epq-btn, .epq-price…). DS.
     epq-site.css              THIS FILE — page composition the DS has no
                               opinion about: the document shell, section
                               rhythm, the card interiors, the footer.

   Rules for editing this file:

   1. NEVER write a px font-size. Use a role token — 13 / 21 / 34 / 55 / 89 via
      --epq-t-label / -body / -title / -stmt / -hero, and the two fluid roles
      --epq-t-display and --epq-t-statement. Choose the role by what the element
      DOES, never by how big it should look. The floor is 13px and there is
      nothing below it: 8 is the next Fibonacci number down and sits under the
      floor, so "slightly smaller" is not an option.
   2. NEVER write a pigment. --epq-surface-page / -card, --epq-text / -muted,
      --epq-line. A literal var(--vellum) or var(--ink) stays light on the
      cyanotype ground. (The bridge in episquire.css re-points the legacy
      aliases for markup we have not migrated; new rules should not use them.)
   3. Text on a FILL is not text on a surface. --epq-on-field / --epq-on-cta,
      never #fff or ink — the -rev fields are lighter on the dark ground, which
      inverts what reads on them.
   4. Static shadows one hop: 5px 5px 0 0 var(--epq-line). Two hops through a
      property declared on `*` do not reliably re-resolve.
   5. If a component already exists for the thing you are styling, use it and
      delete the local rule. One layer per element — never both.
   ============================================================================ */

/* ---- Upstream correction: --epq-on-field never sees the field scope -----
   REV F bug, reported. The computed-value trap Design named in their own reply
   to our last upstream — "declare a composite custom property on the element
   that owns its inputs" — applied to --epq-on-field itself.

   It is declared twice, on :root and on html.epq-reveal, both of which resolve
   var(--epq-src-on-field) against the HTML element. The field scopes
   ([data-epq-field="agents"] and the class aliases) set --epq-src-on-field on
   BODY. So html computes --epq-on-field from the :root default — ink — and
   body inherits that already-resolved value, never its own.

   Measured on /services (agents violet, light theme): body carried
   --epq-src-on-field #FFFFFF while --epq-on-field resolved to #16180F. Ink on
   #6B4EFF is 3.5:1, below AA, and it was painting the stamp cell, the filled
   tags and the early-bird price chip. The dark theme looked right only by
   accident — ink is the correct answer there.

   Re-declared on `*`, the same shape the field tints and rule weights already
   use and for the same reason: a declaration on the element beats an inherited
   value, so every element resolves this against the nearest scope. Delete once
   the export carries it. */
* {
  --epq-on-field: color-mix(in srgb, var(--epq-src-on-field) calc(var(--epq-reveal) * 100%), var(--epq-expose-ink));
}

/* ---- Upstream correction: field scopes on the cyanotype ground ---------
   REV F bug, reported. episquire.css sets --epq-src-on-field: #FFFFFF inside
   [data-epq-field="drift"] and ="agents", and nothing re-scopes it per theme.
   The dark theme substitutes the LIGHTER -rev pigments for those two fields
   (#FF4DA6, #8B72FF), where white measures 3.1:1 and 3.5:1 — below AA — while
   ink measures 5.9:1 and 5.1:1. episquire.css already makes exactly this
   correction for --epq-src-on-f-drift / -agents; the field-scope path was
   missed, and it is the path this site uses.

   It cannot be fixed from :root — the scope declares the property on <body>,
   and the nearest declaring ancestor wins regardless of specificity — so the
   correction has to match at or below the scope element itself. Delete this
   block once the export carries it. */
[data-epq-theme="reverse"] [data-epq-field="drift"],
[data-epq-theme="reverse"] [data-epq-field="agents"] {
  --epq-src-on-field: var(--epq-src-ink);
}

/* ---- Document shell ---------------------------------------------------- */
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
/* THE SHEET NEVER SCROLLS SIDEWAYS. Standing structural guard, learned the
   expensive way: the press-sheet marginalia (.doc::before, further down)
   once overhung the viewport by 64px a side — furniture drawn for the
   retired 1080px trim — and though it painted entirely off-canvas, the
   overhang manufactured a permanent horizontal scrollbar. svh does not
   subtract scrollbars, so every card the pager sized to 100svh − nav ran
   15px past the screen it was snapped to (measured at 2560×1271) and
   scrolled inside itself by exactly the scrollbar's height. The marginalia
   has since been re-anchored inside the viewport; this guard stays so no
   future stray can repeat the trick. clip, not hidden: clip is the one
   overflow value that neither makes body a scroll container nor blockifies
   its visible partner axis — the same idiom .epq-mantra already uses for
   its own guard. Fixed-position overlays (drawer, fab) resolve their
   containing block past body, so they still reach the screen edge. */
body { margin: 0; overflow-x: clip; }

/* Selecting text is the one interaction every visitor performs that the
   system had no opinion about, so it fell back to the browser's blue — a
   pigment from nobody's palette. The selection takes the view's field with
   its own on-colour, both of which already resolve per scope, per theme and
   per reveal frame, so highlighting a paragraph answers the same question the
   clause numbers do: which module am I in. */
::selection { background: var(--epq-field); color: var(--epq-on-field); }

/* The scrollbar sits against the page ground on every OS that shows one.
   Both values are semantic aliases, so it is ink-on-manila in light and
   line-blue-on-bath in reverse — the last piece of browser chrome that was
   still painting someone else's colours. */
html { scrollbar-color: var(--epq-line) var(--epq-surface-page); }

/* ---- The infinite sheet · FOUNDER 2026-07-28 ---------------------------
   The framed 1080px document is retired. The metaphor stays — this is still
   an engineering document — but the SHEET is now the whole screen: the site is
   written onto paper whose edges end far beyond any viewport, so there is
   nothing to frame. Concretely:

   · The fixed veil over the engineering grid re-colours from the PAGE surface
     to the CARD surface, so the entire viewport reads as the drawing sheet
     with the grid faintly showing through — the paper's own graph ruling. On
     the cyanotype theme that is literally blueprint paper.
   · .doc loses its width cap, side borders and opaque background; it no
     longer IS the paper, it only groups what is written on it.
   · Content stays legible in a centred pillar (--epq-pillar) — one rule on
     .doc's children, so every page and every future template follows. Line
     length was never at stake (64ch caps hold); the pillar bounds the STAGE.
   · Sections divide with light, slightly irregular PENCIL rules — an
     engineer's early draft pass — instead of the hard 2px ink rule. See the
     pencil block below. */
:root { --epq-pillar: 1200px; }
.epq-ground::before { background: var(--epq-surface-card); }
.doc {
  position: relative;
  z-index: 1; /* still lifts the writing above the fixed grid veil */
  /* Second layer of the sideways-scroll guard (the first is on body, with
     the full story). The body clip propagates to the viewport, which removes
     the scrollbar but — in Chrome — still lets a programmatic scroll shift
     the page onto anything a descendant paints past the edge (the
     marginalia's since-removed 64px overhang, at the time). Clipping here,
     on the box such strays belong to, zeroes the overflow at its source:
     scrollWidth reads the viewport and nothing can be scrolled onto. x only
     — a y clip would shave the card deal's 44px throw at the document's
     ends. clip creates no scroll container, so the sticky nav (a .doc
     child) is untouched. */
  overflow-x: clip;
}
.doc > * {
  max-width: var(--epq-pillar);
  margin-left: auto;
  margin-right: auto;
}

/* ---- Section bands: the ink bar runs the full width of the sheet -------
   Founder 2026-08-03: "extend the section bars fully across the screen from
   left to right on all pages." The same shape as the nav one change earlier
   — the BAND paints edge to edge, the CONTENT stays exactly where it was.

   A section cannot host a full-width bar while it is itself capped, so the
   cap moves DOWN a level. The section opts out with max-width: none, and so
   does the .epq-card between the section and the bar, because index.mjs
   wraps each clause head in one (the other templates put the bar straight
   into the section, hence both selectors). That is the idiom .epq-mantra
   already uses and the reason it is safe is stated there: .doc carries no
   horizontal padding, so an uncapped child IS the content width. No 100vw
   anywhere, so there is no scrollbar width to subtract and no horizontal
   overflow manufactured at any viewport.

   Everything else a section holds — the body, the figure band, the footer —
   takes the pillar back on its own box and centres, so every word lands
   where it landed before. margin-left/right rather than the `margin`
   shorthand: the pager sets `margin-top: auto` on the last card's footer to
   push it to the screen's foot, and a shorthand here would silently reset
   it.

   The clause bar is the one exemption, and it keeps its TEXT in the pillar
   with padding rather than an inner wrapper. Percentage padding resolves
   against the CONTAINING BLOCK, which is now the full-width band, so
   (100% − pillar) / 2 is exactly the gutter either side and + 28px is the
   bar's own inset from the design system. max() floors it at the plain
   gutter below 1200px, where the pillar does not bind and the calc would go
   negative. A wrapper would have worked too — it is what .epq-nav-in does —
   but the bar's markup is repeated in four templates and its contents are
   two inline children, so the padding carries it without touching any of
   them. */
.doc > section,
.doc > section > .epq-card { max-width: none; }
.doc > section > * {
  max-width: var(--epq-pillar);
  margin-left: auto;
  margin-right: auto;
}
/* Same cap one level in, plus width: 100%, and the width is not decoration.
   On the pager the card is a flex COLUMN, so its children's left/right
   margins are on the CROSS axis — and a flex item with an auto cross-axis
   margin does not stretch. Cap + auto margins alone therefore shrank the
   last card's footer to the width of its own text and centred that. The
   explicit width gives the item a definite cross size again, which the cap
   then holds at the pillar; border-box is global, so the padding those
   children carry stays inside it. Off the pager the card is an inert block
   div and width: 100% is what a block child does anyway. */
.doc > section > .epq-card > * {
  width: 100%;
  max-width: var(--epq-pillar);
  margin-left: auto;
  margin-right: auto;
}
/* --epq-stage is the pillar's one sanctioned override, and it exists so the
   bar and the content it heads cannot come apart. A section may widen its
   measure (the role split does — see THE SPLIT CLAIMS ITS FRAME); the bar's
   inset is derived from that measure rather than from the pillar, so the clause
   number stays on the same vertical as the first word under it. Unset — which
   is every section on every other page — it falls back to the pillar and this
   is the rule that has always been here. */
.doc > section > .epq-clause-bar,
.doc > section > .epq-card > .epq-clause-bar {
  max-width: none;
  margin-left: 0;
  margin-right: 0;
  padding-left: max(28px, calc((100% - var(--epq-stage, var(--epq-pillar))) / 2 + 28px));
  padding-right: max(28px, calc((100% - var(--epq-stage, var(--epq-pillar))) / 2 + 28px));
}

/* ---- Section rhythm ---------------------------------------------------- */
/* Sections separate with a PENCIL rule, not the 2px ink rule: on the infinite
   sheet the hard rule read as a cut edge, and the founder asked for the line
   an engineer rules before inking — light, slightly irregular, drawn twice.

   The wobble is an SVG tile used as a MASK, not as an image: a mask carries
   only alpha, so the line's colour comes from background-color and follows the
   theme (graphite on vellum, chalk-blue on the blueprint) with one asset. Two
   strokes in the tile — a firm pass and a fainter second pass below it — are
   what say "draft" rather than "wavy border". The tile is 720px and seam-
   matched, so the repeat has no step; the wobble repeating every 720px is
   below notice at ±1px amplitude.

   The rule sits in its own air (38px above and below) rather than flush on
   the clause bar: a draft line floats on the sheet between blocks of writing,
   it does not underline the next heading. It runs the full width of the
   sheet, which it gets for free from the section band below — a pseudo-
   element is not matched by `> *`, so it keeps the band's width while the
   section's real children take the pillar back. That retired an overshoot
   rule (width: 100% + 76px past 1320px, a draftsman's rule running past the
   margin) which had no margin left to run past and, on a full-width section,
   would have been 76px of real horizontal overflow. */
:root {
  --epq-pencil: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='720' height='7' viewBox='0 0 720 7'><g fill='none' stroke='black' stroke-linecap='round'><path stroke-width='1.7' d='M0 3.5 C30 2.6 55 4.4 90 3.8 S150 2.7 185 3.4 S250 4.6 290 3.6 S350 2.5 395 3.3 S460 4.5 500 3.7 S560 2.8 610 3.5 S680 4.2 720 3.5'/><path stroke-width='1.1' stroke-opacity='.5' d='M0 4.6 C40 5.2 70 3.9 110 4.5 S180 5.4 220 4.6 S300 3.8 340 4.7 S420 5.3 470 4.4 S550 3.9 600 4.8 S670 5.1 720 4.6'/></g></svg>");
}
section { margin-top: 38px; }
section::before {
  content: "";
  display: block;
  height: 7px;
  margin: 0 0 38px;
  background-color: var(--epq-text-muted);
  -webkit-mask: var(--epq-pencil) repeat-x left center;
  mask: var(--epq-pencil) repeat-x left center;
}
.body { padding: 32px 28px; }
/* A FRAME SECTION (founder: "give the preceding text section space to fill
   the whole frame"): the graded statement's body claims a screen below the
   sticky nav — the hero's claim pattern, minus the clause bar riding above
   it — and centres its one sentence in the claim. It also anchors the hop
   arrow at its foot. */
.body-frame {
  position: relative;
  min-height: calc(100vh - var(--epq-nav-h, 86px) - 54px);
  min-height: calc(100svh - var(--epq-nav-h, 86px) - 54px);
  display: grid;
  align-content: center;
}
.body p { max-width: 64ch; }
.body code { font-family: var(--epq-font-mono); font-size: var(--epq-t-body); }

/* THE MARK ROW — the certification roundel standing beside a section's words
   and its button (founder, 2026-08-03: "we can put the roundel on the last
   section of the index page, to the right of the text and the button"). It is
   the mark that came off /how-it-works when the mantra tail went, in the tail's
   own treatment rather than the hero's full field column: this row ends on the
   page's one CTA, and 260px of field colour beside it would take the weight the
   button is asking for.

   `auto` on the second track, not a fixed width, so the plate is exactly the
   drawing plus its border and the text keeps everything else — the statement
   and body already cap themselves at 22ch and 64ch, so the words do not stretch
   to meet it. Centred against the whole block, which is what puts it beside the
   button and not only beside the heading.

   The plate takes --epq-field from whatever scope it lands in, so a section
   that names its own field (home §2.0 names explorer) tints the mark with it
   for free — the same binding the hero mark uses. */
.sec-mark {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 28px;
  align-items: center;
}
.sec-mark > .body { min-width: 0; }
.sec-mark-plate {
  background: var(--epq-field);
  border: var(--epq-rule);
  padding: 20px;
  line-height: 0;
  margin-right: 28px;   /* the body's own gutter, which the plate is outside of */
}
@media (max-width: 740px) {
  /* Under the words on a phone, left-aligned with them: a mark centred in a
     column of its own would read as a second, unrelated block. The 16px is
     .body's narrow gutter (see the mobile block near the foot of this file).

     The drawing steps down with it. Beside the words the mark costs the card no
     height at all; under them it costs its own, and at 120 in a 20px plate that
     was more height than the screen had left — the closing card would have
     scrolled inside itself for a stamp. So on a phone it IS a stamp: 96, and 72
     where the nav itself recomposes. The SVG carries a viewBox, so every step is
     the same drawing, not a second asset.

     The body's bottom padding goes for the same reason it goes on a folded card
     — the plate brings its own border, so the padding was drawing a second gap
     — and the air moves above the plate where it belongs. */
  .sec-mark { grid-template-columns: minmax(0, 1fr); gap: 0; }
  .sec-mark > .body { padding-bottom: 0; }
  .sec-mark-plate { justify-self: start; margin: 20px 16px 0; padding: 12px; }
  .sec-mark-plate svg { width: 96px; height: 96px; }
}
@media (max-width: 470px) {
  .sec-mark-plate { margin-top: 14px; padding: 10px; }
  .sec-mark-plate svg { width: 72px; height: 72px; }
}

/* The in-body statement — one per section, the 55px step. Fluid so it travels
   between 34 and 55 and never lands between roles. */
.stmt {
  font-family: var(--epq-font-display);
  font-variation-settings: var(--epq-big-vf);
  font-size: var(--epq-t-statement);
  line-height: var(--epq-lh-tight);
  letter-spacing: -.01em;
  text-transform: uppercase;
  margin: 0 0 24px;
  max-width: 22ch;
}

/* THE GRADED STATEMENT, third grammar (founder, 2026-08-02: "Instead of
   different sizes and separate lines for emphasis, use hollow letters for
   the lower value words and filled text for the stressed words").

   The grading is INK: the low-value words are HOLLOW (the site's standing
   unprinted-plate idiom, the strike hero's own ghost) and the stressed
   phrases are FILLED. The sentence flows and wraps as a sentence; the spans
   are inline in one paragraph, so a screen reader gets one whole sentence.

   TWO AMENDMENTS, founder 2026-08-06, both because §1.0 now reads over a
   MOVING ambient scene rather than a flat card:

   1. HOLLOW IS NO LONGER EMPTY. A transparent counter let the isometric
      scenery print straight through the letterforms, and the type lost
      against its own background — legible on a still card, unreadable with
      the loop running behind it. The hollow words now carry a WASH: a
      low-alpha ink fill inside the same hairline stroke. Still plainly the
      unprinted plate against the filled words (the grade survives), but the
      counters now hold their own value instead of borrowing the scene's.
      The stroke thickens a touch with them for the same reason.

   2. SIZE RETURNS AS A SECOND AXIS — but only as an OPT-IN per phrase, and
      only in em, so it multiplies the fluid step rather than replacing it.
      That is what killed the original size-graded version: hard sizes that
      the mobile clamp could not collapse. `stmt-lg`/`stmt-xl` ride the
      clamp all the way down, so there is no phone step to write.

   The stroke tracks the type (em with a px floor), same law as the strike
   rule. */
.stmt-graded {
  /* THE SIZE IS TWO-AXIS, and the block half is not belt-and-braces — same
     finding, same reason, as the role split's two-axis gate below.
     --epq-t-statement answers to WIDTH alone (5vw between 27 and 55). That
     was right when the statement was six lines of one size; with the stress
     scales it composes to ~10.2em of leading, so at the 55px ceiling it
     stands 559px tall and the §1.0 card needs 715 — more than the 682 a
     1366x768 laptop's pager screen has, and more than 1280x720 or 1440x810
     have either. Not an edge case: EVERY short-and-wide laptop overflowed,
     because width is exactly the axis that cannot see the problem.

     So the step is ALSO capped by the height actually available: the screen
     less the nav, the clause bar and this card's own padding (242px), split
     across the statement's composed em-total. Both terms are ceilings under
     a min(), so the narrower axis wins and the type never grows past the
     55px step; the max() keeps the phone floor, so a landscape phone gets
     small type rather than unreadable type.

     --stmt-ems IS the composition: 2 base lines + lg + 2 base + 2 xl, each
     at --epq-lh-tight. Re-cut §1.0's copy and this is the number to redo —
     it is the one thing here that knows what the sentence says. */
  --stmt-ems: 10.2;
  font-size: min(
    var(--epq-t-statement),
    max(27px, calc((100svh - 242px) / var(--stmt-ems)))
  );
  /* Wider than the 18ch this carried when every word was one size — the
     enlarged phrases need room to break on their own terms ("GIGANTIC /
     AMBITIONS") — and no wider, because a loose measure is what lets the
     wrap fall INSIDE a stressed phrase: at 24ch "META-ASSISTANT" split at
     its hyphen. 20ch clears the longest xl word (9 chars at 1.9em) with the
     break falling between phrases, where the grade can be read. */
  max-width: 20ch;
  /* The rag, not the break. Each run is its own block (see the scales
     below), and a run that overhangs the measure by one word leaves that
     word alone on a line — "WITH" stranded under "(EVEN ONE PERSON)".
     balance divides the run evenly instead. Progressive by construction: a
     browser without it wraps exactly as it did before. */
  text-wrap: balance;
}
/* §1.0 carries a statement and nothing else, so .stmt's 24px trailing gap is
   dead height at the foot of a centred block — and on the pager dead height
   is budget. It only goes where there is genuinely nothing under it; a
   section with a lede or body keeps the gap. */
.stmt-graded:last-child { margin-bottom: 0; }
.stmt-h {
  /* The wash, not transparency. color-mix keeps it a tint OF the page ink,
     so it follows the theme and the field scope like everything else. */
  color: color-mix(in srgb, var(--epq-text) 34%, transparent);
  -webkit-text-stroke: max(1.5px, .018em) var(--epq-text);
}
/* THE STRESS SCALES. em, so they multiply --epq-t-statement's clamp and
   inherit its mobile floor; nothing here is a second absolute size.
   NO PHONE STEP, and that is the point of writing both the scales and the
   measure in relative units. `20ch` is 20 characters of whatever the clamp
   is currently serving, so the box shrinks with the type and the longest xl
   word (9 characters at 1.9em ≈ 15ch) clears it at 390px exactly as it does
   at 1440. A px measure or a px size here would need a phone override, and
   the override is what broke the first size-graded statement.

   A SCALED PHRASE TAKES ITS OWN LINE (founder, 2026-08-06: "we're going to
   need to put phrases with different font sizes on different lines"). Two
   sizes sharing a line sat on one baseline with the line box sprung to the
   taller of them, so the small run floated in the big run's leading and the
   ragged edge read as an accident. display:block gives each scaled phrase
   its own line and, by splitting the paragraph's inline flow, gives the
   base-size runs either side of it their own lines too — so a line carries
   exactly one size. The spans are still spans in one <p>: the sentence is
   unbroken for a screen reader, and no block element enters the paragraph.

   The base runs still WRAP as running text inside their own lines; only the
   size changes force a break. */
.stmt-lg,
.stmt-xl { display: block; }
.stmt-lg { font-size: 1.28em; }
.stmt-xl { font-size: 1.9em; }

/* ---- THE ROLE SPLIT (home §2.0, founder 2026-08-05) --------------------
   Role hero on the left half, two cards on the right: the station-glyph grid
   and the positioning card. Card layout after render.com's panel grammar —
   bordered sheets with the house hard shadow, the second one taking the
   section's field as a solid fill so the column carries colour where the
   grid is monotone.

   The roll's host column carries minmax(0,1fr) via .role-split's first
   track and the element's own min-width:0 — the two things epq-role-roll.js
   documents as load-bearing for the fit.

   WRITTEN MOBILE-FIRST, and the side-by-side layout starts at 1151px rather
   than 901 — the breakpoint the isometric mounts already use, and here it is
   arithmetic too. Beside each other, the copy and the seven-station index
   have to share one pager screen; at 1024x768 that needed 919px against a
   682px budget however hard the cells were compacted, because a narrow right
   column drops the glyph grid to two tracks and gains a row. Stacked, the
   same content becomes three snap stops that each fit. Below 1151 the
   section therefore stacks and pages; above it, it is the two-column split. */
.role-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 28px;
  align-items: center;
  padding: 20px 16px;
}
/* THE SPLIT CLAIMS ITS FRAME (founder, 2026-08-06: "not drawing correctly —
   it should take up a full frame like its peer sections").
   Measured on the founder's own screen, 2560x1271: the card was the full
   1185px the pager gives it, and the SPLIT inside it was a 1200x660 island
   with 262px of blank above and below and 680px of blank either side. Its two
   neighbours are ambient sections, whose stage escapes the pillar and takes
   flex: 1 1 auto — so they paint the whole 2560x1185 while this one printed a
   postcard in the middle of it. Nothing was broken; the section simply was not
   claiming what it had been given.

   Two corrections, one per axis.

   ACROSS: a stage measure of its own, wider than the pillar. The pillar is a
   rule about READING — one column of prose at a comfortable measure — and it
   is still doing that job here, because what the split holds is two columns
   side by side and the prose inside them keeps its own 52ch cap.

   The clamp is written so the tuned screens cannot move. Its FLOOR is the
   pillar itself, so everything up to a 1336px viewport is arithmetically the
   layout that shipped; the 136px term is that viewport's own gutter carried
   upward, so the stage widens by taking the same margin it always had; and
   1560px is the ceiling, which is where two columns and the 44px gap stop
   getting wider than the glyph index wants to be. It first binds at 1696px.

   The measure is published as --epq-stage rather than written here, because
   the clause bar above derives its inset from it — a wider stage with a
   pillar-inset bar puts the clause number 180px right of the first word under
   it, which is the kind of thing that reads as a mistake without ever looking
   like one.

   DOWN: flex: 1 1 auto, the same correction .body-frame and .amb-stage each
   carry, for the same reason stated there — the CARD is the screen, the clause
   bar is the flex item above, and the rest of it belongs to this. It can only
   ADD height: where the content is already taller than the screen (the 1280x800
   laptop the vertical allowances below are tuned for) flex-basis auto leaves
   the box exactly content-sized, so the tight case is unchanged. */
.doc > section:has(.role-split) { --epq-stage: clamp(var(--epq-pillar), 100% - 136px, 1560px); }
.doc > section > .epq-card > .role-split { max-width: var(--epq-stage, var(--epq-pillar)); }
@media (prefers-reduced-motion: no-preference) {
  html.epq-paged .epq-card > .role-split { min-height: 0; flex: 1 1 auto; }
}
/* THE GATE IS TWO-AXIS, and the height half is not belt-and-braces: side by
   side the split's content is a constant ~699px whatever the width, so a
   1366x768 or 1200x760 laptop overflows its 682/674px budget by the same
   amount a 1440x900 clears it by. Width alone cannot see that. Below either
   threshold the section stacks and pages as three stops, each of which fits.
   Same idiom, same reason, as the folded-card padding gate further down. */
@media (min-width: 1151px) and (min-height: 780px) {
  .role-split {
    /* UNEVEN ON PURPOSE (founder, 2026-08-12: the text cards had "too much
       unused space… leaving more perceived space for the dynamic images/
       text"). The left half is the section's dynamic thing — the rolling
       headline, or the backlog loop — and the right half is now a content-
       sized text block rather than a stretched panel, so the stage takes the
       larger share. 1.15/.85, not further: the glyph index still needs two
       240px-order tracks in its .85 share at the pillar. */
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
    gap: 44px;
    /* stretch, not centre, once the split has a frame to fill — see the
       flex: 1 1 auto note above. Stacked (below either threshold) the tracks
       are content-sized and centred, which is what a phone wants. */
    align-items: stretch;
    /* Tight on the block axis on purpose: side by side, the whole split —
       clause bar, seven glyphs, the pitch — has to fit one pager screen, and
       the binding case is a 1280x800 laptop (a 714px budget), not the 1440
       this was first composed at. Every vertical allowance in the split is
       lean for that screen; it clears with ~30px to spare. */
    padding: 14px 28px;
  }
}
/* ONE SIZE FOR EVERY LINE — AND THE ROLL MUST NEVER SCALE ITSELF.
   Founder, 2026-08-05, twice: the roll rendered smaller than the static
   lines around it ("the ____ for your agents").

   The element fits ITSELF to its column — `min(1, column / ghost)` — while
   the static lines take whatever the CSS says. So the ONLY way the two sizes
   are equal is for that fit to resolve to exactly 1, and then the headline's
   own font-size governs every line.

   The first cut sized the statics on the ghost's measured em ratio (10.219)
   with a hair of slack, which made them equal ON THIS MACHINE and left a
   0.3% margin — 548.4px of roll in a 550px column. That is not a margin, it
   is a coincidence: a different rasteriser, a webfont that lands a frame
   late, a fractional column, and the ghost crosses the column, the element
   scales the roll a few percent, and the mismatch is back. It was reported
   from a real browser doing exactly that.

   So every divisor here carries a REAL 6% headroom over the widest run it
   has to clear. The fit then clamps to 1 by construction rather than by
   luck, and the headline is one size because there is only one size in play.

   RETUNE ONLY IF THE COPY CHANGES, and keep the 6%. Two runs to measure, and
   the divisor is the larger: the roll, as ghost.scrollWidth / font-size; and
   the tail, as the UNION of its .rs-l spans under white-space:nowrap
   (getClientRects returns one rect per span, so a max() over them silently
   measures the wider span instead of the joined run — that mistake set this
   divisor too low once already and put the tail back on two lines). A raw px
   value with no type token is the roll-fitting licence — the same one the
   mantra's fitter takes — done in CSS here because the measure is a
   container, not the viewport. */
.role-hero {
  container-type: inline-size;
  /* the statement centres in whatever height the stretched track hands it;
     content-sized tracks (stacked, or a short screen) are unaffected */
  display: flex;
  flex-direction: column;
  justify-content: center;
}
/* THE AMBIENT STAGE — the split's other left half (founder, 2026-08-06). The
   same looping scenery the full-bleed bands carry, given a frame of its own:
   no veil, because no copy sits on it, and the scene FITS rather than covers,
   because a crop here would throw away composition nobody is reading through.
   The band is absolutely positioned by its own chrome, so the stage needs a
   height to be absolute against, and its only content is that absolute band —
   so the track's stretch is what gives it one, and the min-height is the floor
   for every case where the track is CONTENT-sized instead (stacked on a phone,
   or a short screen) and there is no row height to take a share of. Without
   the floor the stage measures zero there and the scene simply is not on the
   page. The band's own positioning comes from ambient-band.mjs's chrome. */
.role-stage { position: relative; min-width: 0; min-height: 380px; }
.role-stmt {
  font-family: var(--epq-font-display);
  font-variation-settings: var(--epq-big-vf);
  /* PHONE. The roll stacks a word per line and the tail breaks, so the widest
     run is "FOR YOUR" at 5.178em; x1.06 = 5.49. The 52px ceiling is taste,
     not fit: it keeps a five-line headline from owning the whole screen. */
  font-size: min(52px, calc(100cqw / 5.49));
  line-height: var(--epq-lh-tight);
  letter-spacing: -.01em;
  text-transform: uppercase;
  margin: 0;
}

/* THE ROLE BREAKS ACROSS TWO LINES (founder, 2026-08-05: "you can split the
   changing role text across two lines on landscape form factors and adapt
   for mobile — the ___ / ___ / for your agents").

   "The" is now the roll's OWN first column, constant across every role, so
   the element's rule that a word shared by consecutive roles holds dead
   still is what keeps it from twitching — it is the same mechanism that
   holds "manager" still between project and product manager, spent on a
   word that never changes at all. That is what lets "THE" sit on the same
   line as the stepping word without leaving the element.

   The columns lay out in a GRID rather than the element's own inline-flex
   row: three columns flowing into two grid tracks put [the][word1] on row
   one and [word2] on row two — the founder's composition exactly, and
   deterministic, where a flex-wrap would depend on shrink-to-fit resolving
   the way we hoped. `display` is the one property the element writes inline,
   so this is the one !important; everything else is set from here cleanly.
   Both the WINDOW and the GHOST take it, or the ghost measures the words in
   one row and the fit comes out sized for a line that is never drawn.

   The word space is padding on the columns that END a row, because gap="0"
   is passed at the source (a flex/grid gap would also open the ROWS, and
   this headline wants its own leading). A trailing pad on the last row's
   single column costs a few px of invisible width and no rule. */
.role-stmt epq-role-roll [data-window],
.role-stmt epq-role-roll [data-ghost] {
  display: grid !important;
  grid-template-columns: auto;      /* phone: one word per line */
  justify-content: start;
}
.role-stmt epq-role-roll { line-height: var(--epq-lh-tight); }

/* The tail runs: a line each on a phone, one line in a landscape column. A
   <br> could not do this — the break has to be undoable. */
.rs-l { display: block; }

@media (min-width: 741px) {
  /* LANDSCAPE COMPOSITION — the founder's sketch: THE + the stepping word,
     the second word, then the tail on one line. It starts at 741px, the
     site's own phone boundary, not at the 1151 layout breakpoint: a stacked
     tablet column is wider than a split desktop one, so it can carry the
     composition comfortably. */
  /* THE BINDING RUN IN LANDSCAPE IS THE ROLL, as of 2026-08-12. The /10
     divisor was sized so the tail "FOR YOUR AGENTS" could sit on one line —
     but the tail measures 10.428em (not the 9.445 first recorded; see
     decisions/2026-08-06-the-suite-splits-and-claims-its-frame.md, "Left
     alone, deliberately"), so it wrapped to two lines at every width anyway
     and the headline paid for a composition it never drew. The founder's
     call ("the role text is also too small") takes the other branch: size on
     the roll's widest row, "THE BUSINESS" at 7.167em (ghost.scrollWidth,
     pad included), x1.06 headroom = 7.6, and let the tail break where it
     always did — FOR YOUR / AGENTS, the phone's own composition. The roll's
     fit clamps to 1 by construction, so the headline stays one size.
     Measure BOTH runs if either changes: the roll as ghost.scrollWidth, the
     tail's longest WORD-RUN between break opportunities ("FOR YOUR",
     5.178em) as the union of its rects under white-space:nowrap. */
  /* The CEILING is taste, not fit — the divisor is the fit, and it is the
     thing the block above forbids retuning without remeasuring. 88 was set
     when the divisor was 10; kept there it would cap the whole widened range
     (the /7.6 fit passes 88px from a 669px column up) and repeal the size
     this change exists to grant. 112 binds at 851px, which only the stage
     ceiling's 840px column approaches, so what governs is the divisor and
     the top end stops a hair short of poster size. */
  .role-stmt { font-size: min(112px, calc(100cqw / 7.6)); }
  .role-stmt epq-role-roll [data-window],
  .role-stmt epq-role-roll [data-ghost] { grid-template-columns: auto auto; }
  /* the first column carries the word space; the second ends the row */
  .role-stmt epq-role-roll [data-window] > *:nth-child(1),
  .role-stmt epq-role-roll [data-ghost] > *:nth-child(1) { padding-right: .22em; }
  /* ROW TWO SPANS BOTH TRACKS, and this is load-bearing rather than tidy.
     Left to auto-flow, the third column lands in track 1 and forces that
     track to fit "ANALYST" — so the grid measured max(the, analyst) +
     business = 611px, wider than either row it actually draws, the ghost
     reported 611 into a 550px column, and the element dutifully scaled the
     roll to 0.9 of the static lines. That IS the reported mismatch, arriving
     by a new route. Spanning the row means the tracks are sized by the row
     that is really drawn, and a spanning item only grows them if it is wider
     than both together — which "ANALYST" is not. */
  .role-stmt epq-role-roll [data-window] > *:nth-child(3),
  .role-stmt epq-role-roll [data-ghost] > *:nth-child(3) { grid-column: 1 / -1; }
  .rs-l { display: inline; }
  .rs-l + .rs-l::before { content: " "; }
}
.role-cards { display: flex; flex-direction: column; gap: 12px; min-width: 0; justify-content: center; }
/* THE CARDS SHED THEIR PANELS (founder, 2026-08-12: "too much unused space on
   desktop/tablet… find a way for the text containers to blend/fade with the
   background and use only the space they need"). Each card was a bordered,
   hard-shadowed sheet on the page surface, stretched to its share of the
   column — which put a visible frame around every pixel the content did not
   fill and made the column's air read as the card's emptiness. Border, shadow
   and fill all go, and the card goes content-sized: the text now sits directly
   on the section's own surface and the COLUMN centres it (justify-content
   above), so the blank around it is the section's air, not a panel's. The
   .role-card class stays load-bearing chrome-free — it is a phone snap stop
   and the columns' flex item. */
.role-card { flex: 0 0 auto; }
.role-card--glyphs { padding: 15px 20px; container-type: inline-size; }
.glyph-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  /* 240px, not 210: the track floor has to hold TWO columns across both card
     widths the split now produces — 510px of inner card at the pillar and
     690px at the widened stage. 210 gave two at the first and three at the
     second, and three tracks over seven stations rags 3/2/2 and shrinks every
     caption to hold a column that is not there. 240 floors it at two for
     anything between 480 and 719, which is the whole two-column range. */
  /* 210, retuned 2026-08-12 with the .85fr track: the floor has to hold TWO
     columns across the card widths the split now produces — ~467px of inner
     card at the pillar and ~587 at the widened stage. 240 held two across the
     old even split's 510/690 but drops the new pillar case to one column of
     seven, a list taller than the frame; 210 floors both at two, and three
     tracks (which rag 3/2/2) need 666px, which no split column reaches. */
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  gap: 10px 18px;
  /* the index sits in the middle of whatever height it is handed, rather than
     spreading seven stations over a tall screen until they stop reading as one
     list */
  align-content: center;
}
/* THE MARK SITS BESIDE ITS CAPTION, at every width. Stacked above it, each
   cell measured 120px and three rows of them ran the card to 428px — which
   is what pushed the section past its frame on a 1280x800 laptop and, two
   columns deep, past it at 1024x768 as well. As a row the cell is ~half that
   and the seven stations read as the index they are. One treatment, so there
   is no width at which the taller one can come back and overflow a screen. */
.glyph-grid li {
  display: grid;
  grid-template-columns: 30px minmax(0, 1fr);
  grid-template-rows: auto auto;
  column-gap: 11px;
  row-gap: 2px;
  align-items: center;
  align-content: start;
}
.gg-ic { line-height: 0; grid-row: 1 / 3; }
.gg-name, .gg-txt { grid-column: 2; }
.gg-name {
  font-family: var(--epq-font-mono);
  font-size: var(--epq-t-label);
  font-weight: var(--epq-w-label);
  letter-spacing: var(--epq-track-label);
  text-transform: uppercase;
  color: var(--epq-text);
}
/* label size, not body: seven captions at 21px would out-weigh the copy they
   support. Muted, because the glyph and its name are the thing being said. */
.gg-txt { font-size: var(--epq-t-label); line-height: 1.45; color: var(--epq-text-muted); }
/* THE INDEX STEPS UP WITH ITS PANEL. "Out-weigh the copy they support" was
   written when this card shared a column with the positioning card; that card
   moved to §3.0 on 2026-08-06 and there is no copy left here to out-weigh —
   the index IS the section's right half now. On the widened stage a label-sized
   list read as a footnote pinned to the middle of a 1000px panel, which is the
   same under-filling the section was reported for, one level in.

   A CONTAINER query, not a viewport one, because what governs is how much card
   there is: under the .85fr track the card is ~467px of content at the pillar
   and ~587 at the stage measure, and 560 is the threshold between them that
   cannot drift when a breakpoint moves (620 was, when the split was even and
   the widths were 510/690). This is the one place on the page where the seven
   stations have room to be read rather than counted. */
@container (min-width: 560px) {
  .glyph-grid { gap: 24px 30px; }
  .glyph-grid li { grid-template-columns: 42px minmax(0, 1fr); column-gap: 16px; row-gap: 5px; }
  .gg-ic svg { width: 40px; height: 40px; }
  .gg-name, .gg-txt { font-size: var(--epq-t-body); }
}
/* THE PITCH CARD IS ORDINARY TEXT (founder, 2026-08-06: "change it to match
   normal card text"; 2026-08-12: the panel itself goes — see .role-card). It
   was a solid field fill, then a sheet; now it is the section's own ink on the
   section's own surface, beside the isometric stage it captions. */
.role-card--pitch { color: var(--epq-text); padding: 15px 20px; }
.pitch-stmt {
  font-family: var(--epq-font-display);
  font-variation-settings: var(--epq-big-vf);
  font-size: var(--epq-t-title);
  line-height: var(--epq-lh-tight);
  text-transform: uppercase;
  margin: 0 0 10px;
  /* no measure cap: the card is the measure, and the extra line a 24ch cap
     forced was the line that pushed the split past its screen */
}
.pitch-txt { margin: 0; max-width: 52ch; }
.pitch-txt + .pitch-txt { margin-top: 10px; }
@media (max-width: 900px) {
  .role-split { grid-template-columns: minmax(0, 1fr); gap: 28px; }
}
@media (max-width: 640px) {
  .role-card--glyphs, .role-card--pitch { padding: 16px; }
  /* one station per row on a phone, and the mark steps down with the type */
  .glyph-grid { grid-template-columns: minmax(0, 1fr); }
  .glyph-grid li { grid-template-columns: 26px minmax(0, 1fr); }
  .gg-ic svg { width: 24px; height: 24px; }
}

/* THE SPLIT DEALS ITSELF AS THREE FRAMES ON A PHONE. Stacked, the section is
   three-plus screens tall, and the pager's escape valve (free scroll inside an
   oversized card) left it arriving cut mid-content — the founder's "overflows
   the frame". Below the stacking breakpoint the split's own blocks become
   snap stops: the role headline, the glyph card, the pitch card — one
   gesture, one block, each landing flush under the nav like a card of its
   own. Descendants of the scroll container are legal snap areas, so this is
   pure CSS; the flick animation stays with the outer card, which is right —
   these are stops within a card, not new cards. The query is the exact
   negation of the two-column gate above — stacked because it is too narrow,
   OR stacked because the window is too short — so the stops exist on every
   screen where the section is stacked, and nowhere else. Same reduced-motion gate as
   the pager itself: off the pager the section is an ordinary scrolling
   document and needs none of this. */
@media (prefers-reduced-motion: no-preference) and (max-width: 1150px),
       (prefers-reduced-motion: no-preference) and (max-height: 779px) {
  html.epq-paged .role-hero,
  html.epq-paged .role-stage,
  html.epq-paged .role-card {
    scroll-snap-align: start;
    scroll-snap-stop: always;
    scroll-margin-top: calc(var(--epq-nav-h, 86px) + 10px);
  }

  /* …EXCEPT THE BLOCK THAT OPENS THE SPLIT, WHICH YIELDS ITS STOP TO THE CARD.
     This is the founder's "false stops" (2026-08-11), and it was arithmetic
     rather than bad luck: the card is a snap area too, and the split's first
     block starts one clause bar below the card's top. Measured at 390x844, the
     two mandatory stops landed 62px apart on §2.0 and 73px apart on §3.0 — so
     one gesture in three moved the page by a thumb's width and halted, which is
     precisely what a false stop feels like. scroll-snap-stop: always made it
     unskippable; without it the browser would have flung past the near-duplicate
     and nobody would have seen it.

     The CARD keeps the stop and the opening block loses it, not the other way
     round. The card's stop lands its clause bar flush under the nav — §2.0 THE
     SUITE, the section's own marker — and the opening block is fully on screen
     from there anyway. Yielding the other way scrolls the clause bar up behind
     the bar and the reader arrives at a headline with no section on it.

     Written against the split's STRUCTURE rather than against .role-hero and
     .role-stage by name: the two splits that exist today open with one of those,
     a third could open with its cards, and the invariant being protected is
     "whatever comes first has the card's stop already", not the class list. */
  html.epq-paged .role-split > :first-child,
  html.epq-paged .role-split > :first-child .role-card:first-child {
    scroll-snap-align: none;
  }

  /* THE CARD YIELDS ITS SNAP TO ITS CLAUSE BAR — the other half of the false
     stops, and the half that only shows on a real phone.

     A split section is TALLER THAN THE SCREEN when it stacks: measured at
     360x640, §2.0 is 885px in a 531px frame and §3.0 is 745px, so two of the
     five cards are OVERSIZED SNAP AREAS. The spec's escape valve for those is
     that any position where the snapport sits entirely inside the area is a
     legal rest — 354px of them on §2.0 — so mandatory snap stops being
     mandatory and the reader is set down wherever the flick ran out, mid-glyph
     and mid-sentence. The block above documents that valve as a FEATURE, and on
     a card that is genuinely one long read it is; on a card composed of frames
     it is the bug, because every one of those rest positions is a stop nobody
     designed.

     The cure is to stop the card being an area at all, WITHOUT moving where the
     reader lands. Its clause bar starts at the card's exact top, so a stop on
     the bar puts the section marker flush under the nav — the same arrival to
     the pixel — while the area being aligned is 72px tall instead of 885 and
     can never swallow the snapport. The frames inside then own every other
     stop, and there is no position left that is not one.

     Only in this query, and only for split cards: everywhere else the card IS
     one screen, is not oversized, and keeps its own stop. */
  html.epq-paged .epq-card:has(> .role-split) { scroll-snap-align: none; }
  html.epq-paged .epq-card:has(> .role-split) > .epq-clause-bar {
    scroll-snap-align: start;
    scroll-snap-stop: always;
    scroll-margin-top: var(--epq-nav-h, 86px);
  }
}

/* THE ROLL'S ABERRATION IS ON THE LETTERFORMS HERE TOO. The DS's word-change
   flash splays an outer box-shadow on the column's plate span, and at the
   role split's display size the column box is far larger than the word — the
   flash painted coloured BARS across the headline, the exact failure the
   mantra block above documents ("the box is not the word"). Same cure, scoped
   to the split: the plate span stays the trigger and paints nothing; the
   fringe (epq-mantra-fringe, drop-shadows that follow the glyph silhouettes)
   rides the slug window beside it. The em tracks the headline's own size, so
   one rule serves every width the clamp lands on. */
/* The splay is ABSOLUTE-FLOORED HIGH on purpose. The first cut used
   max(5px, .05em) and the founder reported the effect lost — 5px at 54px
   type for 320ms is below notice, the same finding the click path and the
   mantra each made in turn ("a single instant at peak offset is
   imperceptible"). 8px is the click path's own word-flash order of
   magnitude, and the drift doubles so the 18–55% hold is long enough to
   catch — this flash is ambient, nobody is primed by a click. */
.role-stmt epq-role-roll,
.role-stmt epq-role-roll [data-plate],
.role-stmt epq-role-roll [data-plate] + span {
  --epq-mantra-plate: max(8px, .13em);
}
.role-stmt epq-role-roll [data-plate].epq-plate-enter { animation: none; }
.role-stmt epq-role-roll [data-plate].epq-plate-enter + span {
  animation: epq-mantra-fringe calc(var(--epq-plate-drift) * 2) steps(3) 1 both;
}
.role-stmt epq-role-roll { overflow-clip-margin: max(12px, .16em); }


/* The detail line under a tagline (hero, and any section that sets ledeDetail).
   It is the lede's supporting sentence stepped down ONE role — 34px lede over
   21px body. Per rule 1 there is no size between those two, and no "slightly
   smaller": the hierarchy is the scale step itself. Full ink, not muted — the
   detail carries the value prop, so the drop is size alone. The 12px top margin
   pairs it to the tagline above (tighter than the 24px that sits over a lede),
   so the two read as one unit rather than two loose lines. */
.epq-lede-detail {
  font-size: var(--epq-t-body);
  line-height: var(--epq-lh-body);
  max-width: 46ch;
  margin: 12px 0 0;
}

/* Sub-label inside a section body. Mono, label size, muted — it orients and is
   then ignored, which is the definition of the 13px role. */
h3 {
  font-family: var(--epq-font-mono);
  font-size: var(--epq-t-label);
  font-weight: var(--epq-w-label);
  letter-spacing: var(--epq-track-label);
  text-transform: uppercase;
  color: var(--epq-text-muted);
  margin: 34px 0 12px;
}

/* ---- Upstream correction: the clause number fails AA on the ink bar -----
   REV F bug, reported, and found by scripts/palette-audit.mjs rather than by
   eye — which is the point of having the script.

   The clause number is field-coloured on an ink bar; binding rule 3 calls it
   the field's primary carry down the page. At --epq-t-clause (21px) and
   --epq-w-label (600) it is not "large text", so it needs 4.5:1. On the LIGHT
   theme, against #16180F:

       drift  #E6007E  3.98:1   FAIL
       agents #6B4EFF  3.55:1   FAIL      <- every services/book/detail page
       capture #FF5C00 5.79:1   pass
       explorer, verify         pass comfortably

   Design already solved this exact problem once, for the dark theme's raised
   panel, and wrote the reason next to --epq-f-capture-rev. What the export
   missed is that the -rev variants are not "the dark theme's colours" — they
   are the colours for a DARK GROUND, and an ink bar is a dark ground in both
   themes. So the fix is theirs, applied one scope wider:

       drift-rev  #FF4DA6  5.86:1
       agents-rev #8B72FF  5.08:1
       capture-rev #FF7A33 6.90:1

   On the cyanotype theme this is a no-op — theme.css has already aliased those
   fields onto the same -rev pigments, so the value is unchanged.

   Mixed on --epq-reveal rather than set flat, because the -rev tokens are
   declared raw at :root and are NOT revealed. Used directly they would put a
   saturated violet number on an otherwise monochrome first frame, which is the
   exact failure the reveal block guards against for the base fields. */
[data-epq-field="agents"] .epq-ink-bar {
  --epq-field: color-mix(in srgb, var(--epq-src-agents-rev) calc(var(--epq-reveal) * 100%), var(--epq-expose));
}
[data-epq-field="drift"] .epq-ink-bar {
  --epq-field: color-mix(in srgb, var(--epq-src-drift-rev) calc(var(--epq-reveal) * 100%), var(--epq-expose));
}
[data-epq-field="capture"] .epq-ink-bar {
  --epq-field: color-mix(in srgb, var(--epq-src-capture-rev) calc(var(--epq-reveal) * 100%), var(--epq-expose));
}

/* ---- Reveal timing · founder ------------------------------------------
   1s delay -> 0.25s. Only the delay is overridden; duration, steps and fill
   stay with the export so a regeneration can retune the curve.

   What this actually buys, because steps(3, jump-end) makes it non-obvious:
   jump-end emits 0 through its own first interval, so the VISIBLE HOLD is
   delay + duration/3, not the delay. It goes 1120ms -> 370ms, and the three
   colour jumps land at 370 / 490 / 610ms instead of 1120 / 1240 / 1360ms.
   Retiming again: set the delay to the hold you want minus 120ms. */
html.epq-reveal { animation-delay: .25s; }

/* ---- Founder correction: the light reveal opens on the wrong paper ------
   The export exposes the light theme against pure #FFFFFF, so a page that
   settles on manila (#D9D6C7) opens two-and-a-half stops brighter than it
   ends. On a full-bleed ground that is a white FLASH, not an unexposed
   sheet — the same complaint the dark default exists to answer, arriving on
   the theme a light-sensitive reader switched TO.

   The dark theme already gets this right: its exposure colour is
   --epq-src-cy-ground, i.e. its own settled ground, so the page opens on the
   bare bath and the pigments come up on it. Light is the outlier, and this
   pins it to the same rule — the unexposed sheet is the paper the page is
   printed on.

   Only the ground moves. --epq-expose-ink is untouched, so ink is still black
   from the first frame, --epq-line is still ink and unrevealed, and every
   pigment still mixes over the same three steps. The reveal reads exactly as
   before, minus the flash: a black line drawing on manila that takes its
   colour.

   SCOPED, not global. `:root` here would load after episquire.css and outrank
   its equal-specificity [data-epq-theme="reverse"] rule, dragging the dark
   theme's exposure onto manila too — a white flash on the theme that had
   none. The :not() raises specificity above both and excludes dark outright. */
html:not([data-epq-theme="reverse"]) { --epq-expose: var(--epq-src-manila); }

/* ---- Upstream correction: the dark reveal erases its own line drawing ---
   REV F bug, reported. The reveal's whole premise is a monochrome line drawing
   that gets printed: on manila that works because --epq-ink is deliberately
   NOT revealed ("ink is the one thing already black on the first frame"), and
   --epq-line is ink, so the borders are there from frame one.

   The cyanotype block exempts --epq-cy-ink for the same stated reason but then
   reveals --epq-cy-line:

     --epq-cy-line: color-mix(… var(--epq-src-cy-line) … var(--epq-expose));

   --epq-expose is the bare ground on this theme, so at reveal 0 every border on
   the page computes to exactly the page ground. The drawing has no lines. The
   first 1.12s is a blank field that then grows its own structure, which is the
   opposite gesture from the light theme's.

   The line colour is this theme's drawing colour, not one of its surfaces, so
   it belongs with --epq-cy-ink on the exempt side. Pinned to its source: the
   page opens as #5E8AA6 rules on the bare bath — a blueprint, which is what
   cyanotype means — and the panels, fields and muted text expose against it. */
html.epq-reveal[data-epq-theme="reverse"] {
  --epq-cy-line: var(--epq-src-cy-line);
}

/* ---- Plates on the cyanotype ground · FOUNDER OVERRIDE -----------------
   REV F bans the misregistration plates on dark (theme.css): "they read as a
   screen fault rather than a press artefact once there is no white paper for
   them to slip against." Founder overrides that — the aberration is the
   system's one piece of motion colour and losing it on the default theme
   loses it everywhere that matters.

   Design's objection is real, though, and it is about ENERGY, not hue. A plate
   at 62% is a small darkening over white paper; the identical plate over
   #123048 is a large lightening, so the same values that read as ink slip on
   manila read as a backlit glow on the ground. So this does not simply delete
   their rule — it restores the animation and re-specifies the plates lower.

   The alpha was 38% on that reasoning and the founder read it as too muted on
   the ground — real eyes beat the argument, so it is back to 62%, parity with
   the light theme and the design system's own number. Note the dark theme
   already substitutes the LIGHTER -rev pigments for magenta and orange, so
   62% here is not the same stimulus as 62% on manila; it is stronger. If it
   still reads as too quiet, this is the knob and it can go past 62 — the
   plates are a fixed triad and owe nothing to any contrast floor, because
   they never carry text and never persist. */
[data-epq-theme="reverse"] {
  --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);
}
/* Same selectors as the ban, and this sheet loads later, so equal specificity
   wins. Restoring the shorthand rather than `animation-name` keeps duration,
   easing and fill in one place with the light-theme rule. */
[data-epq-theme="reverse"] .epq-plate-press:active {
  animation: epq-press-plates var(--epq-plate-flash) steps(3) 1 both;
}
[data-epq-theme="reverse"] .epq-plate-enter,
[data-epq-theme="reverse"] [data-plates="fire"] {
  animation: epq-misregister var(--epq-plate-drift) steps(3) 1 both;
}

/* The DS's dark-theme ink-bar treatment enumerates the band corner tabs it
   knew about (.rec-tab, .br-tab) when exempting chips from the 2px field
   rule. The two bands added 2026-07-28 carry the same chips; exempt them
   here — this sheet loads later, so equal specificity wins. Offered
   upstream: the export should key the exemption off a class, not a list. */
[data-epq-theme="reverse"] .mp-tab,
[data-epq-theme="reverse"] .ag-tab { border-top: 0; }

/* ---- Click aberration on links too · FOUNDER --------------------------
   The CMY press-plate splay is the system's one piece of motion colour, and
   the founder wants every pressable thing to earn it on click. Buttons
   already opt in (.epq-plate-press on .epq-btn) and so does the figure play
   control; this widens the reward to the site's links.

   A button is a block with a resting ink shadow, so epq-press-plates suits it
   verbatim — the plates splay while the 5px shadow collapses, which IS the
   press-down. A link is a WORD, and the DS rule for a word is "cancel the
   resting shadow" (see --epq-plate-shadow, foundations.css) so no drop shadow
   prints behind the text and the word does not translate — only the colour
   splits. epq-press-plates-word is epq-press-plates with the 4th (ink) shadow
   layer removed and everything else — the CMY triad, the --epq-plate-flash
   duration, steps(3) — held identical, so it reads as the same effect. The
   global reduced-motion rule (foundations.css) still cancels all of it, which
   leaves the plain :active state as the correct degradation.

   Not theme-scoped: the plate tokens already resolve per theme (the founder
   override restores them on the cyanotype ground), so one rule covers both. */
@keyframes epq-press-plates-word {
  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); }
  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); }
  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); }
  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); }
}
.epq-nav a:active,
.epq-wm:active,
.epq-det-link:active,
footer a:active {
  animation: epq-press-plates-word var(--epq-plate-flash) steps(3) 1 both;
}

/* .epq-fig-play:active sets animation:none to stop its idle breathe, which
   also cancels the plate press it opts into via .epq-plate-press. Re-assert it
   here (later sheet, equal specificity) so the play control aberrates on click
   like every other button rather than pressing in monochrome. */
.epq-fig-play:active {
  animation: epq-press-plates var(--epq-plate-flash) steps(3) 1 both;
}

/* JS-fired variant of the press splay. The .epq-firing class is added on
   pointerdown / keyboard activation by the delegated handler in head()
   (util.mjs) and removed on animationend, so the splay plays start-to-finish
   even on a fast click — which the :active rules above cannot guarantee once
   the mouse is released early.

   These keyframes are LOUDER than the DS :active flash on purpose. The bare
   flash — 180ms, 7px, and a single instant at peak offset — is imperceptible
   in real motion however reliably it fires (the founder's report), so the
   fired path reaches a larger misregistration FAST and then HOLDS it (18%→55%)
   so the eye catches the colour split before it snaps back. Still one flash,
   still under the DS ~340ms ceiling. The DS :active keyframes stay untouched as
   the no-JS fallback; only what the JS drives is tuned here.

   Block keeps its resting hard shadow throughout (--epq-plate-shadow, constant
   like epq-misregister) so a button does not appear to un-press while the
   plates splay over it. A word carries no shadow layer at all. */
@keyframes epq-fire-block {
  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); }
  18%  { box-shadow: -13px 9px 0 0 var(--epq-plate-c), 13px -8px 0 0 var(--epq-plate-m), 5px 14px 0 0 var(--epq-plate-y), var(--epq-plate-shadow); }
  55%  { box-shadow: -13px 9px 0 0 var(--epq-plate-c), 13px -8px 0 0 var(--epq-plate-m), 5px 14px 0 0 var(--epq-plate-y), var(--epq-plate-shadow); }
  80%  { box-shadow: -5px 4px 0 0 var(--epq-plate-c), 5px -3px 0 0 var(--epq-plate-m), 2px 5px 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); }
}
@keyframes epq-fire-word {
  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); }
  18%  { 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); }
  55%  { 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); }
  80%  { 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); }
  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); }
}
.epq-btn.epq-firing,
.epq-fig-play.epq-firing {
  animation: epq-fire-block 300ms steps(3) 1 both;
}
.epq-nav a.epq-firing,
.epq-drawer a.epq-firing,
.epq-wm.epq-firing,
.epq-det-link.epq-firing,
footer a.epq-firing {
  animation: epq-fire-word 300ms steps(3) 1 both;
}

/* ---- Figure ground dim (phones only) -------------------------------------
   Ruling: decisions/2026-07-29-mobile-figures-miniature-grammar.md (which
   superseded the same-day no-camera ruling this layer arrived with — the dim
   is the part that survived).

   House lights. While a figure plays on a phone, the GROUND goes down and the
   sheets standing on it stay lit — attention on the figure with nothing
   moving. Under the wire ruling (2026-07-29, third of the day) it plays
   beneath the drafted wires: the dim drops the ground, the wires draft the
   relationships across it, the sheets stay lit.

   The layer dims the GROUND only. It sits above the band's background and its
   depth grid and BELOW the camera's content, so cards, chips and rules keep
   full strength and GAIN contrast as the ground drops away beneath them. The
   corner tabs stay above it untouched: they are the film frame, not the
   picture.

   It is light, not pigment. Near-black at low alpha darkens the cream ground in
   the light theme and deepens the cyanotype ground in dark, so it derives in
   both themes with nothing authored per-theme — the complement of the
   aerial-perspective veil, which mixes toward --vellum because distance adds
   air, where this one mixes toward ink because it takes light away.

   Animating opacity alone keeps it on its own layer and never repaints the grid
   beneath it. Rest is fully transparent and the dim exists ONLY as an
   animation, so the global prefers-reduced-motion kill in episquire.css lands
   the reader on the undimmed sheet — the resting frame stays the finished
   sheet, which the camera ruling requires.

   Consumed by all four figure bands (record, backbone, agents, branch); each
   applies it over its own run length, the same way they share epq-misregister.

   The alpha is the one thing that CANNOT derive from a single value, and it is
   physics rather than taste. Taking light away from a ground that is already
   near the floor has almost nowhere to go: the same veil that drops the cream
   ground by a luminance ratio of 1.39 moves the cyanotype panel by 1.06, which
   is not a dimming, it is nothing. Even pure black only reaches 1.54 there, so
   no shared number serves both. The reverse theme therefore spends the headroom
   it actually has (42% → 1.14) and stops short of the black that would erase
   the grid with it — the grid stays faintly legible, because a dimmed sheet is
   still a sheet. Same exception, and the same reason, as the fields remapped on
   the ground in episquire.css: authored per-theme only where deriving is not
   physically available. */
:root { --epq-fig-dim-ink: color-mix(in srgb, var(--epq-src-ink) 16%, transparent); }
[data-epq-theme="reverse"] { --epq-fig-dim-ink: color-mix(in srgb, var(--epq-src-ink) 42%, transparent); }
@keyframes epq-fig-dim {
  0%   { opacity: 0 }
  9%   { opacity: 1 }
  88%  { opacity: 1 }
  100% { opacity: 0 }
}
/* The global reduced-motion kill in episquire.css is authored on `*`, and `*`
   does not match a generated pseudo-element — so a dim living on ::after runs
   straight through it, for exactly the readers who asked motion to stop. Killed
   explicitly here. It has to be !important and it has to be in THIS file: the
   band CSS is inlined into the page, so it lands after this stylesheet and wins
   every specificity tie, and episquire.css is vendored — hand-editing it would
   be undone by the next re-vendor. !important for a reduced-motion kill is the
   house pattern, not an exception to it; the prohibition noted in episquire.css
   is on the reveal opting OUT of that kill, which is the opposite case. */
@media (prefers-reduced-motion: reduce) {
  .rec-band::after, .mp-band::after, .ag-band::after, .br-band::after {
    animation: none !important; opacity: 0 !important;
  }
}

/* ---- PAGE MANTRA -----------------------------------------------------------
   Ruling: decisions/2026-07-31-page-mantra-exception.md. After The Designers
   Republic: massive title type as logo/identity. The mantra is the ONE
   sanctioned exception to the type scale's ceiling — it sizes on its MEASURE
   (the longest line filling up to 90% of the screen, any platform, fitted by
   static/epq-mantra.js), not on a scale step, and it may run tighter than
   display tracking, down to a 1px GUARD: gaps may approach one pixel; glyphs
   never touch. Everything else on the site keeps the rules, including this
   file's own "never write a px font-size" law — the computed px the fitter
   writes are authorised by the ruling and exist nowhere in authored CSS.

   ONE SIZE FOR EVERY WORD (founder, 2026-07-31). The block is a solid slab of
   uniform caps; the longest line sets the size and the shorter lines are
   simply shorter. No per-line scaling, and the rolling word is the same size
   as the words above and below it — the fitter measures the roll's own ghost
   alongside the static lines so the common size clears all of them.

   The em tracking below is the aggressor at mantra sizes (−0.04em at 300px is
   −12px between letters); the founder's 1px is the guard it is tuned against,
   on Archivo wdth 118 uppercase, worst pairs. Leading 0.76 on uppercase (no
   descenders) leaves a few px between cap rows — tune toward 0.73 to approach
   the 1px floor, never past touching. */
/* THE HORIZONTAL-SCROLL GUARD LIVES HERE, not on the stage. This box is a
   .doc > * with max-width:none, and neither .doc nor body nor html carries
   horizontal padding, so it IS the viewport width: clipping at its padding
   box cuts exactly where the page would have scrolled and nowhere sooner.
   It contains everything the stage's own clip used to (the pre-fit line
   overflow before the fitter runs, sub-pixel slack, the roll's out-of-flow
   ghost) while leaving the plate splay free to reach into the gutter the
   centred stage leaves either side.

   overflow-x:clip with overflow-y left visible is legal — `clip` is the one
   overflow value that does not blockify its partner — so the plates still
   splay VERTICALLY onto the neighbouring line, which is most of the effect.
   And with no clip-margin declared anywhere, no clip-margin region is ever
   counted toward the page's scrollable overflow, so the splay can grow with
   the type without ever manufacturing the scrollbar it used to guard. */
.epq-mantra { display: block; max-width: none; overflow-x: clip; }
.epq-mantra { --epq-mantra-track: -0.04em; --epq-mantra-lh: 0.76; --epq-mantra-plate-k: .06; }
.epq-mantra-stage {
  position: relative;
  width: 88vw; /* no-JS fallback measure — under the classic-scrollbar budget;
                  the fitter replaces it with clientWidth-derived px */
  margin: 0 auto;
  /* The padding is TOKENISED because an absolutely-positioned overlay resolves
     against the padding box, not the content box: anything that wants to sit
     square on the type has to inset itself by exactly these. One place to
     change them, and the strike hero's small line follows. */
  --epq-mantra-pt: 40px;
  --epq-mantra-pb: 36px;
  padding: var(--epq-mantra-pt) 0 var(--epq-mantra-pb);
  container-type: inline-size;
  /* No clip here. It used to carry the scroll guard, but a 16px clip margin
     also shaved the plate splay — and at 320px, where the gutter is exactly
     16px, the margin region was itself on the verge of recreating the
     scrollbar it existed to prevent. The guard moved up to .epq-mantra. */
}
.epq-mantra h1 { position: relative; z-index: 1; margin: 0; letter-spacing: var(--epq-mantra-track); line-height: var(--epq-mantra-lh); }
/* Specificity 0,2,1, and it needs every bit of it: the phone compaction block
   near the foot of this file sets .epq-hero h1 { margin-bottom: 14px }, which
   matches a mantra hero too (it is an .epq-hero) at equal specificity and
   later in the file. That margin exists for the CLASSIC hero, where a lede
   follows the headline inside the same box. Nothing follows a mantra's h1 —
   the stage's own padding is the spacing, and the tail is a separate block —
   so on a phone it was making the stage 14px taller than its own type, which
   is exactly the kind of quiet offset anything positioning itself against the
   type has to fight. */
.epq-hero.epq-mantra h1 { margin: 0; }
/* one size, every line — including the roll's. The fallback below is what a
   no-JS reader gets; the fitter overwrites all of them with one computed px. */
.epq-mantra-line { display: block; width: 100%; min-width: 0; white-space: nowrap; font-size: 9cqw; }

/* THE ROLLING PHRASE BREAKS ACROSS LINES. The element lays its word columns
   out side by side; in a mantra they STACK, one word per line, which is what
   lets the block fill the vertical space — with the longest line down to a
   single word, height becomes the binding constraint instead of width.

   Done by turning the element's own flex axis, not by splitting it into one
   element per word: one element is one timer, so the halves of the phrase
   cannot drift apart, and the element's own rule — a word shared by
   consecutive roles holds still while its neighbour steps — survives intact.
   Both the window and the GHOST have to turn, or the ghost still measures
   the words side by side and the fit comes out too small. Neither sets
   flex-direction inline, so no !important is needed; the gap is handled at
   the source instead, by the gap="0" the mantra passes the element. */
.epq-mantra epq-role-roll [data-ghost],
.epq-mantra epq-role-roll [data-window] { flex-direction: column; }

/* THE ABERRATION IS ON THE LETTERFORMS HERE, NOT ON THE BOX.
   Founder: "the chromatic aberration effect on the rolling words is clipped
   by nearby words." Unclipping it (the guard move at the top of this
   section, and the roll-host margin below) turned out to be a third of the
   problem. The other two:

   · IT IS A BOX SHADOW, AND THE BOX IS NOT THE WORD. The design system's
     plate is an empty span at inset:0 of the column, splayed with an outer
     box-shadow. On a chip or a table row the box IS roughly the word, so an
     offset copy of it reads as ink slipping. In a mantra the column box is
     the full measure — some 800x117px — while the word inside it is much
     smaller, so the same effect paints thin coloured RULES along the box's
     edges, crossing the type and nowhere near the glyphs. Bigger offsets
     only make the rules bigger. So the mantra takes the same registration
     geometry to the LETTERFORMS: a filter of three drop-shadows, which
     follow the alpha of what is actually rendered — the word's silhouette —
     and paint outside the element's own box, so the clip that makes a roll
     a roll (overflow:hidden on the slug window) cannot eat them.
   · TIME. The DS holds its peak for a single step: ~37ms at steps(3) over
     320ms. This site has already found that imperceptible and written it
     down, for the click path, further up this file — "a single instant at
     peak offset is imperceptible in real motion however reliably it fires
     (the founder's report)" — which is why epq-fire-word reaches its offset
     fast and HOLDS it. The mantra's flash is AMBIENT: nobody is clicking, so
     nobody is primed to look. It needs the hold more than the click path
     does, not less. Same shape here: 18% -> 55%.

   Ratios are the design system's own, over 10: cyan -1.0/+0.7, magenta
   +1.0/-0.6, yellow +0.4/+1.1, decaying at 0.4x. Only the unit changes, to
   an em that tracks the type. Not theme-scoped — the plate pigments already
   resolve per theme, and the founder override further up this file long
   since restored the plates on the cyanotype ground, so this reads on both.

   Chained drop-shadows do shadow each other's output; at these offsets and
   62% alpha the compound is a fourth, fainter ghost between the plates,
   which is what a real three-pass misregistration looks like anyway. */
@keyframes epq-mantra-fringe {
  0%, 100% {
    filter: drop-shadow(0 0 0 transparent) drop-shadow(0 0 0 transparent) drop-shadow(0 0 0 transparent);
  }
  18%, 55% {
    filter:
      drop-shadow(calc(var(--epq-mantra-plate) * -1) calc(var(--epq-mantra-plate) * .7) 0 var(--epq-plate-c))
      drop-shadow(var(--epq-mantra-plate) calc(var(--epq-mantra-plate) * -.6) 0 var(--epq-plate-m))
      drop-shadow(calc(var(--epq-mantra-plate) * .4) calc(var(--epq-mantra-plate) * 1.1) 0 var(--epq-plate-y));
  }
  80% {
    filter:
      drop-shadow(calc(var(--epq-mantra-plate) * -.4) calc(var(--epq-mantra-plate) * .3) 0 var(--epq-plate-c))
      drop-shadow(calc(var(--epq-mantra-plate) * .4) calc(var(--epq-mantra-plate) * -.2) 0 var(--epq-plate-m))
      drop-shadow(calc(var(--epq-mantra-plate) * .2) calc(var(--epq-mantra-plate) * .4) 0 var(--epq-plate-y));
  }
}
/* 1em here IS the mantra's own size: the roll's internal fit ratio is pinned
   at exactly 1 in a mantra, because the fitter sets the stage width from the
   widest run INCLUDING the roll's ghost, so the element never has to scale
   itself down off the common size. The px floor keeps a phone from dropping
   under the system's own absolute splay. */
.epq-mantra epq-role-roll,
.epq-mantra epq-role-roll [data-plate],
.epq-mantra epq-role-roll [data-plate] + span {
  --epq-mantra-plate: max(9px, calc(var(--epq-mantra-plate-k) * 1em));
}
/* The plate span stays as the TRIGGER and keeps its timing — the element's
   own JS restarts it on every word change — but paints nothing here: its box
   is the wrong shape at this scale (see above). The fringe rides the slug
   window next to it, which is the box that actually contains the glyphs.
   Specificity (0,3,1) beats both the design system's ban and the founder
   override further up this file, so only the mantra's own plates change —
   the 404's number still gets the DS keyframe verbatim. */
.epq-mantra epq-role-roll [data-plate].epq-plate-enter {
  animation: none;
}
.epq-mantra epq-role-roll [data-plate].epq-plate-enter + span {
  animation: epq-mantra-fringe var(--epq-plate-drift) steps(3) 1 both;
}
/* The standing epq-role-roll clip (further down this file) sizes its margin
   against the DS's fixed 11px. The splay tracks the type now, so the margin
   has to as well: 1.25k clears the largest offset (the yellow plate's 1.1k)
   with slack for rounding. Higher specificity than that rule, so it wins
   despite sitting earlier in the file. */
.epq-mantra epq-role-roll {
  overflow-clip-margin: max(11px, calc(var(--epq-mantra-plate-k) * 1.25em));
}

/* THE ONE OVERLAY. Selective, by ruling: the eyebrow is small, sits in the
   stage's own top margin, and never crosses a letterform. z 2 over the
   mantra's 1 — the whole assembly stays inside .doc (z 1), far below the
   nav's 79. Everything else that used to be overlaid now lives in the tail. */
.epq-mantra-ov { position: absolute; z-index: 2; margin: 0; }
.epq-mantra-ov--tl { top: 8px; left: 2px; }

/* ---- THE STRIKE HERO (home page) ------------------------------------------
   Ruling: decisions/2026-07-31-strike-hero.md. A page mantra with a STATE.
   "kill giants" is set gigantic but GHOSTED — hairline outline, unfilled —
   until the reader pushes the button, at which point it inks with a flash of
   the chromatic aberration and keeps flashing intermittently.

   The outline is the system's existing ghost idiom, not a new one: the branch
   band draws the option it did not take as a hairline unnumbered face, and
   the agents band's undocked card is the same trick. Here it earns a second
   meaning — a plate that has not printed yet — which is why the fill reads as
   the press finally taking, and why it had to be an outline rather than a
   simple opacity fade. -webkit-text-stroke is the only way to stroke text
   without duplicating it in the DOM; every browser the site supports has it
   under that prefix and no standard property has shipped.

   RESTING STYLES ARE THE FINISHED SHEET, as everywhere on this site: the
   filled state needs no rule, and the GHOST is what gets added — and only
   where a button can actually be pressed. Without scripting the class never
   comes off, so the ghost is gated to (scripting: enabled); under reduced
   motion it is dropped too, because a reader who asked motion to stop should
   not be left holding an unfinished sheet behind a control whose whole job is
   to animate. Same gate the record band uses for its SCATTERED start.

   THE PRESS RUNS ON ARRIVAL (founder, 2026-08-02: the click-gated reveal
   was "an underwhelming muddle" — the vital text was functionally invisible
   in the frame most visitors ever saw, and the payoff for those who did
   press lasted a third of a second). The scanline/interference murk is
   GONE; the ghost is now only the first BEAT of an entrance that runs by
   itself: epq-strike.js puts .epq-strike-printing on the hero shortly after
   load, the sequence below prints the giant — cyan plate, magenta, yellow,
   each slammed in out of register, pulled toward register, then the ink
   lands — and the armed class comes off at its end. Every visitor sees the
   big text arrive as an event; nobody has to earn it.

   THE PRINT IS A FLASH NOW (founder, 2026-08-20: "the initial chromatic
   effect on the text should happen faster - 0.2 seconds"). The six beats
   keep their shape and their order; the whole sequence runs in the one
   duration token below — 200ms, so each plate holds for roughly two frames
   and the entrance reads as one chromatic SNAP rather than a printing you
   watch. It is the opening beat of the giant's fall (see the strike tale in
   src/templates/strike-fall.mjs), which is where the watching now happens.

   THE PLATES ARE TEXT-SHADOWS, deliberately: text-shadow paints the glyph
   SILHOUETTE at the shadow's own colour and full alpha even while the fill
   is transparent, which is exactly a plate that has printed before the ink
   — and unlike the box-shadow plates it needs no extra DOM at mantra scale.
   Offsets are em, so the splay tracks the type. Colours are the process
   plate tokens — this is MOTION, their sanctioned use. */
@media (scripting: enabled) {
  .epq-strike-armed .epq-mantra-line {
    color: transparent;
    -webkit-text-stroke: var(--epq-strike-rule, 2px) color-mix(in srgb, var(--epq-text) 40%, transparent);
  }
}
@keyframes epq-strike-print {
  0%, 15% { color: transparent; text-shadow: none }
  16%, 32% { color: transparent; text-shadow: .055em .038em 0 var(--epq-plate-c) }
  33%, 49% { color: transparent; text-shadow: .055em .038em 0 var(--epq-plate-c), -.05em .028em 0 var(--epq-plate-m) }
  50%, 65% { color: transparent; text-shadow: .055em .038em 0 var(--epq-plate-c), -.05em .028em 0 var(--epq-plate-m), .018em -.052em 0 var(--epq-plate-y) }
  66%, 81% { color: transparent; text-shadow: .014em .01em 0 var(--epq-plate-c), -.012em .007em 0 var(--epq-plate-m), .005em -.013em 0 var(--epq-plate-y) }
  82%, 100% { color: var(--epq-text); text-shadow: none; -webkit-text-stroke-width: 0 }
}
.epq-strike-printing .epq-mantra-line {
  animation: epq-strike-print var(--epq-strike-print, 200ms) steps(1, jump-end) 1 both;
}
@media (prefers-reduced-motion: reduce) {
  .epq-strike-armed .epq-mantra-line { color: var(--epq-text); -webkit-text-stroke-width: 0; }
}
/* The stroke tracks the type, so the outline reads as one weight of line at
   every size rather than a hairline on a phone and a slab at 2560. Floored so
   it never falls under the system's own hairline. */
.epq-strike .epq-mantra h1,
.epq-strike h1 { --epq-strike-rule: max(1.5px, .012em); }

/* THE SMALL LINE STRADDLES THE GIANT'S TOP EDGE (founder, 2026-08-02:
   "aligned to the left and have about 50% overlap on the kill giants text").
   Silkscreen, the site's one pixel face, at --epq-t-statement — 27px on a
   phone against ~75px of mantra, 55px at desktop against ~250px, so the
   scale contrast the mantra exception is built on survives.

   Left-aligned on the TYPE's own left edge, and hung on the h1's top edge
   with a -50% translate: half the line rides ON the giant, half sits in the
   air above it — the "about 50% overlap" asked for, and it stays 50% at
   every size because the anchor is an edge, not a length. The inset is
   var(--epq-mantra-pt) because that IS the h1's top inside the stage's
   padding box (the stage hugs the type; the h1 is its only in-flow child).

   It is ALWAYS in front — z 2 over the h1's 1, both frames. What changes on
   the push is its COLOUR: over the faint ghost it is the sheet's own text
   colour, and once the giant inks in the same colour underneath it, it
   swaps to the page's field orange to stay readable — half of it now sits
   on solid ink, and orange is the one pigment this view owns that reads on
   both the fill and the ground, in both themes. Resting styles are the end
   state, so the FIELD colour is the default and the armed text colour is
   what gets added, behind the same scripting gate as the ghost — a no-JS or
   reduced-motion reader lands on the filled sheet and needs the contrast
   colour from the first paint. */
.epq-strike-small {
  position: absolute;
  top: var(--epq-mantra-pt);
  left: 0;
  transform: translateY(-50%);
  pointer-events: none;
  font-family: "Silkscreen", "IBM Plex Mono", monospace;
  font-size: var(--epq-t-statement);
  font-weight: 400;
  line-height: 1;
  letter-spacing: .1em;
  text-transform: lowercase;
  color: var(--epq-f-capture);
}
@media (scripting: enabled) {
  .epq-strike-armed .epq-strike-small { color: var(--epq-text); }
}
@media (prefers-reduced-motion: reduce) {
  .epq-strike-armed .epq-strike-small { color: var(--epq-f-capture); }
}

/* THE HERO FILLS THE SCREEN BEFORE THE FOLD (founder, 2026-08-02, "on any
   device") — a deliberate, scoped reversal of the mantra's own "earns its
   screen" contract, which still holds on /how-it-works. The strike hero has
   a STATE and a control, and the button-then-arrow story needs the fold to
   be ITS fold: the arrow points at a place, and that place must be exactly
   one screen down.

   The nav is sticky above, so the claim is viewport minus nav: the fallback
   var is the nav's resting height per breakpoint, and epq-strike.js writes
   the measured height over it. svh, so a phone's collapsing toolbar never
   makes the hero taller than the screen it actually has. The stage centres
   in the claimed height; the button and arrow anchor to the HERO's own
   corners, not the stage's, because the stage now floats mid-screen. */
.epq-strike {
  --epq-nav-h: 86px;
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: calc(100vh - var(--epq-nav-h));
  min-height: calc(100svh - var(--epq-nav-h));
}

/* THE PLUNGER — A CONTROL VOCABULARY, CURRENTLY WITHOUT A CONSUMER.
   A raised ISOMETRIC BLOCK in the site's block grammar (the same three-face
   recipe as FIG 1.1: host = left face, ::before = right face, ::after = top
   face; one hue shaded toward ink and vellum), sitting in a dark socket,
   its label riding above as a flat chip exactly like the board's brand
   chips — text stays flat and legible, the block does the volume. Pressing
   drives the cap DOWN into the socket in one hard cut: no easing, because a
   plunger is a mechanism, not a cushion.

   It was the strike hero's button for one day. The founder retired that
   button (2026-08-02: the type itself now takes the touch) but explicitly
   kept the PATTERN — "we can use that pattern elsewhere though" — so this
   block stays, unattached to any page, waiting for the control that wants
   it. It is scoped to .epq-plunger and knows nothing about the hero; a
   consumer supplies the positioning and the click handler. If a year passes
   with no consumer, delete it — an unused component is a liability, and the
   only thing keeping this one is an explicit instruction to keep it. */
.epq-plunger {
  --plw: 92px;  /* top-face box width */
  --pld: calc(var(--plw) * .5774);
  --plh: 22px;  /* cap extrusion */
  --plc: var(--epq-f-capture);
  position: relative;
  display: block;
  width: calc(var(--plw) + 14px);
  height: calc(var(--pld) + var(--plh) + 44px);
  padding: 0; margin: 0;
  background: none; border: 0; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.epq-plunger-label {
  /* right-aligned to the block: the chip is wider than the cap, and a
     consumer will usually hang this off a page gutter where a centred chip
     would graze the edge. */
  position: absolute; top: 0; right: 0;
  font-family: "IBM Plex Mono", monospace;
  font-size: var(--epq-t-label); font-weight: 600; letter-spacing: .14em;
  text-transform: uppercase; white-space: nowrap;
  color: var(--epq-text);
  background: var(--epq-surface-card); border: var(--epq-hair);
  padding: 3px 8px;
}
/* the socket: a shallow dark slab the cap sinks into */
.epq-pl-base, .epq-pl-cap { position: absolute; left: 50%; bottom: 0; transform: translateX(-50%); }
.epq-pl-base {
  --pbw: calc(var(--plw) + 14px); --pbd: calc(var(--pbw) * .5774);
  width: var(--pbw); height: calc(var(--pbd) + 8px);
  background: color-mix(in srgb, var(--plc) 30%, var(--epq-ink));
  clip-path: polygon(50% 0, 100% calc(var(--pbd) / 2), 100% calc(var(--pbd) / 2 + 8px), 50% 100%, 0 calc(var(--pbd) / 2 + 8px), 0 calc(var(--pbd) / 2));
}
.epq-pl-cap {
  width: var(--plw); height: calc(var(--pld) + var(--plh));
  bottom: 10px;
  background: color-mix(in srgb, var(--plc) 78%, var(--epq-ink));
  clip-path: polygon(50% 0, 100% calc(var(--pld) / 2), 100% calc(var(--pld) / 2 + var(--plh)), 50% 100%, 0 calc(var(--pld) / 2 + var(--plh)), 0 calc(var(--pld) / 2));
}
.epq-pl-cap::before {
  content: ""; position: absolute; inset: 0;
  background: color-mix(in srgb, var(--plc) 55%, var(--epq-ink));
  clip-path: polygon(50% var(--pld), 100% calc(var(--pld) / 2), 100% calc(var(--pld) / 2 + var(--plh)), 50% 100%);
}
.epq-pl-cap::after {
  content: ""; position: absolute; inset: 0;
  background: color-mix(in srgb, var(--plc) 82%, var(--epq-src-vellum));
  clip-path: polygon(50% 0, 100% calc(var(--pld) / 2), 50% var(--pld), 0 calc(var(--pld) / 2));
}
/* the push: one hard cut down into the socket, one hard cut back. The
   slamming class holds it down for the plates' first beat so a keyboard
   press reads the same as a pointer press. */
.epq-plunger:active .epq-pl-cap,
.epq-plunger.is-pressed .epq-pl-cap { bottom: 2px; }
.epq-plunger:focus-visible { outline: 2px solid var(--epq-f-capture); outline-offset: 4px; }

/* The scroll indicator: a video-game direction arrow at the foot of the hero,
   on the RIGHT (founder, 2026-08-02) — the same edge as the button, so the
   push and the place it points you next share one column. Drawn as a SPRITE:
   ten hard pixels on a 5x6 grid, one element and a box-shadow list, because a
   smooth triangle would be the one thing on this page pretending not to be
   made of squares.

   IT IS A LINK NOW, not decoration: clicking or touching it scrolls to the
   next section (founder, same note). An anchor rather than a script, so it
   works without JS — the travel itself is the scroll-behavior rule below,
   which is CAMERA, the one class of motion allowed to ease, and it is gated
   off under reduced motion where the jump is instant. The padding is the tap
   target: the sprite is 40x48 and a finger needs more than that.

   And it BOUNCES — but in steps, not in easing. The site's binding rule is
   that nothing eases except a rostrum camera, and an eased bob would break it
   for decoration. Three hard positions is also simply more correct for the
   register it is borrowing: an arcade indicator was drawn on a sprite sheet,
   not tweened.

   Hidden until the button is pushed — there is nothing to scroll to until the
   mantra has inked. Resting styles are the finished sheet, so the arrow is
   PRESENT by default and the armed state is what hides it, gated to
   (scripting: enabled) like the ghost above. */
/* .epq-hop is the SHARED sprite — the strike hero's arrow and the frame
   sections' hops are the same object, so the page has one way of saying
   "the next thing is below". Consumers add a positioning class. */
.epq-hop {
  --u: 8px;
  position: absolute;
  /* right side, but clear of the theme switch: the FAB owns the corner
     itself (fixed, --epq-fab-inset + --epq-fab-size), so the hop stands
     just inboard of it on the same foot line. */
  right: calc(var(--epq-fab-inset, 24px) + var(--epq-fab-size, 56px) + 20px);
  bottom: 18px; z-index: 3;
  display: block;
  width: calc(var(--u) * 5); height: calc(var(--u) * 6);
  padding: 12px;
  animation: epq-strike-bob 900ms steps(1) infinite;
}
.epq-hop i {
  position: absolute; top: 12px; left: 50%;
  width: var(--u); height: var(--u); margin-left: calc(var(--u) * -.5);
  background: var(--epq-f-capture);
  box-shadow:
    0 var(--u) 0 var(--epq-f-capture),
    0 calc(var(--u) * 2) 0 var(--epq-f-capture),
    calc(var(--u) * -2) calc(var(--u) * 3) 0 var(--epq-f-capture),
    0 calc(var(--u) * 3) 0 var(--epq-f-capture),
    calc(var(--u) * 2) calc(var(--u) * 3) 0 var(--epq-f-capture),
    calc(var(--u) * -1) calc(var(--u) * 4) 0 var(--epq-f-capture),
    0 calc(var(--u) * 4) 0 var(--epq-f-capture),
    calc(var(--u) * 1) calc(var(--u) * 4) 0 var(--epq-f-capture),
    0 calc(var(--u) * 5) 0 var(--epq-f-capture);
}
@media (scripting: enabled) { .epq-strike-armed .epq-strike-arrow { display: none; } }
/* the frame hop scrolls within its own frame, whose foot it marks */
.body-frame-hop { bottom: 14px; }
@keyframes epq-strike-bob {
  0%   { transform: translateY(0) }
  33%  { transform: translateY(5px) }
  66%  { transform: translateY(9px) }
}

/* THE ABERRATION, AND THE TYPE AS THE INSTRUMENT.
   epq-strike.js puts .epq-plate-enter on the h1 to flash it — but the design
   system's own rule for that class splays an outer BOX shadow, and the h1's
   box here is the whole mantra: it would paint coloured rules around the
   block instead of a fringe on the type. So the strike takes the mantra's
   letterform fringe (the filter of drop-shadows defined above), which follows
   the glyph silhouettes at any box size. The token has to resolve on the h1
   for the em to be the mantra's own size.

   THE GIANT IS NOW THE CONTROL (founder, 2026-08-02: "remove the button
   entirely… the text will show the chromatic effect on touch/click. Quickly
   repeated touches/clicks intensify the effect"). Two things carry that:

   · --epq-strike-heat, 0 to 1, written by the script from how fast the taps
     are coming. It widens the splay (the plate token scales with it) and
     shortens the flash (the duration divides by it), so a hammered mantra
     throws its plates further and re-fires quicker — "faster/broader",
     exactly. Heat DECAYS on a timer, so the intensity is momentary and the
     resting behaviour is always the calm single flash.
   · The multiplier tops out at 1.8x the base splay, and that ceiling was
     found by looking: 3.4x measured 50px of throw at 1280 and the plates
     stopped reading as misregistration altogether — they became three
     separate coloured words behind the ink, which is a different and much
     worse effect, and 2.2x still let the yellow plate fall a
     third of a cap height clear of the baseline. 1.8x is the most throw that still reads as ONE object
     slipping. The same ceiling the mantra ruling hit when it tried the
     design system's own px ratio at poster scale.

   The heat is a NUMBER in a var, not a set of classes, because it is a
   continuous quantity and the alternative is five state classes that all
   have to be mutually exclusive. Nothing eases: the flash itself is still
   the site's stepped keyframe, and heat only changes its two dials. */
.epq-strike h1 {
  --epq-strike-heat: 0;
  /* --epq-mantra-size is the FITTED size, published by the fitter. NOT 1em:
     the h1 carries the hero type step (89px) while the mantra renders at
     whatever the fit produced (247px at 1280, 75px on a phone), so an em
     here measures type that is not on the screen and the splay stops
     tracking the size it is supposed to belong to. */
  /* HEAT MULTIPLIES THE FLOORED BASE, it is not floored WITH it. Written the
     other way round the 7px floor swallows the heat on a phone — base and
     hammered both clamp to 7px, and touch is exactly where the drumming
     happens — so the one platform the gesture was designed for felt nothing. */
  --epq-mantra-plate: calc(max(7px, var(--epq-mantra-plate-k) * var(--epq-mantra-size, 1em)) * (1 + var(--epq-strike-heat) * .8));
  cursor: pointer;
  /* rapid tapping must not select the headline or summon the double-tap
     zoom — both turn a percussion instrument into a text field. */
  -webkit-user-select: none;
  user-select: none;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}
.epq-strike h1.epq-plate-enter {
  animation: epq-mantra-fringe calc(var(--epq-plate-drift) * (1 - var(--epq-strike-heat) * .55)) steps(3) 1 both;
}
/* A pointer-only flourish gets a pointer-only cursor: under reduced motion
   nothing fires, so the type should not advertise itself as pressable. */
@media (prefers-reduced-motion: reduce) {
  .epq-strike h1 { cursor: auto; }
}

/* ANCHOR TARGETS CLEAR THE STICKY NAV. Scrolling an element to the top of
   the viewport puts its first --epq-nav-h pixels UNDER the nav, which is
   fixed to that band of the screen — so a figure sized to exactly one free
   screen arrives with its top slice hidden and reads as clipped (founder:
   "the animation is still clipped"). scroll-margin-top is the browser's own
   answer: it offsets where "the top" is for anchor navigation, so the frame
   lands flush under the nav instead of behind it. The height is measured by
   the figure's script and by the hero's; the fallback matches the nav's
   resting height. */
section[id], .ism-frame[id] { scroll-margin-top: var(--epq-nav-h, 86px); }

/* SMOOTH TRAVEL FOR IN-PAGE ANCHORS. The arrow is an anchor, and the glide
   down to the section it names is CAMERA — a rostrum move over a static
   sheet, the one class of motion this site lets ease. Gated to
   no-preference, so a reduced-motion reader gets the instant jump.

   On a PAGED page the pager overrides this back to auto — see below. */
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

/* ---- THE CARD PAGER ------------------------------------------------------
   Founder, 2026-08-03: "the scroll-up/down action causes the sections to flick
   into the frame like cards - no normal scrolling", mobile and desktop.

   A CARD IS A FRAME THE PAGE ALREADY HAD. The home page was already composed
   as screen-claiming frames with hop arrows between them — hero, then §1.0's
   graded statement, then FIG 1.1 — so the pager did not invent a unit, it
   made the unit binding. §1.0's statement and its figure stay two cards
   because they were already two screens; a card per <section> would have made
   that one double-height card and put the hop arrow back inside it.

   THE MECHANISM IS NATIVE SCROLL-SNAP, not a transform pager driving the
   viewport. Snap leaves the browser's own scrolling underneath, so touch
   inertia, trackpad flings, find-in-page, the hop anchors and focus travel all
   still work, and a card that outgrows its screen degrades to ordinary
   scrolling instead of hiding its overflow. static/epq-cards.js adds only what
   snap cannot do: the flick, the arrow keys, the measured nav height.

   THE WHOLE FEATURE IS GATED ON no-preference. Mandatory snap moves the
   viewport out from under the reader on every gesture, which is exactly the
   class of motion a reduced-motion setting is asking not to have — so the
   degrade is the ORDINARY DOCUMENT, not a pager with the animation off. That
   is why the gate is this media block and not a rule inside it: nothing below
   reaches a reader who asked for stillness, the section rules and pencil
   dividers are never touched, and the page is the one that shipped yesterday.
   The .epq-card wrappers are inert divs there.

   `html` is the snap container: the viewport's scrollport propagates from it.
   scroll-behavior goes back to AUTO because a snap that glides is the browser
   easing the travel, and the arrival is meant to be a hard cut with the flick
   on top of it — the one eased thing on this site is a rostrum camera. */
@media (prefers-reduced-motion: no-preference) {
  html.epq-paged { scroll-snap-type: y mandatory; scroll-behavior: auto; }

  /* min-height, never height: a card that needs more than a screen (long copy
     on a short phone) must be allowed to grow. An oversized snap area is one
     the spec lets the reader scroll freely inside, so growing IS the escape
     valve that stops a mandatory pager making content unreachable. Capping the
     height instead would hide the overflow behind a clip.

     scroll-margin-top is the same sticky-nav offset the anchor targets take
     above, applied where the snap position lands: without it every card after
     the first arrives with its top --epq-nav-h pixels behind the bar. */
  html.epq-paged .epq-card {
    scroll-snap-align: start;
    scroll-snap-stop: always;    /* one gesture, one card — no flinging past */
    scroll-margin-top: var(--epq-nav-h, 86px);
    min-height: calc(100vh - var(--epq-nav-h, 86px));
    min-height: calc(100svh - var(--epq-nav-h, 86px));
  }

  /* Content centres in its claimed screen. The frames that already claim one
     (the strike hero, .body-frame, the figure band) centre their own contents
     and are unaffected; this is for the SHORT cards — a closing section of a
     lede and a button — which would otherwise sit in the top fifth of an empty
     screen. The hero is excluded because .epq-mantra is display:block on
     purpose and the fitter measures it as one. */
  html.epq-paged .epq-card:not(.epq-hero) {
    display: flex;
    flex-direction: column;
    justify-content: center;
  }
  /* The footer rides at the foot of the last card, not in the centred group
     with the copy: it is the sheet's document code, and it belongs on the
     bottom edge of the screen exactly as it belongs on the bottom of a page. */
  html.epq-paged .epq-card > footer { margin-top: auto; }

  /* THE CLOSING CARD CENTRES ITS COPY — the fix for the one card where the
     centring above could never land. Auto margins absorb flex free space
     BEFORE justify-content is consulted, so the footer's margin-top: auto
     took the whole screen's slack for itself and parked the clause bar, the
     copy and the button in the top fifth — the exact composition the
     justify-content rule documents itself as preventing. The auto pair here
     splits the free space around the copy instead: bar pinned to the head,
     copy dead-centre of what remains, footer on the foot. The footer's own
     auto margin is zeroed for this shape or it would claim a third share
     and float the copy high. Longhands, never the margin shorthand — the
     section band rules further up centre these boxes on their left/right
     autos, and a shorthand would silently reset them. */
  html.epq-paged .epq-card > .body:has(+ footer),
  html.epq-paged .epq-card > .sec-mark:has(+ footer) {
    margin-top: auto;
    margin-bottom: auto;
  }
  html.epq-paged .epq-card > .body:has(+ footer) + footer,
  html.epq-paged .epq-card > .sec-mark:has(+ footer) + footer { margin-top: 0; }

  /* THE CARD DOES THE CLAIMING, NOT THE FRAME. .body-frame claims a screen
     minus a 54px ALLOWANCE for the clause bar riding above it — a fair
     estimate on a continuous page, and 13px short of the bar's real height at
     1280, which put the card 13px over the screen. That is not a cosmetic
     miss: a snap area taller than the snapport is one the spec lets the reader
     scroll freely inside, so those 13px were 13px of the ordinary scrolling
     the pager exists to remove, on the page's main text card.

     Measuring the bar to fix the number would be a third copy of a height that
     already has two. Inside a card there is no estimate to make: the card is
     the screen, the bar is the flex item above, and flex:1 gives the frame
     exactly the rest of it — arithmetic the browser does with the real height.
     The frame keeps its own grid centring, so the statement still sits in the
     middle of it, and the hop arrow it anchors still marks the true foot. */
  html.epq-paged .epq-card > .body-frame { min-height: 0; flex: 1 1 auto; }

  /* A FOLDED CARD — clause bar, the words, and the figure those words describe,
     all on one screen (founder, 2026-08-03: "the text elements should be in the
     same frame as the figure it describes"). index.mjs folds every section whose
     statement is a plain sentence; the graded-statement sections stay split, and
     their figure card is .epq-card--fig WITHOUT --fold, which is why the two
     selectors are not interchangeable.

     THE BAND IS THE ELASTIC THING, so the band is what yields. On a continuous
     sheet its height comes from an aspect ratio on its own width — right for a
     full-bleed band with a whole screen to itself, and 60–100px too tall once a
     clause bar and two paragraphs share the screen with it. In a folded card the
     ratio goes and the band takes what the words leave.

     That is safe because a band is a CLIPPING VIEWPORT onto a CENTRED
     composition: taking 100px off closes the margins around the drawing, it does
     not crop it. And it is bounded, because each band keeps its own narrow-width
     min-height — where that floor binds (a small phone, whose screen is shorter
     than the drawing needs) the card overflows and scrolls inside itself, which
     is the pager's documented escape valve and much better than a clipped
     figure. Verified fitting at 1024x768 and up, and at 390x844; 360x780 lands
     on the floor and scrolls.

     A NEW BAND KIND MUST BE NAMED HERE. Miss it and it keeps its aspect ratio
     and pushes its card off the bottom of the screen. */
  html.epq-paged .epq-card--fold > .br-frame,
  html.epq-paged .epq-card--fold > .rec-frame,
  html.epq-paged .epq-card--fold > .mp-frame,
  html.epq-paged .epq-card--fold > .ag-frame,
  html.epq-paged .epq-card--fold > .ism-frame {
    min-height: 0;
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
  }
  html.epq-paged .epq-card--fold .br-band,
  html.epq-paged .epq-card--fold .rec-band,
  html.epq-paged .epq-card--fold .mp-band,
  html.epq-paged .epq-card--fold .ag-band,
  html.epq-paged .epq-card--fold .ism-band {
    aspect-ratio: auto;
    flex: 1 1 auto;
    /* deliberately NO min-height: 0 — the floor each band declares at narrow
       widths is what stops the drawing being crushed. See above. */
  }

  /* The body's bottom padding goes with the fold. It is the air between one
     block of writing and the next; below it now is a bordered full-bleed object
     that brings its own edge, so the padding was drawing a second gap on top of
     the one the band's rule already makes — and on a phone it was the last
     20px between fitting and scrolling. */
  html.epq-paged .epq-card--fold > .body { padding-bottom: 0; }
  /* …except where the screen has height to spare. At zero padding the lede's
     descenders sit ~13px off the band's top rule — with the FIG corner tab
     riding that rule — while every other junction on the card carries 40px+.
     The band is the elastic thing (see above) and its composition centres in
     itself, so on a tall screen these 20px come out of the band's own idle
     margin, not out of anything drawn. Gated on BOTH axes, and the width gate
     is not decoration: at 390×844 the height alone passes while the bands sit
     ON their narrow-width min-height floors — nothing left to take, so the
     pad became 9–16px of card overflow (measured). 741px is where the phone
     miniature grammar ends and the floors stop binding; 800px keeps 1024×768
     on the tight junction, which that screen needs. */
  @media (min-width: 741px) and (min-height: 800px) {
    html.epq-paged .epq-card--fold > .body { padding-bottom: 20px; }
  }

  /* THE SEAM IS THE DIVIDER NOW. Sections part with a pencil rule and 38px of
     air either side — right on a continuous sheet, and on a pager it is a line
     drawn across the middle of nothing, since the two things it separates are
     never on screen together. Worse, it is ~83px of height on top of a card
     already sized to the exact screen, which would push every text card into
     the overflow case and un-page the page. The screen edge separates them. */
  html.epq-paged section { margin-top: 0; }
  html.epq-paged section::before { display: none; }

  /* THE BAR STAYS. head()'s script hides the nav on downward travel, which on a
     continuous page is chrome getting out of a reader's way and on a pager is
     a bar that flickers off and on once per card. It also breaks the geometry:
     a card is positioned a nav's height from the top of the screen and sized to
     the rest, so a nav that leaves opens an empty band above every card. The
     script still runs and still scores direction — this pins what it moves, so
     the float shadow it sets is kept. */
  html.epq-paged.epq-nav-away .epq-nav { transform: none; }

  /* THE FLICK. Three hard positions, no easing: the same grammar as the hop
     arrow's bob and the roundel's ratchet. steps(1) holds each keyframe stop
     for its whole segment, so what plays is three frames of a card being
     dealt, not a slide sampled three times.

     --epq-deal is the direction, written by the script from which way the
     reader travelled: 1 when they went down and the card rises from below,
     -1 when they went up and it drops from above. One keyframe block serves
     both because the sign is a variable — mirrored blocks would be the same
     three numbers written twice. 44px rather than a percentage: the throw
     should read the same on a phone card and a desktop one, and a percentage
     of the card's own height does not.

     transform only. It paints outside the layout, so a card mid-flick never
     moves a snap position or the scroll height it was snapped to. */
  @keyframes epq-card-deal {
    0%   { transform: translateY(calc(var(--epq-deal, 1) * 44px)); }
    33%  { transform: translateY(calc(var(--epq-deal, 1) * 15px)); }
    66%  { transform: translateY(0); }
  }
  html.epq-paged .epq-card.is-dealt {
    animation: epq-card-deal 180ms steps(1) 1 both;
  }
}

@media (max-width: 740px) {
  /* THE BUTTON STAYS TOP-RIGHT ON A PHONE (founder, 2026-08-02): one corner
     at every width is one less thing the eye has to re-find. With the stage
     centred in the hero's claimed screen, nothing sits near the nav for the
     button to fight, so the old 92px of stage padding that held it clear is
     gone — the hero's own height is the spacing now. */
  .epq-hop { right: calc(var(--epq-fab-inset, 24px) + var(--epq-fab-size, 56px) + 6px); bottom: 12px; }
  /* the plunger steps down with the screen */
  .epq-plunger { --plw: 72px; --plh: 18px; }
  /* Silkscreen's counters close up under a phone's smaller step, so the
     tracking opens a little further to keep the line legible at 27px. */
  .epq-strike-small { letter-spacing: .12em; }
}
/* the nav recomposes to two rows below 470 (see its own block near the foot
   of this file); the hero's viewport claim has to know. epq-strike.js writes
   the measured height over both fallbacks. */
@media (max-width: 470px) {
  .epq-strike { --epq-nav-h: 108px; }
}

/* THE TAIL — the lede and the certification mark, in their own space below
   the fold (the hero is held to exactly one screen by the fitter). Off the
   mantra, they are no longer overlays and no longer small: the lede returns
   to its own step of the type scale and the mark to a full field plate. This
   is why the exception needs no "tagline near body size" carve-out. */
.epq-mantra-tail {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 32px;
  align-items: start;
  padding: 56px 28px 60px;
  border-bottom: var(--epq-rule);
}
.epq-mantra-tail-body .epq-lede { max-width: 34ch; }
.epq-mantra-tail-mark {
  background: var(--epq-field);
  border: var(--epq-rule);
  padding: 20px;
  line-height: 0;
}
@media (max-width: 740px) {
  .epq-mantra-tail { grid-template-columns: minmax(0, 1fr); padding: 34px 16px 40px; }
  .epq-mantra-tail-mark { justify-self: start; padding: 14px; }
}

/* ---- Figure wires (phones only) -------------------------------------------
   Ruling: decisions/2026-07-29-mobile-wires-no-camera.md. The phone figures
   draw their source→destination relationships as DRAFTED DASHED WIRES — the
   draftsman's construction lines — instead of camera moves or carrier
   flights: a wire drafts from the source box to the destination box as the
   entry travels, one dash at a time, and STAYS on the finished sheet.

   Two rules broken here, both knowingly and both scoped to phones (founder:
   "let's break some rules on mobile"):
   · The plates do not register at rest. Every wire is four passes — the
     themed line core plus the three process plates — printed permanently out
     of register, so the drafted line reads as our chromatic aberration held
     still. Everywhere else the plates must return to 0,0; on a phone wire
     the misregistration IS the ink.
   · Structural rules are solid; these are dashed. A wire is an annotation
     ABOUT the sheet, not part of it — the dash is what says so.

   What is NOT broken: wires never ease. They draft in cuts — the clip
   reveals one dash-unit per step (dash count from --wn, matched by the
   steps() the consumer declares), the drafting-hand cadence.

   Anatomy: one element or pseudo-element, 9px across the line's axis. The
   four passes are background layers of one image token (--epq-wire-ang set
   per orientation); each plate sits at its own lateral offset and its own
   PHASE offset along the line, so fringes lead and trail every dash. The
   consumer sets: the image token, --epq-wire-ang, background-size (dash
   unit from --wn), background-position (the plate offsets below), repeat
   axis, and the draw animation with its beat.

     horizontal: --epq-wire-ang:90deg; background-size:calc(100%/var(--wn)) 2px;
       background-position:0 4px,-2px 1px,2px 6px,4px 2px; repeat-x; height:9px
     vertical:   --epq-wire-ang:180deg; background-size:2px calc(100%/var(--wn));
       background-position:4px 0,1px -2px,6px 2px,2px 4px; repeat-y; width:9px
   The passes are 2px, not the 3px of structural rules — a wire is a drafted
   annotation and must read a weight below the sheet's own ink.

   Resting state is the DRAWN wire (resting styles are the end state, as
   everywhere); armed hides it with the same from-clip the draw starts at.
   The negative clip outsets keep the plate fringes from being shaved at the
   line's flanks while the length reveals. */
:root {
  --epq-wire-img:
    linear-gradient(var(--epq-wire-ang,90deg), var(--epq-line) 0 55%, transparent 55% 100%),
    linear-gradient(var(--epq-wire-ang,90deg), var(--epq-plate-c) 0 55%, transparent 55% 100%),
    linear-gradient(var(--epq-wire-ang,90deg), var(--epq-plate-m) 0 55%, transparent 55% 100%),
    linear-gradient(var(--epq-wire-ang,90deg), var(--epq-plate-y) 0 55%, transparent 55% 100%);
}
@keyframes epq-wire-draw-h { from { clip-path: inset(-4px 100% -4px 0) } }
@keyframes epq-wire-draw-v { from { clip-path: inset(0 -4px 100% -4px) } }

/* ---- Theme switch: the floating circle ---------------------------------
   Founder 2026-07-28: the switch was crowding the nav, worst on mobile where
   the row already had to recompose onto two lines. It moves out to a floating
   control at the bottom right and drops from three positions to two — see
   nav() in util.mjs for what that costs, and the decision doc for the two
   house rules it deliberately breaks.

   The circle SHOWS BOTH THEMES AT ONCE, which is the whole idea, so it cannot
   be painted in --epq-surface-* — those resolve to the current theme and half
   the control would vanish into the page. It takes the --epq-src-* literals
   instead, the same exception (and for the same reason) as the roundel's
   centre disc in index.mjs: an element that must be identical in both themes
   has to sit outside the theme tokens. The divider is --epq-src-cy-line, the
   palette's mid-tone — the one pigment that reads against both halves.

   Rotation is DERIVED from the theme attribute, not stored: the head script
   sets that attribute before first paint, so the circle is already oriented
   correctly on load with no flash and no second source of truth. Shift and
   rotation compose through two custom properties because they share one
   transform — writing either one directly would cancel the other. */
:root {
  --epq-fab-size:  56px;   /* clears the 44px hit-target floor */
  --epq-fab-inset: 24px;
  --epq-fab-rot:   0deg;   /* dark: cyanotype half top-left */
  --epq-fab-shift: 0;
}
[data-epq-theme="light"] { --epq-fab-rot: 180deg; }

.epq-fab {
  position: fixed;
  right: var(--epq-fab-inset);
  bottom: var(--epq-fab-inset);
  /* Escape .doc > *'s pillar cap — this is a viewport-anchored overlay, and a
     max-width here would park it at the pillar edge instead of the screen's.
     Same correction the drawer carries, same reason. */
  max-width: none;
  margin: 0;
  width: var(--epq-fab-size);
  height: var(--epq-fab-size);
  padding: 0;
  border: var(--epq-rule);
  border-radius: 50% !important; /* see below */
  /* Cyanotype half FIRST, so at rest (dark) the active theme's half leads from
     the top-left and the 180° flip hands that corner to manila. */
  background: linear-gradient(
    135deg,
    var(--epq-src-cy-ground) 0 calc(50% - 1px),
    var(--epq-src-cy-line)   calc(50% - 1px) calc(50% + 1px),
    var(--epq-src-vellum)    calc(50% + 1px) 100%
  );
  box-shadow: 5px 5px 0 0 var(--epq-line);
  cursor: pointer;
  z-index: 80; /* above the writing, below the drawer's 90/91 — it is modal */
  transform: translateY(var(--epq-fab-shift)) rotate(var(--epq-fab-rot));
  transition: var(--epq-press), opacity .18s ease;
}
/* The ONE exception to --epq-radius: 0 ("square everywhere. no exception."),
   taken knowingly and recorded in decisions/. !important is the only lever:
   the reset is `*, *::before, *::after { border-radius: 0 !important }` and an
   author !important loses to another author !important on specificity alone,
   so a class rule wins. clip-path: circle(50%) would avoid the keyword but
   clips the box-shadow away, and the hard offset shadow is the system's
   signature — trading it for a punctuation preference is the worse deal. The
   net count of !important in this file is unchanged: this block replaces the
   toggle-sizing hack that carried two. */

.epq-fab:hover  { --epq-fab-shift: 3px; box-shadow: 2px 2px 0 0 var(--epq-line); }
.epq-fab:active { --epq-fab-shift: 5px; box-shadow: 0 0 0 0 var(--epq-line); }
.epq-fab:focus-visible { outline: 3px solid var(--epq-focus-ring); outline-offset: 3px; }

/* Out of the way while the page moves, back when it settles. Kept in the
   layout (no display:none) so the transition has something to run on, and
   pointer-events go with it so a hidden control cannot be clicked. Under
   reduced motion the class is never added — head()'s script returns before
   wiring the listener — so the circle simply stays put. */
html.epq-fab-away .epq-fab {
  opacity: 0;
  pointer-events: none;
}
/* The drawer is modal and covers the page; the scroll lock it applies also
   means no scroll events fire, so without this the circle would sit frozen
   over the backdrop. */
html.epq-drawer-open .epq-fab {
  opacity: 0;
  pointer-events: none;
}

/* ---- Actions ----------------------------------------------------------- */
/* CTA and its secondary link. 44px is the hit-target floor, which .epq-btn and
   .epq-det-link both clear on their own; the gap here keeps them from merging
   into one target. */
.act { display: flex; flex-wrap: wrap; gap: 14px 18px; align-items: center; margin-top: 16px; }

/* Small print — terms, storage, cadence. At label size, NOT below it: this
   carries what a visitor is agreeing to and it was the least readable text on
   the page at 10.5px. */
.fine {
  font-family: var(--epq-font-mono);
  font-size: var(--epq-t-label);
  letter-spacing: .06em;
  line-height: 1.7;
  color: var(--epq-text-muted);
  margin: 18px 0 0;
  max-width: 60ch;
}

.tags { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 18px; }
.xref { margin-top: 14px; }

/* ---- Numbered block header --------------------------------------------- */
/* .epq-block is the component; this is its head. Deliberately NOT
   .epq-block-hd, whose ::before prints an automatic 01/02 counter — these
   blocks number as 1.1, 1.2 … under their clause bar, and that tie to the
   section is worth more than the counter's convenience. The number is derived
   from position in the template, never authored. */
.blk-hd { display: flex; align-items: baseline; gap: 12px; margin: 0 0 12px; }
.blk-hd .no {
  font-family: var(--epq-font-mono);
  font-size: var(--epq-t-label);
  font-weight: var(--epq-w-label);
  color: var(--epq-text-muted);
}
/* A sub-section title: same 21px step as the clause bar above it, separated by
   face, case and ground rather than by size. The scale has nothing between 21
   and 34, and 34 here would outrank the clause that contains it. */
.epq-block h3 {
  font-family: var(--epq-font-display);
  font-variation-settings: var(--epq-clause-vf);
  font-size: var(--epq-t-clause);
  letter-spacing: .02em;
  text-transform: uppercase;
  color: var(--epq-text);
  margin: 0;
}
.epq-block li { margin-bottom: 7px; }
.epq-block > *:last-child { margin-bottom: 0; }

/* ---- Service card interior --------------------------------------------- */
/* The grid around these cards is .epq-group; the card's own inside is here.
   margin-top:auto on the price pushes it to the card foot so a row of cards
   lines its numbers up regardless of body length. */
.svc { border-right: var(--epq-rule); border-bottom: var(--epq-rule); display: flex; flex-direction: column; }
.svc-hd {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  padding: 11px 16px;
  border-bottom: var(--epq-hair);
  background: var(--epq-surface-page);
  font-family: var(--epq-font-mono);
  font-size: var(--epq-t-label);
  font-weight: var(--epq-w-label);
  letter-spacing: var(--epq-track-label);
  text-transform: uppercase;
}
.svc-bd { padding: 18px 16px 20px; display: flex; flex-direction: column; flex: 1; }
.svc-bd p { margin: 0 0 14px; max-width: 60ch; }

/* The early-bird chip carries a fill, and at 21px in a half-width card a value
   like "€1,000 (solo) · €1,750 (team)" wraps. Without this the padding applies
   only to the first and last fragments and the violet block reads as two
   mis-cut rectangles; cloned, each line gets the full chip. Belongs upstream
   with .epq-price — filed. */
.epq-price-early b { -webkit-box-decoration-break: clone; box-decoration-break: clone; }

/* The service name — the thing a visitor scans a card for, so it takes the 34px
   title step rather than sitting at body size with the copy. */
.svc h3 {
  font-family: var(--epq-font-display);
  font-variation-settings: var(--epq-big-vf);
  font-size: var(--epq-t-title);
  line-height: var(--epq-lh-tight);
  letter-spacing: -.005em;
  text-transform: uppercase;
  color: var(--epq-text);
  margin: 0 0 12px;
}

/* A tag filled with the VIEW'S field, as opposed to .epq-tag-verify and the
   other state fills. The design system has no variant for this — the state
   tags name a state, and this one says "the thing this page is about" — so it
   is site-side until Design takes it. Filed for the next upstream. */
.epq-tag-field { background: var(--epq-field); color: var(--epq-on-field); }

/* The discovery card — the one CTA the whole services page drives at, rendered
   twice on purpose (top for the visitor who arrived ready, bottom for the one
   who read everything). Full width, so no right border to cancel. */
.disc { border: var(--epq-rule); display: flex; flex-direction: column; }
.disc .svc-bd { padding: 18px 16px 20px; }
.disc p { margin: 0; max-width: 62ch; }

/* ---- Panel interiors --------------------------------------------------- */
/* .epq-offer and .epq-limit are the panel — ground, rule, measure — and have no
   opinion about what goes inside. These two rules are that opinion: a mono
   label at the floor, and a list at body size. */
.epq-offer h4,
.epq-limit h4 {
  font-family: var(--epq-font-mono);
  font-size: var(--epq-t-label);
  font-weight: var(--epq-w-label);
  letter-spacing: var(--epq-track-label);
  text-transform: uppercase;
  margin: 0 0 12px;
}
.epq-offer ul,
.epq-limit ul { margin: 0; padding-left: 19px; line-height: 1.7; max-width: 60ch; }
.epq-limit { margin-top: 8px; }

/* ---- Retainer panel ---------------------------------------------------- */
/* Built on .epq-offer (vellum = something you can buy); this adds only the
   measure and the two mono lines that frame it. */
.ret { max-width: 70ch; }
.ret .note {
  font-family: var(--epq-font-mono);
  font-size: var(--epq-t-label);
  font-weight: var(--epq-w-label);
  letter-spacing: var(--epq-track-label);
  text-transform: uppercase;
  color: var(--epq-text-muted);
  margin: 0 0 10px;
}
.ret p { margin: 0 0 12px; }
.ret .tail { font-size: var(--epq-t-label); color: var(--epq-text-muted); margin: 16px 0 0; }

/* ---- Booking page ------------------------------------------------------ */
/* "Enquiring about: <service>", written by the ?service= slug. Hidden until it
   has something to say, so an unrecognised slug leaves no empty rule behind. */
.svc-note {
  font-family: var(--epq-font-mono);
  font-size: var(--epq-t-label);
  font-weight: var(--epq-w-label);
  letter-spacing: var(--epq-track-label);
  text-transform: uppercase;
  border-left: var(--epq-rule);
  padding-left: 12px;
  margin: 0 0 22px;
}
.svc-note:empty { display: none; }

/* ---- The acknowledgement lands like a stamp ----------------------------
   .epq-ack-on is the moment the whole funnel exists for: RECORDED — or the
   refusal. Until now the line simply appeared, which is the flattest possible
   delivery of the page's biggest event. It now lands with the same CMY
   misregistration every figure element lands with: epq-misregister, on the
   class the form script adds, so it fires exactly when the message does and
   never on page load. Word treatment — the resting shadow is cancelled, so
   only the plates print and nothing leaves a box behind a line of mono text.
   say() in book-form.js drops and re-adds the class through a reflow so a
   second message (invalid → recorded) re-fires it. Reduced motion kills it
   globally and the message simply appears, which was the old behaviour. */
.epq-ack-on {
  --epq-plate-shadow: 0 0 0 0 transparent;
  animation: epq-misregister var(--epq-plate-drift) steps(3) 1 both;
}

/* ---- Wordmark: the dimension line redraws on hover ---------------------
   The Q's dimension line is the one piece of drafting in the wordmark, and a
   dimension line is a thing a draughtsman DRAWS. On hover it redraws left to
   right in three hard steps — same stepped discipline as every other motion,
   over in 220ms, and the terminal ticks never move (they are the two ends the
   line is dimensioning between). Hover-only, so keyboard focus and touch get
   the resting mark; nothing is conveyed, so nothing is lost. */
@keyframes epq-wm-draw { from { transform: scaleX(0); } }
.epq-wm:hover .epq-wm-axis::before {
  transform-origin: left;
  animation: epq-wm-draw 220ms steps(3) 1;
}

/* ---- Footer ------------------------------------------------------------ */
/* Where the document furniture lives now: the DOC / REV code that used to sit
   in the placard at the top of the page. Print-shop numbers belong at the
   bottom, not in the strip where a reader looks for controls. */
footer {
  /* Pencil rule, same as sections; absolute because footer is a flex row and a
     block ::before would become a flex item. */
  position: relative;
  margin-top: 76px;
  /* The bottom padding is the theme switch's clearance, not taste. On four of
     the five pages the last interactive thing on the page is a footer link
     sitting flush to the right edge, which is exactly where the floating
     circle lands once the reader hits the end of the document. One inset +
     the circle + one more inset puts a full gap between the link and the
     control, and derives from the same tokens, so resizing the circle cannot
     silently re-cover the link. */
  padding: 18px 28px calc(var(--epq-fab-inset) * 2 + var(--epq-fab-size));
  display: flex;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  font-family: var(--epq-font-mono);
  font-size: var(--epq-t-label);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--epq-text-muted);
}
footer a { color: var(--epq-text-muted); }
footer::before {
  content: "";
  position: absolute;
  top: -38px;
  left: 0;
  right: 0;
  height: 7px;
  background-color: var(--epq-text-muted);
  -webkit-mask: var(--epq-pencil) repeat-x left center;
  mask: var(--epq-pencil) repeat-x left center;
}
@media (min-width: 1320px) {
  footer::before { left: -38px; right: -38px; }
}

/* ---- Nav: sticky, hides going down, returns going up -------------------
   Founder 2026-07-29: "make the navbar reappear, floating when the user
   scrolls back up, disappearing when they scroll down." Direction logic is in
   head()'s script; this is the bar itself.

   STICKY, not fixed. Sticky keeps the bar in the flow, so it holds its own
   space at the top of the document and nothing below it shifts when the
   behaviour is added — a fixed bar would need a matching spacer, and that
   spacer would be a second source of truth for the bar's height forever.

   FULL-BLEED, not pillar-capped. Founder 2026-08-03: the bar itself runs edge
   to edge of the screen; only its CONTENT stays at the pillar position. That
   is the opposite of what shipped 2026-07-29 (the bar used to inherit
   .doc > *'s cap "for free" so it floated at the stage's width) — here the
   cap is explicitly escaped, the same correction .epq-fab and .epq-drawer
   already carry, and a second element, .epq-nav-in, re-centres the actual
   flex row (menu button, mark, links) inside the full-width bar so the
   contained elements land exactly where the pillar-capped bar used to put
   them. .epq-nav itself now owns only the bar's paint (background, rule,
   sticky/float behaviour); .epq-nav-in owns the layout and all padding.

   IT HAS TO BE OPAQUE NOW. Un-stuck, the nav sat on the sheet and needed no
   ground of its own; floating, it has writing running underneath it. That
   ground is --epq-surface-card, the same surface .epq-ground::before paints the
   viewport with, so the bar reads as the sheet rather than as a panel laid on
   it. The one visible difference is the engineering grid, which the veil lets
   through at 7% and this does not — a strip of plain sheet travelling over
   ruled sheet, which is the correct read for a floating bar.

   The 2px rule underneath was already there and now earns its keep twice: it
   is the bar's bottom edge against the copy sliding beneath it. The hard offset
   shadow arrives only once the bar is actually floating (.epq-nav-float, set by
   the same script) — at rest on the first screen the bar is part of the sheet
   and a shadow would be a lie about depth.

   z-index 79 is deliberate: under the fab's 80 and the drawer's 90/91. The bar
   is the least modal thing on the page and must never cover the control that
   opens or closes it. */
.epq-nav {
  position: sticky;
  top: 0;
  z-index: 79;
  background: var(--epq-surface-card);
  transition: transform .26s ease;
  /* Escape .doc > *'s pillar cap — see the block comment above. */
  max-width: none;
  margin: 0;
  padding: 0;
  display: block;
}
/* Past the rule and the shadow, so nothing peeks below the viewport edge. */
html.epq-nav-away .epq-nav { transform: translateY(calc(-100% - 8px)); }
html.epq-nav-float .epq-nav { box-shadow: 0 5px 0 0 color-mix(in srgb, var(--epq-line) 38%, transparent); }

/* The bar's actual content row — flex layout, the pillar cap the bar itself
   just gave up, and all of the DS's padding (14px 28px, before the overrides
   below). A plain block div sized to the bar's full width, then capped and
   centred exactly like any other .doc > * child, so menu button/mark/links
   sit where they sat when .epq-nav carried the cap directly. */
.epq-nav-in {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  max-width: var(--epq-pillar);
  margin: 0 auto;
  padding: 14px 28px;
}

/* Reduced motion: head()'s script never wires the listener, so the bar never
   leaves — but the transition would still be live for anything else that moves
   it. Cancelled here so it can only ever be where it is. */
@media (prefers-reduced-motion: reduce) {
  .epq-nav { transition: none; }
}

/* ---- Nav: the wordmark is the home button ------------------------------
   Founder 2026-07-29: HOME leaves the link row and the mark grows to take its
   place ("this serves to reinforce the episquire name and function as the
   obvious home button at all times"). The DS ships .epq-wm at 23px, which is
   right for a mark sitting beside other chrome and too small for one carrying
   a destination on its own.

   Fluid rather than a step, because this is the widest single word in the bar
   and it shares a row with the menu button and the CTA: at a fixed ceiling it
   forces the recomposition below on any phone. The clamp lets it fall at
   narrow widths — still comfortably the largest thing in the chrome — and
   reach its ceiling from ~700px up. The axis, ticks and tracking are all
   em-based, so one font-size carries the whole drawing.

   STEPPED UP AGAIN 2026-07-31 (founder: "a few points bigger, and more
   breathing space top and bottom"). 26→30 at the floor, 34→40 at the ceiling.
   40 is off the type scale's steps, and that is the same licence the mark
   already took at 34: the wordmark is identity, not running type, and it is
   now the site's only home affordance. The floor moves with the ceiling so the
   phone mark does not read as a different mark; the slope stays 4.8vw, so the
   recomposition breakpoint below is unchanged — at 470px the clamp is still on
   its floor.

   The air is in .epq-nav-in's own padding rather than here, because a margin
   on the mark alone would move the mark and leave the menu button and the CTA
   where they were: the row has align-items: center, so the height has to come
   from the row.

   Only inside the nav. .epq-wm is the design system's component and any other
   consumer of it keeps the 23px default. */
.epq-nav .epq-wm { font-size: clamp(30px, 4.8vw, 40px); }
/* Breathing space, top and bottom. The DS ships 14px, measured against a 23px
   mark; with the mark at 40px the same proportion asks for 24, and the row as
   shipped reads as clamped to the type. 22 sits just inside that — the bar
   grows with the mark without turning into a band. Padding only: the
   horizontal 28px gutter is the DS's and the 640px override below still owns
   it, which is why this sets the two axes it means and not the shorthand. */
.epq-nav-in { padding-top: 22px; padding-bottom: 22px; }
@media (max-width: 470px) {
  /* Two rows down here (see the recomposition block at the foot of this file),
     so the bar is already tall; the extra air would only push the first screen
     further down a screen that has least of it to spend. */
  .epq-nav-in { padding-top: 16px; padding-bottom: 16px; }
}
/* The wordmark is an <a> INSIDE .epq-nav, so the component's own `.epq-nav a`
   rules have always reached it: a transparent 2px bottom border that turns
   --epq-line on aria-current and --epq-field on hover. At 23px nobody noticed;
   at 34px that border lands within a few pixels of the Q's dimension line and
   the two read as a single broken rule — a rendering fault, not a mark. Zeroed
   here (specificity 0,2,0 beats the component's 0,1,1, and a 0-width border
   cannot be recoloured by the :hover rule that does outrank this one). What the
   mark loses is a hover underline it never needed: hovering already redraws the
   dimension line, which is the wordmark's own answer and a better one. */
.epq-nav .epq-wm { border-bottom: 0; padding-bottom: 0; }

/* Active-page state, on the mark that inherited the job from HOME. Not another
   underline, for the reason just given — the mark already carries a line, so
   the state moves onto it: on home the axis and both terminal ticks take the
   field, the same carry the nav links use for hover. One line, two states, no
   new furniture.

   Colour is not doing this alone: the anchor carries aria-current for assistive
   tech, and the drawer's own Product entry marks the home page in the ordinary
   way for anyone who opens the index. */
.epq-nav .epq-wm[aria-current="page"] .epq-wm-axis::before,
.epq-nav .epq-wm[aria-current="page"] .epq-wm-axis::after,
.epq-nav .epq-wm[aria-current="page"] .epq-wm-axis > i { background: var(--epq-field); }

/* ---- Nav refinement: multi-word link spacing ---------------------------
   The nav sets letter-spacing: --epq-track-label (0.08em) for the label look,
   and that tracking lands after the SPACE too — so on multi-word links
   ("Book a call", "Decision Audit") the word gaps carry the mono space PLUS
   the tracking and read as too wide beside single words like HOME.
   word-spacing pulls only the gaps back in (single-word items have no space
   to touch), keeping the letter tracking the label wants. Hyphenating instead
   ("book-a-call") would fix the look but corrupt the link's accessible name,
   so this is spacing-only. Site-layer override because .epq-nav is vendored;
   this loads after it. */
.epq-nav ul { word-spacing: -0.18em; }

/* ---- The drawer: full pages listing, slid in from the sheet's edge ------
   Founder 2026-07-28: the pages listing collapses into a drawer that slides
   in FROM THE FAR LEFT EDGE OF THE SCREEN — not from the pillar — and overlays
   the page. On the infinite sheet that reads as a second sheet pulled across
   the first, so it dresses like one: card surface, 2px rule on its exposed
   edge, the hard offset shadow, mono labels. Markup in util.mjs nav();
   behaviour in head()'s drawer script; open state is a class on <html> so the
   scroll lock and the transitions key off one place.

   Both the drawer and its backdrop must escape the .doc > * pillar rule —
   they are fixed overlays, and max-width would cap the BACKDROP at the pillar
   and the reader would see the page dim only in the middle. */
.epq-menu-btn {
  font-family: var(--epq-font-mono);
  font-size: var(--epq-t-label);
  font-weight: var(--epq-w-label);
  letter-spacing: var(--epq-track-label);
  text-transform: uppercase;
  color: var(--epq-text);
  background: transparent;
  border: var(--epq-rule);
  padding: 8px 14px;
  cursor: pointer;
}
.epq-menu-btn:hover { border-color: var(--epq-field); }
.epq-menu-btn:focus-visible { outline: 3px solid var(--epq-focus-ring); outline-offset: 2px; }

.epq-drawer-backdrop {
  position: fixed;
  inset: 0;
  max-width: none;
  margin: 0;
  background: color-mix(in srgb, var(--epq-ink) 52%, transparent);
  opacity: 0;
  pointer-events: none;
  transition: opacity .22s ease;
  z-index: 90;
}
.epq-drawer {
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  max-width: none;
  margin: 0;
  width: min(84vw, 360px);
  background: var(--epq-surface-card);
  border-right: var(--epq-rule);
  box-shadow: 5px 0 0 0 var(--epq-line);
  transform: translateX(calc(-100% - 8px)); /* past the shadow, fully off the sheet */
  visibility: hidden;
  transition: transform .28s ease, visibility 0s .28s;
  z-index: 91;
  overflow-y: auto;
  padding: 0 0 32px;
}
html.epq-drawer-open .epq-drawer {
  transform: none;
  visibility: visible;
  transition: transform .28s ease;
}
html.epq-drawer-open .epq-drawer-backdrop { opacity: 1; pointer-events: auto; }
html.epq-drawer-open { overflow: hidden; } /* scroll lock while modal */

.epq-drawer-hd {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 14px 22px;
  border-bottom: var(--epq-rule);
  font-family: var(--epq-font-mono);
  font-size: var(--epq-t-label);
  font-weight: var(--epq-w-label);
  letter-spacing: var(--epq-track-eyebrow);
  text-transform: uppercase;
  color: var(--epq-text-muted);
}
.epq-drawer-hd button {
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  color: var(--epq-text);
  background: transparent;
  border: 0;
  border-bottom: 2px solid transparent;
  padding: 2px 0;
  cursor: pointer;
}
.epq-drawer-hd button:hover { border-bottom-color: var(--epq-field); }
.epq-drawer-hd button:focus-visible { outline: 3px solid var(--epq-focus-ring); outline-offset: 2px; }

.epq-drawer nav { display: block; padding: 10px 0 0; word-spacing: -0.18em; }
.epq-drawer-group { border-bottom: var(--epq-hair); padding: 12px 22px 16px; }
.epq-drawer-row { display: flex; justify-content: space-between; align-items: center; }
/* Top-level entries share the primary nav's control size; the group toggles
   carry a +/− state glyph drawn from aria-expanded so the control cannot lie. */
.epq-drawer-main,
.epq-drawer-toggle {
  font-family: var(--epq-font-mono);
  font-size: var(--epq-t-control);
  font-weight: var(--epq-w-label);
  letter-spacing: var(--epq-track-label);
  text-transform: uppercase;
  color: var(--epq-text);
  text-decoration: none;
  background: transparent;
  border: 0;
  padding: 6px 0;
  cursor: pointer;
  text-align: left;
}
.epq-drawer-toggle::after { content: " \2212"; color: var(--epq-text-muted); } /* − */
.epq-drawer-toggle[aria-expanded="false"]::after { content: " +"; }
.epq-drawer-main { display: inline-block; border-bottom: 2px solid transparent; }
.epq-drawer-main:hover { border-bottom-color: var(--epq-field); }
.epq-drawer-main[aria-current="page"] { border-bottom-color: var(--epq-line); }
.epq-drawer nav > .epq-drawer-main { display: block; padding: 18px 22px; }
.epq-drawer-main:focus-visible,
.epq-drawer-toggle:focus-visible { outline: 3px solid var(--epq-focus-ring); outline-offset: 2px; }

.epq-drawer-group ul {
  list-style: none;
  margin: 8px 0 0;
  padding: 0 0 0 16px;
  border-left: var(--epq-hair);
  display: grid;
  gap: 8px;
  font-family: var(--epq-font-mono);
  font-size: var(--epq-t-label);
  font-weight: var(--epq-w-label);
  letter-spacing: var(--epq-track-label);
  text-transform: uppercase;
}
/* Full-strength ink, like every other link in the nav. These were muted, which
   made a link you can follow and a SOON row you cannot the same colour at rest —
   the chip and the hover underline were the only tells, and neither is visible
   until you look for it. Rank inside the drawer is already carried by size and
   the ul's left rail, so tone is free to mean what it means everywhere else
   here: muted = not a destination. Current page is marked by the underline
   alone, as on .epq-nav a and .epq-drawer-main. */
.epq-drawer-group ul a {
  color: var(--epq-text);
  text-decoration: none;
  padding-bottom: 2px;
  border-bottom: 2px solid transparent;
}
.epq-drawer-group ul a:hover { border-bottom-color: var(--epq-field); }
.epq-drawer-group ul a[aria-current="page"] { border-bottom-color: var(--epq-line); }
.epq-drawer-group ul a:focus-visible { outline: 3px solid var(--epq-focus-ring); outline-offset: 2px; }
/* display:grid above beats the hidden attribute's UA display:none, so a
   collapsed group would flip its toggle to + and keep its list on screen.
   Re-assert the attribute. */
.epq-drawer-group ul[hidden] { display: none; }
/* Listed-but-unshipped pages: named, muted, and honestly tagged. The muting is
   the STATE — it is the one thing in this list that is not full ink, so the
   step down reads as "not yet" rather than as a lower rank. Held at
   --epq-text-muted and no dimmer: that token is the documented AA floor for
   small type on both grounds (6.0:1 on manila, 7.0:1 on the cyanotype), and a
   page we are asking a visitor to remember has to stay readable. cursor:default
   because a span of text otherwise draws an I-beam over what looks like a row
   you could click. */
.epq-drawer-soon { color: var(--epq-text-muted); cursor: default; }
.epq-drawer-soon i {
  font-style: normal;
  font-size: var(--epq-t-label);
  border: var(--epq-hair);
  padding: 1px 6px;
  margin-left: 8px;
}
/* A SOON row at TOP level (Services, while the tree is suppressed) wears
   .epq-drawer-main for the block size and padding the other top-level rows
   have — but .epq-drawer-main:hover lights the field underline, which on a row
   with nowhere to go promises a link that is not there. Cancel it here, where
   the two classes meet, rather than weakening the hover rule every real link
   depends on. */
.epq-drawer-main.epq-drawer-soon:hover { border-bottom-color: transparent; }

/* ---- Upstream correction: the RoleRoll ghost widens the page ------------
   The element measures itself against a hidden out-of-flow ghost rendered at
   FULL size (that is the point — the ghost's scrollWidth is the true unscaled
   width the fit divides by). But visibility:hidden does not remove an
   absolutely-positioned box from the page's scrollable overflow, so below
   ~360px — where the ghost is wider than the viewport — the whole page gains
   ~40px of horizontal scroll into nothing. The fitted window is fine; only
   the ruler leaks.

   overflow:clip stops the host contributing scrollable overflow, and the
   12px clip margin keeps the plate splay (max ~11px offsets on a word)
   unclipped. Not larger: Chromium counts the clip-margin region toward the
   ancestor's scrollable overflow, so the margin has to fit inside the
   narrowest gutter (16px) or it recreates the scrollbar it removes.
   Browsers without overflow:clip ignore it and keep today's behaviour,
   which only ever showed below 360px. Report upstream; delete when the
   export contains it. */
epq-role-roll { overflow: clip; overflow-clip-margin: 12px; }

/* ---- Roundel ratchet · ambient motion ----------------------------------
   The ring type on every certification roundel ticks round like an escapement:
   8° per second, held between ticks. Stepped, never eased — the ambient-motion
   rule (REV E) as literally written, same discipline as epq-breathe. 45 steps
   of 8° close the circle exactly, so the loop has no seam. transform-box:
   view-box centres the rotation on the viewBox (60,60), which is the mark's
   own centre whatever the text's bounding box happens to be.
   prefers-reduced-motion kills it globally and the ring rests where authored,
   which is the frame that was always there. */
.epq-roundel-ring {
  transform-box: view-box;
  transform-origin: center;
  animation: epq-roundel-ratchet 45s steps(45) infinite;
}
@keyframes epq-roundel-ratchet { to { transform: rotate(360deg); } }

/* ---- Press-sheet marginalia (wide viewports) ---------------------------
   Once there is real margin either side of the pillar, the ground reads as
   the PRESS SHEET the page was printed on, and a press sheet carries
   furniture outside the trim — trim ticks, registration crosshairs, and a
   colour-control strip. Same vocabulary as the plates and the hard ink
   shadow: offset litho, not screen decoration.

   RE-ANCHORED TO THE SHEET'S OWN EDGES (founder, 2026-08-03: "restore it").
   This block predates the infinite sheet: it was drawn for the framed
   1080px document and hung its furniture 64px PAST the doc's edges — which,
   once .doc went full-bleed, put every mark off-canvas (invisible for a
   week, nobody noticed) while still manufacturing the horizontal scrollbar
   the overflow guards at the top of this file exist to prevent. On the
   infinite sheet the sheet's edge IS the screen's, so the box now stops at
   0 and the marks keep their small insets from it: same furniture, same
   insets, measured from the edge a viewer can actually see.

   The top trim ticks sit just under the sticky nav rather than at y 0,
   because the bar is opaque and the visible sheet begins beneath it — ticks
   at the true top would mark an edge the bar is covering. The nav-height
   var is the same one the pager and the anchor offsets already use, script-
   measured where scripts run, resting fallback where they don't.

   The pseudo paints in .doc's own layer: above the ground veil (z 1),
   beneath the nav, the clause bars and the figure bands, which are the
   printed objects a sheet's furniture naturally runs under. At the 1280px
   gate the gutter is 40px and the widest mark reaches 44 — past the pillar
   line but well short of the 28px body gutter, so it never touches type.

   All of it is paint on two pseudo-elements of .doc, so no template changes
   and nothing enters the accessibility tree (the legend carries empty alt
   text via the `content: "…" / ""` form; a browser too old for that syntax
   drops the pseudo entirely, which is the correct degradation for furniture).

   Colour discipline: ticks and crosshairs are var(--epq-line) — rules, drawn
   from frame one like every other rule, in cyanotype line blue on the dark
   ground. The control strip is the plate triad plus the view's field, all of
   which already resolve per theme and already sit on the revealed side, so
   the strip colourises with the page. Nothing here is a new pigment.

   Gated at 1280px: below that the marks would overhang the viewport and
   manufacture a horizontal scrollbar. The furniture needs a margin to live
   in; if there is no margin, there is no sheet. */
@media (min-width: 1280px) {
  .doc::before {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    pointer-events: none;
    background-repeat: no-repeat;
    background-image:
      /* trim ticks, aligned with the document's top and bottom edges */
      linear-gradient(var(--epq-line), var(--epq-line)),
      linear-gradient(var(--epq-line), var(--epq-line)),
      linear-gradient(var(--epq-line), var(--epq-line)),
      linear-gradient(var(--epq-line), var(--epq-line)),
      /* colour-control strip: the CMY plates, then the view's field */
      linear-gradient(var(--epq-plate-c), var(--epq-plate-c)),
      linear-gradient(var(--epq-plate-m), var(--epq-plate-m)),
      linear-gradient(var(--epq-plate-y), var(--epq-plate-y)),
      linear-gradient(var(--epq-field), var(--epq-field)),
      /* registration crosshairs — line, line, centre square — top pair */
      linear-gradient(var(--epq-line), var(--epq-line)),
      linear-gradient(var(--epq-line), var(--epq-line)),
      linear-gradient(var(--epq-line), var(--epq-line)),
      linear-gradient(var(--epq-line), var(--epq-line)),
      linear-gradient(var(--epq-line), var(--epq-line)),
      linear-gradient(var(--epq-line), var(--epq-line)),
      /* bottom pair */
      linear-gradient(var(--epq-line), var(--epq-line)),
      linear-gradient(var(--epq-line), var(--epq-line)),
      linear-gradient(var(--epq-line), var(--epq-line)),
      linear-gradient(var(--epq-line), var(--epq-line)),
      linear-gradient(var(--epq-line), var(--epq-line)),
      linear-gradient(var(--epq-line), var(--epq-line));
    background-size:
      26px 2px, 26px 2px, 26px 2px, 26px 2px,
      22px 22px, 22px 22px, 22px 22px, 22px 22px,
      30px 2px, 2px 30px, 8px 8px,
      30px 2px, 2px 30px, 8px 8px,
      30px 2px, 2px 30px, 8px 8px,
      30px 2px, 2px 30px, 8px 8px;
    background-position:
      left 24px top calc(var(--epq-nav-h, 86px) + 14px),
      right 24px top calc(var(--epq-nav-h, 86px) + 14px),
      left 24px bottom 0, right 24px bottom 0,
      left 18px top 170px, left 18px top 192px, left 18px top 214px, left 18px top 236px,
      left 14px top 314px, left 28px top 300px, left 25px top 311px,
      right 14px top 314px, right 28px top 300px, right 25px top 311px,
      left 14px bottom 300px, left 28px bottom 286px, left 25px bottom 297px,
      right 14px bottom 300px, right 28px bottom 286px, right 25px bottom 297px;
  }
  /* No legend. A vertical caption ran down the right margin here for one
     revision and the founder cut it: it serves no function — real press
     furniture is marks, not sentences, and a margin that starts talking is
     noise. The ticks, crosshairs and control strip above say everything the
     sheet needs to. Do not bring text back into the margins. */
}

/* ---- Responsive -------------------------------------------------------- */
/* The hero's own collapse is in the component layer; this is the body gutter.
   .epq-hero-body, .epq-nav-in, .epq-clause-bar and .epq-notice carry 28px
   from the DS, so they are named here too — one gutter for the whole
   document. .epq-nav-in stands in for .epq-nav: the bar itself is full-bleed
   and carries no horizontal padding of its own (see the nav block above). */
@media (max-width: 640px) {
  .body,
  .epq-hero-body,
  .epq-nav-in,
  .epq-clause-bar,
  .epq-notice,
  footer { padding-left: 16px; padding-right: 16px; }
  /* The clause bar needs saying twice. The band rule above gives it a
     max()'d padding at specificity (0,2,1)/(0,3,1), and specificity outranks
     media-query order — the 16px in the list above would never reach it, and
     the bar would keep a 28px inset while every other block on the page came
     in to 16. Below 1200px the max() is already resolving to its floor, so
     this only restates that floor at the narrow value. */
  .doc > section > .epq-clause-bar,
  .doc > section > .epq-card > .epq-clause-bar { padding-left: 16px; padding-right: 16px; }
  /* 16px is the narrow-viewport gutter budget for anything that hangs off the
     edge; the circle's shadow lives inside it. Same tightening the figure
     controls already do on a narrow container. */
  :root { --epq-fab-inset: 16px; }

  /* Vertical rhythm compresses with the type it was sized for. The token
     layer's compressed mobile register (see decisions/2026-07-31-mobile-
     fluid-compressed-type.md) shrinks the words; the air around them was
     measured against the desktop sizes and has to come down with them, or
     the page keeps the old emptiness at the new density. */
  section { margin-top: 24px; }
  section::before { margin-bottom: 24px; } /* section boundary 83px → 55px */
  .body { padding-top: 20px; padding-bottom: 20px; }
  .stmt { margin-bottom: 16px; }
  .epq-lede-detail { margin-top: 8px; }
  h3 { margin: 24px 0 10px; }

  /* Component-internal spacing, overridden site-side so
     episquire-components.css stays identical to the DS export. Filed
     upstream. Button min-height stays 44px — the tap target is not type. */
  .epq-hero-body { padding-top: 24px; padding-bottom: 20px; }
  .epq-eyebrow { margin-bottom: 10px; }
  .epq-hero h1 { margin-bottom: 14px; }
  .epq-clause-bar { padding-top: 12px; padding-bottom: 12px; }
  .epq-btn { padding: 12px 18px; }
}

/* The nav's flex children — menu button, wordmark, link list — wrap
   arbitrarily once the row runs out: "Book a call" used to break in the middle
   of the link. Recompose instead of letting flex decide: the link list takes a
   full row beneath, and the menu button and wordmark pair off on the first.
   order, not markup, so the DOM (and with it tab order and the delegated
   plate-press handler) stays menu → wordmark → links.

   The theme switch used to be a fourth child here and was the main reason the
   row ran out; it now floats bottom-right.

   The breakpoint moved 620 → 470 on 2026-07-29 when HOME left the row. The
   list is one item now, and the wordmark's clamp gives back roughly what the
   larger mark takes, so MENU · EPISQUIRE · BOOK A CALL holds as one row down to
   about 470px. Below that it recomposes as before — and note what the full-row
   treatment means with a single item: justify-content: space-between leaves
   BOOK A CALL flush left under the menu button, which is where the eye lands
   coming down off the mark. If a second primary link ever returns, this
   breakpoint goes back up with it. */
@media (max-width: 470px) {
  .epq-nav-in { row-gap: 12px; }
  .epq-nav ul {
    order: 3;
    flex-basis: 100%;
    justify-content: space-between;
    gap: 10px 16px;
    flex-wrap: wrap;
  }
  .epq-nav a { white-space: nowrap; }
}
