/* home.css — the landing page only. Loaded AFTER style.css, which supplies every colour
   token, the banner, the subscribe form and the buttons. Nothing here redefines a token:
   the brand lives in one file and this one arranges it.

   Built 2026-08-26 with Joe (EosGrove lane). The reasoning behind the copy and the
   structure is EosGrove/marketing/2026-08-26-hopkins-landing-audit.md — read that before
   editing any sentence on this page.

   ⚠ style.css already defines .strip, .hero, .features, .topbar and .eyebrow. This file
   deliberately uses none of those names, so the two cannot fight. The panel is .panel. */

/* ---- self-hosted, no external request. Both are variable fonts, latin subset. ---- */
@font-face {
  font-family: 'Space Grotesk';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url('/fonts/space-grotesk-latin.woff2') format('woff2');
}
@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 400 700;
  font-stretch: 62% 125%;
  font-display: swap;
  src: url('/fonts/archivo-latin.woff2') format('woff2');
}

:root {
  --body: "Space Grotesk", var(--sans, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif);
  --head: "Archivo", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --rail: 1500px;

  /* Where the copy sits over the graph, and where the claim right-justifies to.
     MEASURED off each capture by find_box.py, never estimated. Dark capture's amber
     selection box ends at 79.6% of its width. Re-measure after any new capture. */
  --blurb-y: 52%;
  --copy-x: 6%;
  --box-right: 79.6%;

  /* --ink-soft: the lightest ink on this page that still clears WCAG AA (4.5:1) for
     normal text. It exists because --text-faint does NOT: 3.00:1 on the light page and
     4.20:1 on the dark one. The three "Picture your ..." lines fade as they descend and
     that fade is the design, so the faintest step needed to stay visibly lighter than
     --text-dim while remaining readable. MEASURED, not chosen by eye:
       light #6c7583 — 4.50:1 on #fbfbfc, 4.66:1 on #ffffff
       dark  #788394 — 4.59:1 on #141922, and more on the page behind it
     ⚠ The dark value is solved against the ELEVATED panel, which is the harder surface
     and the one the fine print actually sits on. Solving against the page background
     alone yields #727d8e, which passes there and fails at 4.23:1 on the panel.
     ⚠ Declared DARK-FIRST to match style.css and the rest of this file: the bare :root
     carries the dark value and the light media query overrides it below. Writing it the
     other way round works too, but a file with two opposite conventions in it is how the
     next edit goes wrong. */
  --ink-soft: #788394;
}
/* The light capture is a different shot: its left flank is occupied by nodes, so the
   copy moves lower, and its selection box sits further right. */
@media (prefers-color-scheme: light) {
  :root { --blurb-y: 80%; --copy-x: 1%; --box-right: 95.9%; --ink-soft: #6c7583; }
}
:root { --claim-inset: calc(100% - var(--box-right)); }

body.home { font-family: var(--body); }

.rail { width: min(var(--rail), 100%); margin: 0 auto; padding: 0 32px; }

/* ---------- screen one ---------- */
/* ⚠ THE LOCKUP AND THE HEADLINE SHARE ONE ROW (2026-09-15), headline left, mark right.
   The header keeps its place in the DOM — it is still the first thing after <main>, so
   the landmark and the reading order are untouched — and grid pulls it onto the h1's row
   visually. `order` does the swap rather than reordering the markup, because a screen
   reader should still meet the site identity before the headline.
   The mark is the SAME SIZE it was; what changed is that it no longer owns a full row of
   its own above the headline, which is what made it compete for first attention. */
.home-headrow {
  display: grid; grid-template-columns: 1fr auto;
  align-items: center; column-gap: 24px;
  padding: 30px 0 0;
}
.home-head { display: flex; align-items: center; gap: 11px; order: 2; justify-self: end; }
.home-head .orb { width: 30px; height: 30px; }
.home-head .word { height: 17px; width: auto; fill: currentColor; }
.brand-simple { display: flex; align-items: center; gap: 11px; color: inherit; text-decoration: none; }

.home h1 {
  margin: 0; order: 1; font-family: var(--head); font-weight: 700;
  font-size: clamp(38px, 6.2vw, 74px); line-height: 1.02; letter-spacing: -.02em;
  font-variation-settings: "wdth" 120;
}

/* On a phone the headline needs the full width — a 38px headline and a 30px mark on one
   342px line leaves the headline nowhere to go — so the mark returns to its own row
   above, which is where it started. */
@media (max-width: 34rem) {
  .home-headrow { grid-template-columns: minmax(0, 1fr); row-gap: 20px; }
  .home-head { order: 1; justify-self: start; }
  .home h1 { order: 2; }
}

.stage { position: relative; margin-top: 18px; }
.shot-img { width: 100%; height: auto; display: block; }

.blurb {
  position: absolute; left: var(--copy-x); top: var(--blurb-y);
  transform: translateY(-50%); z-index: 2; pointer-events: none;
}
ul.picture { list-style: none; padding: 0; margin: 0; }
.picture li {
  white-space: nowrap; font-weight: 600; font-size: clamp(15px, 1.7vw, 21px);
  letter-spacing: -.012em; line-height: 1.36; color: var(--text);
}
.picture .lead {
  font-weight: 700; font-size: clamp(20px, 2.5vw, 31px);
  letter-spacing: -.024em; margin-bottom: 2px;
}
.picture .own { color: var(--text-dim); }
/* The faintest step of Joe's descending fade. --ink-soft, not --text-faint: this is
   normal-size text and it has to be readable, fade or no fade. */
.picture .own:last-child { color: var(--ink-soft); }

/* Under this width the graph is too small to host text, so the copy comes out of it. */
@media (max-width: 820px) {
  .blurb { position: static; transform: none; margin-bottom: 18px; }
}

/* ---------- the close: the copy IS the control ---------- */
.home-close { padding: 34px 0 8px; }
.close-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: clamp(28px, 5vw, 72px); flex-wrap: wrap;
  padding-left: var(--copy-x); padding-right: var(--claim-inset);
}
@media (max-width: 820px) {
  .close-row { padding-left: 0; padding-right: 0; justify-content: flex-start; }
}

/* Every reset below is undoing a default the browser gives <button> for free: this must
   look exactly like the paragraph it replaced until the pointer arrives. */
.ask-btn {
  font: inherit; font-family: var(--body); text-align: left;
  background: none; border: 0; padding: 0; margin: 0; color: var(--text-dim);
  font-size: clamp(17px, 1.95vw, 25px); font-weight: 500; letter-spacing: -.012em;
  line-height: 1.36; max-width: 30ch; cursor: pointer; transition: color .15s ease;
}
.ask-btn b { color: var(--text); font-weight: 700; }
.ask-btn .u { text-decoration: none; text-underline-offset: 5px; text-decoration-thickness: 1px; }
.ask-btn:hover { color: var(--text); }
.ask-btn:hover .u { text-decoration: underline; text-decoration-color: var(--text-faint); }
.ask-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 6px; border-radius: 4px; }
.ask-btn:hover + .claim { color: var(--accent-hover); }
/* ⚠ Hover does not exist on touch, and without this the interaction is undiscoverable
   on a phone, which is most of a landing page's traffic. */
@media (hover: none) {
  .ask-btn .u { text-decoration: underline; text-decoration-color: var(--text-faint); }
}
.claim {
  font-size: clamp(17px, 1.95vw, 25px); font-weight: 700; letter-spacing: -.012em;
  line-height: 1; white-space: nowrap; color: var(--accent); transition: color .15s ease;
}

/* ---------- scroll cue ---------- */
.more {
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  padding: 18px 0 40px; text-decoration: none; color: var(--ink-soft);
  transition: color .18s ease;
}
.more span {
  font-family: var(--sans); font-size: 11.5px; font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase;
}
.more svg { display: block; transition: transform .18s ease; }
.more:hover { color: var(--text); }
.more:hover svg { transform: translateY(5px); }
.more:focus-visible { outline: 2px solid var(--accent); outline-offset: 8px; border-radius: 6px; }
@media (prefers-reduced-motion: no-preference) {
  .more svg { animation: bob 2.4s ease-in-out infinite; }
  @keyframes bob { 0%,100% { transform: translateY(0); } 50% { transform: translateY(5px); } }
  .more:hover svg { animation-play-state: paused; }
}
html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

/* ---------- screen two ---------- */
.next { border-top: 1px solid var(--border); padding-top: 26px; }

.nav2 { display: flex; gap: 26px; align-items: center; flex-wrap: wrap; padding: 4px 0 0; }
.nav2 a {
  font-family: var(--sans); font-size: 14px; font-weight: 600;
  text-decoration: none; color: var(--text-dim);
}
.nav2 a:hover { color: var(--text); }
.nav2 .spacer { margin-left: auto; }
.nav2 .keylink {
  color: var(--accent); border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  padding: 6px 13px; border-radius: 8px;
}
.nav2 .keylink:hover { color: var(--accent-ink); background: var(--accent); }

.moment {
  display: flex; align-items: center; gap: clamp(18px, 3vw, 42px);
  padding: clamp(56px, 9vw, 110px) 0 clamp(40px, 6vw, 72px);
}
.moment .orb-lg { width: clamp(48px, 6vw, 84px); height: clamp(48px, 6vw, 84px); flex: none; }
/* The wordmark falls INTO this box, so the box clips it. Capped: uncapped it filled the
   whole rail, which is a billboard rather than a brand moment, and it stretched the drop
   because the fall distance is a percentage of the element. */
.markbox { overflow: hidden; flex: 1; min-width: 0; max-width: 620px; padding: 4px 0; }
.markbox svg { width: 100%; height: auto; display: block; }
.lets, .strike rect { fill: currentColor; }

.claim2 {
  font-family: var(--head); font-weight: 700; font-variation-settings: "wdth" 120;
  font-size: clamp(28px, 4.4vw, 56px); line-height: 1.05;
  letter-spacing: -.02em; margin: 0; max-width: 19ch;
}
.claim3 {
  font-family: var(--head); font-weight: 700; font-variation-settings: "wdth" 120;
  font-size: clamp(21px, 2.8vw, 34px); line-height: 1.1;
  letter-spacing: -.02em; margin: 52px 0 0; max-width: 24ch;
}
.mech {
  margin: 22px 0 0; color: var(--text-dim); max-width: 62ch;
  font-size: clamp(16px, 1.55vw, 19px); line-height: 1.6;
}
.mech b { color: var(--text); font-weight: 700; }

/* ⚠ `stretch`, NOT `center` (Joe, 2026-09-15: "the boxes look wonky. They should match in
   terms of their formatting and size, and then we can show or demonstrate properly the
   formatting difference"). Centering let each pane take its own content height, so a
   fixed-height screenshot beside four lines of text produced two boxes of different
   heights, vertically offset from each other — which reads as a layout bug and buries the
   comparison the panes exist to make. Stretched, the two boxes are the same size and the
   only difference left between them is the rendering, which is the argument. */
.proof { display: grid; grid-template-columns: 1fr auto 1fr; gap: 18px; align-items: stretch; margin-top: 30px; max-width: 820px; }
@media (max-width: 700px) { .proof { grid-template-columns: 1fr; } .proof .arrow { transform: rotate(90deg); justify-self: start; } }
.pane {
  background: var(--bg-elev); border: 1px solid var(--border); border-radius: 10px;
  padding: 14px 16px; font-family: var(--mono); font-size: 13px; line-height: 1.7;
}
/* ⚠ --text-dim, NOT --text-faint. Faint measures 3.00:1 on the light page, under the
   4.5:1 floor for normal text, and these are 11px uppercase labels — the hardest thing
   on the page to read. Faint is for genuinely incidental text only. */
.pane-h {
  font-family: var(--sans); font-size: 11px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--text-dim); margin-bottom: 9px;
}
/* The left pane is a screenshot of the running app; the right stays monospace source text.
   That difference IS the argument the two panes are making.
   ⚠ THE CONTENTS MUST LINE UP, NOT JUST THE BOXES (Joe, 2026-09-15: "the outside boxes
   match now, but the text is floating inside them in a way that doesn't make any sense,
   and the text spacing doesn't match"). Two separate faults produced that:
     1. the shot was CENTRED vertically in a stretched pane, so it floated in the middle
        with dead space above and below while the text pane started at the top;
     2. the shot carries its own internal padding (it is a capture of the app's editor
        pane), so its first line sat further in than the right pane's first line.
   Fixed by starting the shot at the top like the text, and pulling the image out by the
   pane's own padding so the captured content's edge lands where the text's does. */
.pane-shot { display: flex; flex-direction: column; gap: 9px; align-items: stretch; justify-content: flex-start; }
.pane-shot .pane-h { align-self: stretch; }
.proof-img {
  /* ⚠ THE 181px CAP IS GONE (2026-09-15). It was the capture's true CSS width — the PNG is
     362 because it is shot at 2x — and holding the image to it left a 181px shot floating
     in a 349px pane beside a text pane whose content ran edge to edge. Measured: image at
     x=133 w=181, right pane's text at x=486 w=349. Joe: "the text is floating inside them
     in a way that doesn't make any sense."
     ⚠ AND FILLING THE PANE OUTRIGHT WAS ALSO WRONG — tried, rendered, rejected in the
     same sitting. At ~349 CSS px the shot is sharp (362 real pixels, still under 1:1) but
     Legend's type then renders about twice the size of the monospace beside it, so the
     two panes read as a FONT-SIZE comparison instead of a rendering one. That is the
     claim these panes exist to make, so breaking it to fill a box is a bad trade.
     240px is the compromise: the shot is substantial rather than floating, and the two
     type sizes are close enough that the eye compares rendering.
     ⚠ THE REAL FIX IS A RE-SHOOT at the pane's own width — `node
     EosGrove/marketing/capture_proof_pane.mjs` drives the running app over CDP — which
     gets both a filled pane AND matched type. It needs the app closed and rebuilt, so it
     is its own slice; this rule is the interim. */
  display: block; width: 100%; height: auto; max-width: 240px;
  margin-inline: auto;
  border-radius: 6px;
}

/* The arrow sits between two stretched panes, so it centers itself rather than riding
   the grid's alignment. */
.arrow { color: var(--text-faint); font-size: 20px; align-self: center; }
.lnk { color: var(--accent); }
.tag { color: var(--ok); }

.feat-h {
  font-family: var(--head); font-weight: 700; font-variation-settings: "wdth" 120;
  font-size: clamp(21px, 2.4vw, 30px); letter-spacing: -.02em; margin: 64px 0 0;
}
/* ⚠ These are <h3> elements, not list items, and that is not a style choice. The JSON-LD
   featureList is derived from exactly these headings by scripts/seo-check.mjs, which fails
   the build on any mismatch. They are styled to READ as a demoted list — nine equal-weight
   feature cards was the defect the Hopkins audit named — while staying headings a machine
   can find. Section id="features" is load-bearing for the same reason. */
#features { margin: 0; padding: 0; }
.feats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px 34px; margin-top: 18px; }
@media (max-width: 820px) { .feats { grid-template-columns: 1fr 1fr; } }
@media (max-width: 520px) { .feats { grid-template-columns: 1fr; } }
.feats h3 {
  color: var(--text-dim); font-family: var(--body); font-size: 15.5px;
  font-weight: 400; letter-spacing: 0; line-height: 1.5; margin: 6px 0;
}
.feats h3::before { content: "·"; color: var(--accent); margin-right: 9px; font-weight: 700; }

/* .panel, not .strip — style.css owns .strip and this must not collide with it. */
.panel {
  margin-top: 56px; padding: 26px 28px; border: 1px solid var(--border-strong);
  border-radius: 14px; background: var(--bg-elev);
}
/* h2 — these are top-level sections of screen two, not feature subsections. */
.panel h2 {
  margin: 0; font-family: var(--head); font-weight: 700; font-variation-settings: "wdth" 120;
  font-size: clamp(19px, 2.1vw, 26px); letter-spacing: -.02em;
}
.panel p { margin: 10px 0 0; color: var(--text-dim); max-width: 56ch; font-size: 15.5px; }
.panel .btns { display: flex; gap: 11px; flex-wrap: wrap; margin-top: 18px; }
.panel .subform { margin-top: 16px; }

/* ---- "Where to get it" is TWO COLUMNS (2026-09-15) ----
   The download sat alone in a full-width panel with the right two thirds empty, and the
   signup was a second full-width panel below it. Joe merged them: download left, signup
   right, one panel. The columns are equal-width rather than auto-sized so the divider
   lands mid-panel instead of wherever the longer copy happens to end.
   ⚠ .getit-side keeps id="updates", so /#updates still resolves. */
.panel-get { display: grid; grid-template-columns: 1fr 1fr; gap: 34px; align-items: start; }
.getit-side {
  border-left: 1px solid var(--border); padding-left: 34px; min-width: 0;
}
.getit-main { min-width: 0; }
/* One column below the breakpoint, and the divider moves to the top edge — a left border
   on a stacked block is a dangling vertical line against nothing. */
@media (max-width: 760px) {
  .panel-get { grid-template-columns: 1fr; gap: 26px; }
  .getit-side {
    border-left: 0; padding-left: 0;
    border-top: 1px solid var(--border); padding-top: 24px;
  }
}

/* Topic opt-ins. A <fieldset> is the correct grouping element for a set of related
   checkboxes — it is what gives a screen reader the "Send me" context before each
   label — but its default border and padding are UA chrome, not design, so they are
   removed here rather than the element being swapped for a div that says nothing. */
/* ⚠ `flex: 1 0 100%` IS LOAD-BEARING, and the reason is not obvious from here: `.subform`
   is a WRAPPING FLEX ROW (style.css:316), so the fieldset is a flex ITEM sharing a line
   with the submit button. It used to push the button onto the next line only because the
   "Set once" note made it wide enough to; deleting that note on 2026-09-15 let the button
   ride up beside the checkboxes. Claiming the full row states the intent rather than
   relying on the content's width — the same thing `.subform .status` does one line below
   it in style.css, and for the same reason. */
.subtopics { border: 0; flex: 1 0 100%; margin: 12px 0 2px; padding: 0; min-inline-size: 0; }
.subtopics-legend {
  padding: 0; font-size: 13px; color: var(--text-dim); font-variation-settings: "wdth" 110;
}
.subtopic {
  display: flex; align-items: center; gap: 8px; margin-top: 7px;
  font-size: 14.5px; color: var(--text-dim); cursor: pointer;
}
/* The box itself keeps the platform control — it is already accessible, already
   keyboard-reachable, and already matches what the visitor's OS taught them a
   checkbox looks like. Only the size is nudged, so the hit target clears the 24px
   minimum without the label text growing to match. */
.subtopic input { inline-size: 16px; block-size: 16px; margin: 0; flex: none; cursor: pointer; }
/* The set-once disclosure. Quieter than the labels because it is a caveat, not an
   option — but it states a real limitation, so it takes an ink that clears the contrast
   floor rather than --text-faint. Same reasoning as .platline above. */
.subtopics-note { margin: 9px 0 0; font-size: 13px; color: var(--ink-soft); }

/* ---- the gold flash (2026-09-15) ----
   Fires when someone submits an address with neither topic ticked, which is now the
   ordinary first attempt since both boxes ship blank. `subscribe.js` already refused that
   submission and wrote a status line; this says WHERE to look, because the status sits
   below the button and the visitor is looking at the field they just filled.
   ⚠ It tints and rings the fieldset rather than moving it — no shake, no slide. A wobble
   would be the one thing here that implies the viewport moved, and it would read as an
   error state rather than "you missed a step". Twice, ~1.1s total, then nothing: a
   permanent highlight would still be lit long after it was read.
   ⚠ The padding/margin pair keeps the ring off the labels without shifting the layout
   when the class lands; both are on the base rule, not on .flash. */
.subtopics {
  border-radius: 8px;
  padding: 6px 8px; margin-inline: -8px;
  transition: background-color .2s ease, box-shadow .2s ease;
}
/* ⚠ The legend must leave the border line, or the flash's ring draws straight THROUGH
   "Send me" — a <legend> is laid out ON its fieldset's border by default, which is
   invisible while the border is none and becomes a strikethrough the moment one appears.
   ⚠ `float: left` is NOT the fix and was tried: it takes the legend out of the fieldset's
   layout so thoroughly that the first checkbox label wraps outside the box. `display:
   contents` drops the legend's own box while keeping its text in normal flow, which is
   what puts it above the ring instead of on it. */
.subtopics-legend { display: contents; }
.subtopics.flash { animation: topics-flash 1.1s ease-out 2; }
@keyframes topics-flash {
  0%   { background: transparent;                    box-shadow: 0 0 0 0 transparent; }
  18%  { background: color-mix(in srgb, var(--accent) 17%, transparent);
         box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 55%, transparent); }
  100% { background: transparent;                    box-shadow: 0 0 0 0 transparent; }
}
/* Reduced motion gets the same information without the pulse: the tint and ring appear,
   hold, and fade once. The signal is the COLOR, and the color is what survives. */
@media (prefers-reduced-motion: reduce) {
  .subtopics.flash { animation: topics-flash-still 1.6s ease-out 1; }
  @keyframes topics-flash-still {
    0%, 70% { background: color-mix(in srgb, var(--accent) 17%, transparent);
              box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 55%, transparent); }
    100%    { background: transparent; box-shadow: 0 0 0 0 transparent; }
  }
}
/* style.css gives .platline --text-faint. Here it carries the platform and the freeware
   claim, which is substantive product information rather than decoration, so it takes an
   ink that clears the contrast floor. */
.panel .platline { color: var(--ink-soft); }

/* Fine print by Joe's ruling — it reads quieter than the argument above it deliberately.
   ⚠ Quieter is a SIZE and a position, never a contrast failure: this states the business
   terms, and --text-faint would put them at 3.00:1 on the light page. Below the floor is
   not "subtle", it is unreadable to someone with low vision. */
/* Footer navigation, beside the copyright. The nav at the head of screen two is the
   primary one; this is the conventional second copy for anyone who has read to the end. */
.foot-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 18px 32px; flex-wrap: wrap;
}
.foot-nav { display: flex; gap: 22px; flex-wrap: wrap; }
.foot-nav a {
  font-family: var(--sans); font-size: 13.5px; font-weight: 600;
  text-decoration: none; color: var(--ink-soft);
}
.foot-nav a:hover { color: var(--text); }

/* ---- the shared three-site footer (2026-09-15) ----
   EosGrove left, Minedset centre, Legend right, on a 1fr auto 1fr grid so the middle
   lockup is truly centred whatever the outer two weigh.
   ⚠ THE VALUES BELOW ARE EOSGROVE'S OWN, COPIED, NOT LEGEND'S EQUIVALENTS. The first
   version of this block styled the row in Legend's tokens on the reasoning that importing
   another site's palette for one component was heavy-handed. That was wrong for what this
   component IS: a shared footer asserting that three domains are one company reads as
   three different footers the moment its spacing, sizes and inks differ per site. Joe,
   comparing them: "spacing differs and coloring differs as well. The font size on
   EosGrove is different. Minedset is different and our text isn't white. It's an
   off-white."
   So: ink #efeae1 (--ink-on-dark), hover #e2c48d (--dawn-on-dark-only), gap 1rem
   (--space-2), row padding 2.75rem/1.75rem (--space-4/3), wordmark 1.35rem at 0.11em
   (--track-wordmark), Minedset 1.15rem Archivo 600 at -0.02em. Sourced from
   EosGrove/site/public/{tokens,site}.css; they are hard-coded here because this site does
   not load that token file, and a drift between the two is a real risk — if EosGrove's
   footer changes, this block is the second place to change. */
.footer-marks {
  display: grid; grid-template-columns: 1fr auto 1fr;
  align-items: center; gap: 1rem;
  /* ⚠ padding-BLOCK, never the shorthand: this div also carries `.rail`, whose 32px
     horizontal padding is what keeps the outer two lockups off the screen edges. A
     `padding: 2.75rem 0 1.75rem` shorthand zeroed it and pushed EOSGROVE and LEGEND
     flush against the window. */
  padding-block: 2.75rem 1.75rem;
  margin-bottom: 1.75rem;
  border-bottom: 1px solid color-mix(in srgb, #efeae1 18%, transparent);
}
.footer-marks .lockup {
  display: flex; align-items: center; gap: 1rem;
  text-decoration: none; color: #efeae1;
}
.footer-marks .lockup:hover { color: #e2c48d; }
/* ⚠ `:not(.wordmark-img)` matters: the EosGrove wordmark is also an <img>, and without
   the exclusion this rule squashes it to a 30x30 square. It is a wide glyph strip, sized
   by its own rule below. */
.footer-marks .lockup img:not(.wordmark-img),
.footer-marks .mark,
.footer-marks .orb { display: block; width: 30px; height: 30px; flex: none; }
/* Minedset's wordmark: Archivo 600 at 1.15rem / -0.02em, sentence case — its own kit's
   display face, exactly as eosgrove.com sets it. Archivo is self-hosted here already. */
.footer-marks .wordmark {
  font-family: "Archivo", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-weight: 600;
  font-size: 1.15rem;
  letter-spacing: -0.02em;
  line-height: 1;
  text-transform: none;
}
/* The EosGrove wordmark is a white-on-transparent PNG (see index.html for why it is an
   image at all). White suits the dark page; on the light one it would vanish, so it is
   inverted there. `filter`, not two files: one asset, and the tint follows the theme
   the way the text lockups' currentColor does.
   ⚠ Opacity rather than a colour match — the other two lockups sit at --text-dim, and
   dimming white lands in the same place without hard-coding an ink that would then drift
   from the token beside it. */
/* ⚠ 24px, not 19 — the render carries ~6px of internal padding (room for Cinzel's
   tracking on the final E), so the box is taller than the letters. At 19 the caps came
   out around 13px against Minedset's and Legend's, and it read as a squashed sibling.
   ⚠ NO OPACITY. An earlier version dimmed this to .82 to approximate --text-dim, which
   was the same mistake as styling the whole row in Legend's tokens: the glyphs are
   rendered in #efeae1 — EosGrove's actual ink, the same one the other two lockups use —
   so dimming them re-introduces exactly the mismatch the re-render fixed. */
.wordmark-img { display: block; height: 24px; width: auto; }
/* The text lockups shift to dawn on hover via `color`; an image cannot, so it takes the
   nearest equivalent — a warm sepia — rather than sitting inert while its neighbours move. */
.footer-marks .lockup:hover .wordmark-img {
  filter: sepia(.45) saturate(1.7) brightness(1.02);
}
@media (prefers-color-scheme: light) {
  .wordmark-img { filter: invert(1); }
  .footer-marks .lockup:hover .wordmark-img {
    filter: invert(1) sepia(.5) saturate(2) brightness(.8);
  }
}
.sib-minedset { justify-self: center; }
.sib-legend { justify-self: end; }
/* The Legend word is outlines, so it inherits the link colour through currentColor and
   hovers with the rest of the lockup. */
.sib-legend .word { display: block; height: 15px; width: auto; fill: currentColor; }

/* Three lockups do not fit one phone line; they stack, left-aligned, in the same order. */
@media (max-width: 34rem) {
  .footer-marks { grid-template-columns: minmax(0, 1fr); gap: 14px; }
  .sib-minedset, .sib-legend { justify-self: start; }
}

.fineprint { margin-top: 46px; padding-top: 18px; border-top: 1px solid var(--border); }
.fineprint p { margin: 0; color: var(--text-dim); max-width: 66ch; font-size: 13.5px; line-height: 1.65; }
.fineprint b { color: var(--text); }

/* ---- screen two's motion: three states, and the middle one is load-bearing ----
     default : no JS — the finished logo, correct and still
     .armed  : JS present — letters lifted out of the clipped box and the strike
               collapsed to nothing, so the space reads BLANK before the fall
     .live   : scrolled into view — the sequence runs, once, then stops
   Arming cannot be pure CSS: with scripting off it would never un-arm and the wordmark
   would never appear at all.

   ⚠ REDUCED MOTION IS NO LONGER IN THE DEFAULT ROW (Joe-ruled 2026-09-15, matching the
   same day's rulings on eosgrove.com's hero and minedset.io's dot field). The wordmark
   was reported static in Safari in Legend's 2026-09-10 mail and read as a WebKit bug for
   five days; it was this media query, on a Mac with Reduce Motion on. The browser was a
   coincidence of which machine the reporter was using.

   The drop, the slam and the strike RUN under reduced motion: they are brief, they
   finish, and a letterform falling into a box is not the motion the setting exists to
   suppress (parallax, zoom, slide — motion implying the VIEWER moved). What stays
   gated is the ORB: 22 seconds of continuous rotation is ambient perpetual motion,
   which is the same call as minedset's field. Its keyframes land on 9 and 6 whole
   turns, so the un-animated state is already the logo exactly as drawn — nothing is
   lost by holding it. */
.next .gap { display: none; }

.next .lets { transform-box: view-box; transform-origin: 50% 100%; }
.next .strike { transform-box: fill-box; transform-origin: right center; }

.next.armed .gap { display: block; }
.next.armed .lets { transform: translateY(-165%); }
.next.armed .strike { transform: scaleX(0); }

@media (prefers-reduced-motion: no-preference) {
  .next .orb-ellipse, .next .orb-diameter { transform-box: fill-box; transform-origin: center; }

  /* The app's own orb animation (Orb.tsx + App.css .orb-spin): amber ellipse clockwise,
     blue diameter counter-clockwise, ring and centre dot fixed. Its 2.4s/3.6s rates kept
     in proportion, made to vary slow/medium, and landing on 9 and 6 WHOLE turns at 22s so
     the frozen frame is the logo exactly as drawn. */
  .next.live .orb-ellipse  { animation: orb-cw  22s forwards; }
  .next.live .orb-diameter { animation: orb-ccw 22s forwards; }
  @keyframes orb-cw {
    0%{transform:rotate(0deg);animation-timing-function:ease-in-out}
    12%{transform:rotate(180deg);animation-timing-function:ease-in-out}
    27%{transform:rotate(900deg);animation-timing-function:ease-in-out}
    39%{transform:rotate(1080deg);animation-timing-function:ease-in-out}
    54%{transform:rotate(1800deg);animation-timing-function:ease-in-out}
    66%{transform:rotate(1980deg);animation-timing-function:ease-in-out}
    81%{transform:rotate(2700deg);animation-timing-function:ease-in-out}
    90%{transform:rotate(2880deg);animation-timing-function:ease-in-out}
    100%{transform:rotate(3240deg)}
  }
  @keyframes orb-ccw {
    0%{transform:rotate(0deg);animation-timing-function:ease-in-out}
    12%{transform:rotate(-120deg);animation-timing-function:ease-in-out}
    27%{transform:rotate(-600deg);animation-timing-function:ease-in-out}
    39%{transform:rotate(-720deg);animation-timing-function:ease-in-out}
    54%{transform:rotate(-1200deg);animation-timing-function:ease-in-out}
    66%{transform:rotate(-1320deg);animation-timing-function:ease-in-out}
    81%{transform:rotate(-1800deg);animation-timing-function:ease-in-out}
    90%{transform:rotate(-1920deg);animation-timing-function:ease-in-out}
    100%{transform:rotate(-2160deg)}
  }
}

/* ---- the wordmark sequence: OUTSIDE the reduced-motion query, deliberately ----
   Everything below finishes and stops. See the note above the .armed rules for the
   ruling; the orb above is the only part that stays gated. */

.next.live .lets {
  animation: drop .62s cubic-bezier(.55,.06,.68,.19) forwards,
             slam .34s ease-out .62s forwards;
}
@keyframes drop { from { transform: translateY(-165%); } to { transform: translateY(0); } }
@keyframes slam {
  0%   { transform: translateY(0) scaleY(1)    scaleX(1); }
  26%  { transform: translateY(0) scaleY(.90)  scaleX(1.035); }
  62%  { transform: translateY(0) scaleY(1.035) scaleX(.99); }
  100% { transform: translateY(0) scaleY(1)    scaleX(1); }
}

/* One unbroken line, drawn right to left from the D's overshoot. fill-box, never
   view-box: under view-box a length or percentage resolves from the viewport box's
   own corner, which put the origin in the MIDDLE of the word. */
.next.live .strike { animation: sweep 1.8s cubic-bezier(.3,.7,.35,1) 1.00s both; }
@keyframes sweep { from { transform: scaleX(0); } to { transform: scaleX(1); } }

/* Then it recedes LEFT TO RIGHT, each piece collapsing INTO the letter it belongs to
   rather than toward its own centre — a gap shrunk from the centre evaporates in the
   dead space between two letters instead of tucking into the next one. */
.next .gap, .next .lead { transform-box: fill-box; transform-origin: right center; }
.next .lead-out { transform-origin: left center; }
.next.live .gap, .next.live .lead { animation: recede .45s cubic-bezier(.4,0,.2,1) both; }
@keyframes recede { from { transform: scaleX(1); } to { transform: scaleX(0); } }
.next.live .lead-in  { animation-delay: 2.95s; }
.next.live .gap-1    { animation-delay: 3.04s; }
.next.live .gap-2    { animation-delay: 3.13s; }
.next.live .gap-3    { animation-delay: 3.22s; }
.next.live .gap-4    { animation-delay: 3.31s; }
.next.live .gap-5    { animation-delay: 3.40s; }
.next.live .lead-out { animation-delay: 3.49s; }
