/* skin.css — hanakenji (Hana & Kenji). Tokens, type, layout, ornament.
   Loaded after engine.css and atelier.css. Sets custom properties; never forks engine JS.

   The look is the old Sakura demo's own, rebuilt on the engine: cherry, sumi ink, warm paper,
   one vertical line of Japanese on the hero and a hanko seal on every eyebrow. It is deliberately
   NOT site 1's letterpress burgundy — site 2 exists to prove the engine reskins.

   ── Type, decided 2026-08-23 ──────────────────────────────────────────────────
   The reference demo used Shippori Mincho and Karla, both webfonts, which the project's hard
   rule forbids by CDN. Two honest routes existed: a system mincho stack, or self-hosting a cut
   subset. A CJK subset cannot be cut in this project (no font tooling here), and a broken
   @font-face is worse than an honest fallback — so the system stack ships, with the self-host
   slot pre-wired exactly as site 1 did before its woff2 files landed: the family is NAMED FIRST
   below and simply does not resolve until a file exists. Dropping one in is a one-block change.

   Mincho fallbacks land unevenly across platforms — Hiragino on macOS, Yu Mincho on Windows,
   Songti or nothing elsewhere — so the stack ends at a Latin serif that is never wrong, and the
   design never depends on a mincho actually being present.

   ── --on-accent, audited by computation not by eye ────────────────────────────
   --accent #c2506a has a relative luminance of 0.182. Against pure white that is 4.52:1, which
   clears 4.5:1 by a hair; against site 1's ivory it is ~4.4:1, which does NOT. So --on-accent is
   pure #fff here, and #c2506a is never used for small text on paper (that would be 4.19:1 on
   --bg — a fail). Small accent text uses --accent-ink #a83b55: 5.70:1 on paper, 6.14:1 on white.
   ─────────────────────────────────────────────────────────────────────────────── */

/* @font-face slot — intentionally empty. A self-hosted mincho subset drops in here and the
   stack below picks it up with no other change:
   @font-face { font-family: "Shippori Mincho"; src: url("../assets/type/<file>.woff2") format("woff2"); font-display: swap; }
*/

[data-skin="hanakenji"] {
  /* raw palette — the reference demo's own five, measured off _reference/sakura/sakura.css */
  --cherry: #c2506a;
  --cherry-ink: #a83b55;
  --blossom: #ecb6c4;
  --blossom-pale: #f6d7de;
  --paper: #faf6ef;
  --sumi: #2b2733;
  --card-raw: #fffdf8;
  --matcha: #8a9b7d;

  /* semantic */
  --bg: var(--paper);
  --bg-raised: color-mix(in oklab, var(--sumi) 4%, var(--paper));
  --bg-sink: color-mix(in oklab, var(--blossom) 26%, var(--paper));
  --card: var(--card-raw);
  --ink: var(--sumi);
  /* ⚠ Both of these are solved for AA, not chosen by eye — measured 2026-09-06 across the three
     paper grounds (--paper, --bg, --bg-raised). `--faint` shipped at 42%, which is **2.64:1**,
     and it is the small-label colour in ~28 rules: every eyebrow, ledger dt, plate credit, form
     label, counter caption and stat label on all four pages. At 14px those need 4.5:1.
     The ramp survives because 70 / 62 is still a visible step; what does not survive is a third
     grey light enough to read as "faint" AND legible as text — there is no room for one. */
  --muted: color-mix(in oklab, var(--sumi) 70%, var(--paper));
  --faint: color-mix(in oklab, var(--sumi) 62%, var(--paper));
  --rule: color-mix(in oklab, var(--sumi) 15%, var(--paper));
  --rule-strong: color-mix(in oklab, var(--sumi) 34%, var(--paper));
  --accent: var(--cherry);
  --accent-ink: var(--cherry-ink);
  --accent-soft: color-mix(in oklab, var(--cherry) 11%, var(--paper));
  --on-accent: #fff;
  --field: #fff;

  /* type */
  --display: "Shippori Mincho", "Hiragino Mincho ProN", "Yu Mincho", "Songti SC",
             "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
  --body: "Karla", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif;
  --ui: var(--body);
  --jp: "Hiragino Mincho ProN", "Yu Mincho", "Noto Serif JP", "Songti SC", serif;

  --step--1: clamp(.8rem, .77rem + .12vw, .875rem);
  --step-0: clamp(1rem, .97rem + .18vw, 1.0625rem);
  --step-1: clamp(1.15rem, 1.08rem + .36vw, 1.34rem);
  --step-2: clamp(1.4rem, 1.24rem + .84vw, 1.95rem);
  --step-3: clamp(1.8rem, 1.46rem + 1.8vw, 2.8rem);
  --step-4: clamp(2.2rem, 1.62rem + 3.2vw, 3.9rem);
  --step-5: clamp(2.7rem, 1.6rem + 5.6vw, 6rem);

  /* shape + motion
     ⚠ --radius was 10px until 2026-09-07 (§24, the user's "smoothen edges" note). It is read by
     every plate, frame, card, swatch and field on the site AND by --a-corner, so it is the one
     knob for the whole pass — do not add per-element radii to soften something. */
  --radius: 14px;
  --rule-w: 1px;
  --gap: clamp(1.1rem, .8rem + 1.2vw, 1.9rem);
  --page: 1020px;

  /* section 17 — the offset sheet and the rail. The measure sits LEFT inside the sheet and the
     right-hand column is margin; --break is how far a plate may push back out of the measure. */
  --col: 62ch;
  --gutter: clamp(1.2rem, 3vw, 3rem);
  --break: clamp(0rem, 4vw, 3.4rem);
  --rail-w: 3.8rem;
  --motion-fast: 180ms;
  --motion-slow: 620ms;
  --ease: cubic-bezier(.2, .7, .3, 1);

  /* atelier tokens this skin tunes */
  --a-tone: sepia(.06) saturate(1.03);
  --a-lift: 0 1px 2px rgba(43, 39, 51, .05), 0 10px 30px rgba(43, 39, 51, .07);
  --a-shadow: 0 6px 22px rgba(43, 39, 51, .09);
  --a-corner: var(--radius);

  /* petals — read by engine/petals.js off the canvas */
  --petal-near: var(--blossom);
  --petal-far: var(--blossom-pale);
  --petal-size: 6.5;
  --petal-fall: 15;
}

/* the rail is fixed to the left edge at every width; only its register changes */
@media (min-width: 64em) { [data-skin="hanakenji"] { --rail-w: 15rem; } }

/* ---------- base ---------- */
html { scroll-behavior: smooth; scroll-padding-top: 2rem; overflow-x: clip; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
[data-skin="hanakenji"] body {
  margin: 0;
  padding-left: var(--rail-w);
  overflow-x: clip;
  background: var(--bg);
  color: var(--ink);
  font: var(--step-0)/1.65 var(--body);
  text-wrap: pretty;
  -webkit-font-smoothing: antialiased;
}
[data-skin="hanakenji"] .a-petals { --petal-near: var(--blossom); --petal-far: var(--blossom-pale); }

[data-skin="hanakenji"] :is(h1, h2, h3, h4) {
  font-family: var(--display);
  font-weight: 400;
  line-height: 1.14;
  letter-spacing: .01em;
  margin: 0;
}
[data-skin="hanakenji"] :where(h1) { font-size: var(--step-4); }
[data-skin="hanakenji"] :where(h2) { font-size: var(--step-3); }
[data-skin="hanakenji"] :where(h3) { font-size: var(--step-1); }
[data-skin="hanakenji"] p { margin: 0; }
[data-skin="hanakenji"] :is(p, li) a:not([class]) {
  color: var(--accent-ink);
  text-decoration-thickness: 1px;
  text-underline-offset: .18em;
}
[data-skin="hanakenji"] :is(p, li) a:not([class]):hover { color: var(--ink); }

/* Japanese is an ACCENT on this site, never a translation layer — site 5 is the bilingual
   test. Anything in here is set in a mincho at a size that reads as ornament. */
[data-skin="hanakenji"] .jp { font-family: var(--jp); font-weight: 400; }
.seal {
  display: inline-grid;
  place-items: center;
  width: 1.55em; height: 1.55em;
  margin-inline-end: .5em;
  background: var(--accent);
  color: var(--on-accent);
  border-radius: 3px;
  font-size: max(12px, .88em);
  line-height: 1;
  letter-spacing: 0;
  vertical-align: -.35em;
}
.tategaki {
  writing-mode: vertical-rl;
  font-family: var(--jp);
  letter-spacing: .22em;
}

/* ---------- shared layout ---------- */
.wrap { width: min(100% - 2.4rem, var(--page)); margin-inline: auto; }
.wrap--narrow { width: min(100% - 2.4rem, 62ch); margin-inline: auto; }

/* ---------- the offset sheet: site 2's grid claim ----------
   Two columns, never centred type: the measure on the left of the sheet, marginalia on the
   right. Images do not bleed the viewport here (that is site 1's move) — a .plate breaks LEFT
   out of the measure and RIGHT across the margin, so the picture crosses the column instead of
   escaping the page. Below 60em the margin collapses and the marginalia, which is ornament,
   goes with it. */
.sheet { width: min(100% - 2.6rem, 60rem); margin-inline: auto; }
.spread, .spread-head {
  display: grid;
  grid-template-columns: minmax(0, var(--col)) minmax(0, 1fr);
  column-gap: var(--gutter);
}
.spread { row-gap: var(--gap); align-items: start; }
.spread > * { grid-column: 1; }
.spread > .spread-head, .spread > .plate, .spread > .full { grid-column: 1 / -1; }
.spread-head { align-items: start; }
.spread-head-say { display: grid; gap: .6rem; }
.spread-head > .marg { grid-column: 2; justify-self: start; margin: 0; }
.marg { color: var(--faint); font: var(--step--1)/1.6 var(--ui); }
.marg-tate { color: var(--accent-ink); font-size: clamp(.82rem, .74rem + .3vw, 1rem); letter-spacing: .3em; }

/* a plate: the photograph, crossing the column */
.plate { margin: 0 0 0 calc(-1 * var(--break)); }
.plate img { display: block; width: 100%; border-radius: var(--radius); }
.plate figcaption { margin-top: .7rem; }
.plate-credit { font: var(--step--1)/1 var(--ui); letter-spacing: .14em; text-transform: uppercase; color: var(--faint); text-align: right; }
.plate--say figcaption {
  display: grid; gap: .5rem var(--gutter);
  margin-top: .9rem; padding-top: .9rem;
  border-top: 2px solid var(--ink);
}
@media (min-width: 48em) { .plate--say figcaption { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: start; } }
.plate-say-body { display: grid; gap: .7rem; color: var(--muted); }
@media (max-width: 59.99em) {
  .spread, .spread-head { grid-template-columns: minmax(0, 1fr); }
  .spread-head > .marg { display: none; }
  .plate { margin-left: 0; }
}
.center { text-align: center; }
.stack { display: grid; gap: var(--gap); }
.band { padding-block: clamp(3rem, 2rem + 5vw, 5.5rem); position: relative; }
.band--flush { padding-block: clamp(1.6rem, 1rem + 2vw, 2.6rem); }
.band--edge { padding-block: clamp(2.2rem, 1.6rem + 3vw, 4rem); }
/* ⚠ --bg-raised is BRIGHTER than --paper (oklab L .947 against .930), so both greys lose about
   .35 of their ratio on it — which is what put the fortune band's lede at 4.28 and the footer's
   dt at 2.44. The correction is a token re-point on the two grounds that are raised, per §23.1:
   never a per-element colour, or the next element added to either band inherits the failure. */
.band--raised { background: var(--bg-raised); }
/* ⚠ 2026-09-14 · THE SINK GROUND WAS MISSING FROM THIS RE-POINT, and it is the worst of the
   three: --bg-sink is the blossom tint (rgb 247,229,228), where --faint measures **4.11:1** —
   the lowest small-text reading on site 2, against a 4.5 floor. Measured off the tokens
   themselves: 62% is 4.64 on --paper, 4.28 on --bg-raised, 4.11 on --bg-sink; at 66% the sink
   reads 4.62. `.reply-ask` carries `background: var(--bg-sink)` directly rather than the band
   class, so it is named here too — the hint under "Can you come?" is the run that fails, on the
   most functional surface on the site. Same 66% as the raised grounds, so the ramp is unchanged
   (--muted stays 70%); this is a token re-point per ground, never a per-element colour. */
.band--raised, .foot,
.band--sink, .reply-ask { --faint: color-mix(in oklab, var(--sumi) 66%, var(--paper)); }
.band--sink { background: var(--bg-sink); }
.band--bleed { padding-block: 0; }
/* .band--ink's dress is §23.1 at the foot of this file, and it is a TOKEN RE-POINT rather than
   the four per-element overrides that used to live here. The old form only coloured headings,
   eyebrows and ledes, which is why .swatch span, .list-checks li and .link were all still
   reading --faint / --accent-ink against deep sumi. Re-pointing the tokens fixes every child
   at once and needs no modifier — the same route engine atelier §K takes. */

/* the shoji divider: a paper screen seen edge-on. Site 2's own rule, not site 1's ornament. */
.shoji {
  height: 20px;
  width: min(100% - 2.6rem, 60rem);
  margin-inline: auto;
  background:
    linear-gradient(var(--rule), var(--rule)) center/100% 1px no-repeat,
    repeating-linear-gradient(90deg, transparent 0 calc(12.5% - 1px), var(--rule) calc(12.5% - 1px) 12.5%);
  opacity: .8;
}

/* ---------- type utilities ---------- */
[data-skin="hanakenji"] .eyebrow {
  display: flex; align-items: center;
  font: 600 var(--step--1)/1.4 var(--ui);
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--accent-ink);
  margin: 0;
}
.eyebrow--quiet { color: var(--faint); }
.eyebrow--center { justify-content: center; }
[data-skin="hanakenji"] .lede {
  font-family: var(--display);
  font-size: var(--step-1);
  line-height: 1.55;
  color: var(--muted);
  max-width: 36em;
}
[data-skin="hanakenji"] .meta {
  font: var(--step--1)/1.5 var(--ui);
  letter-spacing: .1em;
  color: var(--muted);
}
[data-skin="hanakenji"] .hint { font: var(--step--1)/1.55 var(--ui); color: var(--faint); }
.prose { max-width: 38em; }
.prose p + p { margin-top: 1.05em; }
.figure-note { font: var(--step--1)/1.55 var(--ui); color: var(--faint); }
.sec-head-block { display: grid; gap: .5rem; margin-bottom: var(--gap); }
[data-skin="hanakenji"] .sec-head { font-size: var(--step-2); }

/* ---------- controls ---------- */
.btn, .btn--rule {
  display: inline-flex; align-items: center; gap: .5em;
  min-height: 44px;
  padding: .72em 1.5em;
  border-radius: 999px;
  border: var(--rule-w) solid var(--accent);
  background: var(--accent); color: var(--on-accent);
  font: 600 var(--step--1)/1 var(--ui);
  letter-spacing: .12em; text-transform: uppercase;
  text-decoration: none; cursor: pointer;
  transition: background var(--motion-fast) var(--ease), transform var(--motion-fast) var(--ease);
}
.btn:hover, .btn--rule:hover { background: var(--cherry-ink); border-color: var(--cherry-ink); }
.btn:active, .btn--rule:active { transform: translateY(1px); }
.btn--quiet {
  background: transparent; color: var(--ink);
  border-color: var(--rule-strong);
}
.btn--quiet:hover { background: var(--accent-soft); border-color: var(--accent); color: var(--accent-ink); }
.btn--small { min-height: 40px; padding: .55em 1.1em; }
.btn--big { padding: .95em 1.8em; font-size: var(--step-0); letter-spacing: .1em; }
.btn-arrow { transition: transform var(--motion-fast) var(--ease); }
:is(.btn, .btn--rule):hover .btn-arrow { transform: translateX(3px); }
[data-skin="hanakenji"] .link {
  /* inline-flex + min-height, not inline-block: at --step--1/1 this link was 17px tall and
     failed the 44px gate on both onward cards (measured 2026-08-28) */
  display: inline-flex; align-items: center;
  min-height: 44px;
  font: 600 var(--step--1)/1 var(--ui);
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--accent-ink); text-decoration: none;
  border-bottom: 1px solid var(--blossom);
  padding-bottom: .3em;
}
[data-skin="hanakenji"] .link:hover { color: var(--ink); border-color: var(--ink); }
:is(a, button, input, select, textarea):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}

/* ---------- 17 · the rail: site 2's navigation claim (2026-08-24) ----------
   One element, two registers. Down the left edge for the whole height of the window: the couple
   and the four pages at its head, then the sections of THIS page with a progress line beside
   them, then the design-study marker at its foot. Site 1 owns the sticky top bar; nothing here
   may drift back toward one.

   ≥ 64em  the rail is 15rem wide and everything is lettered.
   < 64em  it narrows to 3rem: the brand and the pages button set vertically, the section marks
           reduced to their ticks (the labels stay in the accessible name, clipped not removed),
           and the pages list opens as a panel beside the strip.
   The body carries padding-left: var(--rail-w) — the rail never overlaps the page. */
.rail {
  position: fixed; inset-block: 0; left: 0; z-index: 55;
  width: var(--rail-w);
  display: flex; flex-direction: column; gap: .7rem;
  padding: .8rem .4rem .8rem;
  background: color-mix(in oklab, var(--paper) 94%, transparent);
  backdrop-filter: blur(7px);
  border-right: var(--rule-w) solid var(--rule);
}
/* ⚠ The rail paints its own ground and that ground is BRIGHTER than the sheet (oklab L .974
   against --paper's .930), so every colour on it loses about .4 of its ratio — measured
   2026-09-06. It cost the whole strip: the wordmark's ampersand and the CURRENT anchor numeral
   at 3.92:1 in --accent, and the date plus the six other numerals at 4.32:1 in --faint. The
   current numeral is the one mark on the rail that has to read, so it failing is the worst of
   the nine. Re-pointed at the rail, per §23.1 — never per element, or the next thing added to
   the strip inherits the failure. Both marks that use --accent as a FILL rather than as text
   (the progress line, the tick) simply get the darker cherry, which suits them. */
.rail { --faint: color-mix(in oklab, var(--sumi) 66%, var(--paper)); --accent: var(--accent-ink); }
.rail-top { display: grid; gap: .4rem; justify-items: center; }
/* 44px in BOTH registers, and it fails differently in each — measured 2026-08-28: at ≤63.99em
   the wordmark goes vertical and the date is hidden, so the link is 18.7×52.6 (too narrow); at
   1440 the rail is 15rem wide and it is 73.8×38.3 (too short). Hence both minimums. */
.rail-brand {
  display: grid; gap: .18rem; justify-items: center; align-content: center;
  min-width: 44px; min-height: 44px;
  text-decoration: none; color: var(--ink);
}
[data-skin="hanakenji"] .rail-brand b { font: 400 var(--step-1)/1 var(--display); letter-spacing: .16em; }
.rail-brand i { font-style: normal; color: var(--accent); }
.rail-brand span { font: var(--step--1)/1 var(--ui); letter-spacing: .2em; color: var(--faint); }
.rail-toggle {
  min-height: 44px; min-width: 44px; padding: .5em .7em;
  background: transparent; border: var(--rule-w) solid var(--rule-strong);
  border-radius: 999px; color: var(--ink);
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .16em; text-transform: uppercase;
  cursor: pointer;
}
.rail-toggle:hover { border-color: var(--accent); color: var(--accent-ink); }
.rail-pages ul { list-style: none; margin: 0; padding: 0; display: grid; }
.rail-pages a {
  display: flex; align-items: center; min-height: 44px; padding: .3rem .55rem;
  font: 600 var(--step--1)/1.2 var(--ui); letter-spacing: .13em; text-transform: uppercase;
  color: var(--ink); text-decoration: none;
  border-left: 2px solid transparent;
}
.rail-pages a:hover { color: var(--accent-ink); background: var(--accent-soft); }
.rail-pages a[aria-current] { border-left-color: var(--accent); color: var(--accent-ink); }

/* the sections of this page, and how far down them the reader is. The progress line is a child
   of the rail, not of the scrolling list — an absolutely positioned child of a scroller scrolls
   away with the content, which is not what a progress indicator may do. The scrollbar is thin
   and dimmed but never hidden: at a short viewport the list scrolls, and a mark with no visible
   affordance is a mark nobody finds. */
.rail-here { position: relative; flex: 1 1 auto; min-height: 0; overflow-y: auto; scrollbar-width: thin; scrollbar-color: var(--rule-strong) transparent; }
.rail-track { position: absolute; left: 0; top: 0; bottom: 0; width: 2px; background: var(--rule); }
.rail-track i {
  position: absolute; inset: 0 0 auto 0; height: 100%;
  background: var(--accent);
  transform: scaleY(var(--rail-progress, 0)); transform-origin: top;
}
.rail-here ol { list-style: none; margin: 0; padding: 0; display: grid; }
.rail-here a {
  display: flex; align-items: center; gap: .65rem; min-height: 44px; padding: .25rem .2rem;
  color: var(--muted); text-decoration: none;
  font: var(--step--1)/1.25 var(--ui);
}
.rail-tick { flex: none; width: .42rem; height: .42rem; margin-left: .3rem; background: var(--rule-strong); transform: rotate(45deg); }
/* move F, 2026-09-06: on the pages that have been through the cinematic pass the anchors are
   NUMBERED instead of ticked — four unlabelled diamonds on a 2px track read as a scrollbar,
   which is the whole finding. The numeral survives the narrow register where the label cannot,
   so the 3.8rem rail stops being five anonymous marks. .rail-tick stays until story / travel /
   rsvp are converted; a page carries one or the other, never both. */
.rail-no {
  flex: none; min-width: 1.55rem; text-align: center;
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .06em;
  font-variant-numeric: tabular-nums; color: var(--faint);
}
.rail-here a:hover .rail-no { color: var(--ink); }
.rail-here a[data-current] .rail-no { color: var(--accent); }
[data-skin="hanakenji"] .rail-brand .brand-full { font: 400 var(--step-1)/1.05 var(--display); letter-spacing: .1em; text-align: center; }
.rail-brand .brand-full i { font-style: normal; color: var(--accent); margin-inline: .3em; }
.rail-brand .brand-short { display: none; }
.rail-rsvp-by { white-space: nowrap; }
@media (max-width: 63.99em) {
  .rail-brand .brand-full { display: none; }
  .rail-brand .brand-short { display: block; writing-mode: vertical-rl; letter-spacing: .1em; }
  .rail-rsvp-by { display: none; }
}
.rail-here a:hover { color: var(--ink); }
.rail-here a[data-current] { color: var(--accent-ink); }
.rail-here a[data-current] .rail-tick { background: var(--accent); transform: rotate(45deg) scale(1.35); }
.rail-foot { display: grid; gap: .3rem; justify-items: center; text-align: center; }
.rail-reply { font: var(--step--1)/1.3 var(--ui); letter-spacing: .14em; text-transform: uppercase; color: var(--faint); }
.rail-rsvp {
  /* ⚠ gap, not the markup's space: this is a flex container, so the leading whitespace inside
     `.rail-rsvp-by` is stripped and the pill read "REPLY· BY 1 MAR". */
  display: inline-flex; align-items: center; justify-content: center; gap: .3em;
  min-height: 44px; padding: .55em 1em; border-radius: 999px;
  background: var(--accent); color: var(--on-accent);
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .14em; text-transform: uppercase;
  text-decoration: none;
}
.rail-rsvp:hover { background: var(--cherry-ink); }

@media (max-width: 63.99em) {
  /* 3.8rem − 1px border − an 11px thin scrollbar = ~49px of content, so every target inside
     clears 44px WHILE THE LIST IS SCROLLING, which is when it is narrowest. Measure it with the
     rail overflowing (a short viewport), never with it at rest. */
  .rail { padding-inline: 0; }
  .rail-brand b { writing-mode: vertical-rl; letter-spacing: .1em; }
  .rail-brand span, .rail-reply { display: none; }
  .rail-toggle { writing-mode: vertical-rl; padding: .8em .45em; border-radius: 999px; letter-spacing: .2em; }
  .rail-pages {
    position: fixed; left: var(--rail-w); top: .8rem;
    width: min(15rem, calc(100vw - var(--rail-w) - 1rem));
    padding: .35rem;
    background: var(--card); border: var(--rule-w) solid var(--rule-strong);
    border-radius: var(--radius); box-shadow: var(--a-lift);
    display: none;
  }
  .rail-pages[data-open] { display: block; }
  .rail-here a { justify-content: center; gap: 0; }
  .rail-tick { margin-left: 0; }
  .rail-name { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
  .rail-rsvp { padding: .55em .6em; letter-spacing: .06em; font-size: .72rem; }
}
@media (min-width: 64em) { .rail-toggle { display: none; } }
/* In the narrow register the labels are clipped and the anchors are numerals alone, so the
   progress track has to sit BESIDE them or the column reads as seven loose numbers rather than
   a scale. At left: 0 it is flush against the viewport edge, where it is invisible. */
@media (max-width: 63.99em) { .rail-track { left: .5rem; } }

/* the noren over the top of the page — see §19, the ornament layer, at the foot of this file */
.noren {
  position: relative; z-index: 4;
  display: flex; gap: 6px;
  height: clamp(96px, 9vw, 132px);
  padding: 9px clamp(.7rem, 2vw, 1.4rem) 0;
}

/* ---------- the split hero: site 2's hero claim (2026-08-24) ----------
   Photograph one half, paper the other, the names SET IN THE PAPER. No scrim, no centred names
   over a full-bleed frame, no floating date card — all three are site 1's, and all three were on
   this page until today. Nothing is typed over the photograph, so the contrast arithmetic is
   trivially safe whatever image lands in the slot. */
.hero { padding: 0; display: grid; }
@media (min-width: 62em) {
  .hero { grid-template-columns: 1.04fr .96fr; align-items: stretch; min-height: min(94svh, 52rem); }
}
.hero-paper {
  position: relative;
  display: grid; align-content: center;
  padding: clamp(1.9rem, 1.2rem + 4vw, 4rem) clamp(1.3rem, .8rem + 3vw, 3.2rem);
}
.hero-say { display: grid; gap: .75rem; max-width: 34rem; }
[data-skin="hanakenji"] .hero-names { font-size: var(--step-5); line-height: .96; }
.hero-amp { display: block; font-family: var(--display); font-size: .3em; font-style: italic; color: var(--accent-ink); margin-block: .14em; letter-spacing: .02em; }
.hero-jp { font-family: var(--jp); font-size: var(--step-1); letter-spacing: .3em; color: var(--accent-ink); }
.hero-lede { color: var(--muted); max-width: 32ch; }
.hero-when { display: grid; margin: .4rem 0 0; border-top: 2px solid var(--ink); }
.hero-when > div {
  display: grid; grid-template-columns: minmax(0, 7.5rem) minmax(0, 1fr);
  column-gap: 1rem; align-items: baseline;
  padding-block: .55rem; border-bottom: var(--rule-w) solid var(--rule);
}
.hero-when dt { font: 600 var(--step--1)/1.4 var(--ui); letter-spacing: .16em; text-transform: uppercase; color: var(--faint); }
[data-skin="hanakenji"] .hero-when dd { margin: 0; font: 400 var(--step-1)/1.3 var(--display); }
.hero-act { margin-top: .7rem; }
/* the date, vertical, in the seam between the paper and the photograph */
.hero-tate {
  position: absolute; top: clamp(1.2rem, 4vh, 3rem); right: clamp(.5rem, 1.2vw, 1.1rem);
  font-size: clamp(.82rem, .72rem + .35vw, 1rem);
  color: var(--accent-ink);
}
@media (max-width: 61.99em) { .hero-tate { display: none; } }
.hero-shot { position: relative; margin: 0; }
.hero-shot img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: 50% 42%; }
@media (max-width: 61.99em) {
  .hero-shot { order: -1; }
  .hero-shot img { height: auto; aspect-ratio: 5 / 4; }
}

/* ---------- countdown ---------- */
.plate-count { border-top: var(--rule-w) solid var(--rule); padding-top: .8rem; margin-top: .3rem; }
.plate-count-cap { font: 600 var(--step--1)/1 var(--ui); letter-spacing: .16em; text-transform: uppercase; color: var(--faint); margin-bottom: .6rem; }
.count { display: flex; flex-wrap: wrap; gap: clamp(.8rem, .4rem + 1.8vw, 2rem); }
.count-cell { min-width: 3.6rem; }
[data-skin="hanakenji"] .count-num {
  display: block; font: 400 var(--step-3)/1 var(--display);
  font-variant-numeric: tabular-nums; color: var(--ink);
}
.count-label { display: block; margin-top: .35rem; font: 600 var(--step--1)/1 var(--ui); letter-spacing: .16em; text-transform: uppercase; color: var(--faint); }

/* ---------- the ledger ----------
   Site 1 puts the three essentials in a three-up dl under the hero. This is a ruled ledger of
   rows instead, and the labels are this couple's own — the two sites shared all three of
   Wear / Reply by / Where, word for word, until today. The countdown is the last row. */
.ledger { display: grid; margin: 0; border-top: 2px solid var(--ink); }
.ledger > div {
  display: grid; grid-template-columns: minmax(0, 11rem) minmax(0, 1fr);
  column-gap: var(--gutter); row-gap: .2rem;
  padding-block: .95rem; border-bottom: var(--rule-w) solid var(--rule);
}
.ledger dt { grid-column: 1; font: 600 var(--step--1)/1.5 var(--ui); letter-spacing: .18em; text-transform: uppercase; color: var(--faint); }
[data-skin="hanakenji"] .ledger dd { grid-column: 2; margin: 0; font: 400 var(--step-2)/1.2 var(--display); }
.ledger-sub { grid-column: 2; font: var(--step--1)/1.55 var(--ui); color: var(--muted); }
@media (max-width: 47.99em) {
  .ledger > div { grid-template-columns: minmax(0, 1fr); }
  .ledger dt, .ledger dd, .ledger-sub { grid-column: 1; }
}
.tally { display: flex; flex-wrap: wrap; align-items: baseline; gap: .6rem var(--gutter); padding-block: .95rem; border-bottom: var(--rule-w) solid var(--rule); }
.tally-cap { flex: 0 0 auto; min-width: 11rem; font: 600 var(--step--1)/1.5 var(--ui); letter-spacing: .18em; text-transform: uppercase; color: var(--faint); }

/* Full-bleed photographs arrived with the cinematic pass (2026-09-06, move C) — but NOT as
   engine .a-bleed. The 2026-08-24 refusal still stands on its own terms: a white-type-on-
   gradient scrim is site 1's interlude verbatim. Site 2's bleed is §23.2's `.frame` — the
   photograph is the whole passage and the type sits on an OPAQUE PAPER SHEET flush into one
   corner of it, which is the same device the threshold hero uses. No scrim anywhere on this
   site, so no bleed on it has a contrast question. .plate figures survive unchanged. */

/* ---------- onward: where to go next ----------
   Two destinations, no 01–02–03 index rows and no image-right rhythm — both are site 1's. */
.onward { display: grid; gap: var(--gap); }
@media (min-width: 48em) { .onward { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.onward-card { display: grid; gap: .55rem; justify-items: start; }
.onward-card img { display: block; width: 100%; aspect-ratio: 5/4; object-fit: cover; border-radius: var(--radius); }
.onward-jp { font-family: var(--jp); font-size: var(--step--1); letter-spacing: .34em; color: var(--accent-ink); }

/* ---------- story ---------- */
.chapter {
  display: grid; gap: var(--gap);
  padding-block: clamp(2rem, 1.4rem + 2.6vw, 3.4rem);
  border-top: var(--rule-w) solid var(--rule);
}
.chapter:first-child { border-top: 0; padding-top: 0; }
@media (min-width: 56em) {
  .chapter { grid-template-columns: 6rem 1fr 1fr; align-items: start; }
  .chapter-shots { grid-column: 3; grid-row: 1 / span 3; }
}
[data-skin="hanakenji"] .chapter-no { font: 400 var(--step-3)/1 var(--display); color: var(--blossom); }
.chapter-head { display: grid; gap: .4rem; }
[data-skin="hanakenji"] .chapter-head h2 { font-size: var(--step-2); }
.chapter-date { font: 600 var(--step--1)/1 var(--ui); letter-spacing: .18em; text-transform: uppercase; color: var(--accent-ink); }
[data-skin="hanakenji"] .chapter-blurb {
  font-family: var(--display); font-size: var(--step-1); font-style: italic; color: var(--muted);
}
.chapter-shots { display: grid; gap: .8rem; }
.chapter-shots img { width: 100%; border-radius: var(--radius); display: block; }

/* ---------- the wedding party ---------- */
.party-grid { display: grid; gap: var(--gap); grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); }
.party-card {
  display: grid; gap: .3rem; justify-items: start;
  background: var(--card); border: var(--rule-w) solid var(--rule);
  border-radius: var(--radius); padding: 1.1rem;
}
.pav {
  display: grid; place-items: center;
  width: 2.9rem; height: 2.9rem; margin-bottom: .4rem;
  border-radius: 50%;
  background: var(--accent-soft); color: var(--accent-ink);
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .1em;
}
[data-skin="hanakenji"] .party-card b { font: 400 var(--step-1)/1.2 var(--display); }
.party-card .role { font: 600 var(--step--1)/1 var(--ui); letter-spacing: .14em; text-transform: uppercase; color: var(--accent-ink); }
.party-card p { color: var(--muted); font-size: var(--step--1); }

/* ---------- schedule ---------- */
.sched-group { padding-block: var(--gap); border-top: 2px solid var(--ink); }
.sched-group:first-child { border-top: 0; }
/* D4 (design read, site 2): 44em = 748px at 1440 and ran to 100ch on one line, against §17's
   --col: 62ch. The venue block sits outside the sheet's grid, so it never got the measure. */
.sched-venue { display: grid; gap: .4rem; margin-bottom: var(--gap); max-width: var(--col); }
[data-skin="hanakenji"] .sched-venue h2 { font-size: var(--step-2); }
.sched-venue p { color: var(--muted); }
.sched-list { display: grid; gap: 1.1rem; }
.sched-item {
  display: grid; gap: .3rem .9rem;
  grid-template-columns: 5.5rem 1fr;
  padding-top: .9rem; border-top: var(--rule-w) solid var(--rule);
}
.sched-time { font: 600 var(--step--1)/1.6 var(--ui); letter-spacing: .1em; color: var(--accent-ink); }
[data-skin="hanakenji"] .sched-title { grid-column: 2; font-family: var(--display); font-size: var(--step-1); }
.sched-note { grid-column: 2; color: var(--muted); font-size: var(--step--1); line-height: 1.6; }

/* ---------- the travel guide: site 2's hero feature ----------
   A ruled guide, not a card grid: four columns that stay legible as two rows on a phone. */
.guide { border-top: 2px solid var(--ink); }
.guide-row {
  display: grid; gap: .3rem 1.2rem;
  grid-template-columns: 1fr;
  padding-block: 1.1rem;
  border-bottom: var(--rule-w) solid var(--rule);
}
@media (min-width: 48em) {
  /* D1a (design read, site 2): was 1.1fr 1.5fr .7fr .8fr — the prose column ran 29–31ch while the
     two one-word label columns held 250px between them. 1fr 2fr .65fr .8fr → prose 32–39ch, five
     lines to four, the guide 112px shorter, and nothing in either label column wraps.
     ⚠ .guide-head below MUST carry the same ratio or the head stops aligning to the rows. */
  .guide-row { grid-template-columns: 1fr 2fr .65fr .8fr; align-items: baseline; }
}
[data-skin="hanakenji"] .guide-leg { font: 400 var(--step-1)/1.25 var(--display); }
.guide-how { color: var(--muted); }
.guide-len, .guide-by {
  font: 600 var(--step--1)/1.5 var(--ui); letter-spacing: .08em;
}
.guide-len { color: var(--ink); }
.guide-by { color: var(--accent-ink); }
.guide-head {
  display: none;
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .18em; text-transform: uppercase; color: var(--faint);
  padding-bottom: .6rem;
}
@media (min-width: 48em) {
  .guide-head { display: grid; grid-template-columns: 1fr 2fr .65fr .8fr; gap: 1.2rem; } /* D1a: matches .guide-row */
}
.guide-note { margin-top: 1rem; }
.guide-row .guide-len::before, .guide-row .guide-by::before {
  content: attr(data-label) " ";
  color: var(--faint); letter-spacing: .16em; text-transform: uppercase;
}
@media (min-width: 48em) {
  .guide-row .guide-len::before, .guide-row .guide-by::before { content: none; }
}

/* the deadline stack */
.dates { display: grid; gap: 1px; background: var(--rule); border: var(--rule-w) solid var(--rule); border-radius: var(--radius); overflow: hidden; }
.date-row { display: flex; flex-wrap: wrap; align-items: baseline; gap: .3rem 1rem; background: var(--card); padding: 1rem 1.1rem; }
[data-skin="hanakenji"] .date-when { font: 400 var(--step-1)/1 var(--display); color: var(--accent-ink); min-width: 8.5rem; }
.date-what { flex: 1 1 16rem; }
.date-what b { display: block; font-weight: 600; }
.date-what span { color: var(--muted); font-size: var(--step--1); }

/* words you will hear — the Japanese accent, glossed in English */
.words { display: grid; gap: 1px; background: var(--rule); border-radius: var(--radius); overflow: hidden; }
.word { display: grid; gap: .25rem; background: var(--card); padding: 1rem 1.1rem; }
.word-jp { font-family: var(--jp); font-size: var(--step-2); line-height: 1.2; color: var(--ink); }
.word-say { font: 600 var(--step--1)/1 var(--ui); letter-spacing: .14em; text-transform: uppercase; color: var(--accent-ink); }
.word p { color: var(--muted); font-size: var(--step--1); line-height: 1.6; }
@media (min-width: 48em) { .words { grid-template-columns: repeat(2, 1fr); } }

.stay { list-style: none; margin: 0; padding: 0; display: grid; gap: .8rem; }
.stay li { display: grid; gap: .15rem; padding-top: .8rem; border-top: var(--rule-w) solid var(--rule); }
[data-skin="hanakenji"] .stay b { font: 400 var(--step-1)/1.2 var(--display); }
.stay span { font: var(--step--1)/1.5 var(--ui); color: var(--faint); }
.invented {
  margin-top: 1rem; padding: .8rem 1rem;
  background: var(--accent-soft); border-radius: var(--radius);
  font: var(--step--1)/1.6 var(--ui); color: var(--ink);
}
.cols { display: grid; gap: var(--gap); }
@media (min-width: 48em) { .cols { grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); } }
.col { display: grid; gap: .6rem; align-content: start; }
.col h3 { font-size: var(--step-1); }
.col p { color: var(--muted); }
.list-checks { margin: 0; padding: 0; list-style: none; display: grid; gap: .55rem; }
.list-checks li { padding-left: 1.4rem; position: relative; color: var(--muted); }
.list-checks li::before {
  content: ""; position: absolute; left: 0; top: .55em;
  width: .55rem; height: .55rem; border-radius: 50%;
  background: var(--blossom);
}
.swatches { display: flex; flex-wrap: wrap; gap: .8rem; list-style: none; margin: 0; padding: 0; }
.swatch { display: grid; gap: .4rem; justify-items: center; }
.swatch i { display: block; width: 3rem; height: 3rem; border-radius: 50%; background: var(--sw, var(--blossom)); border: var(--rule-w) solid var(--rule); }
.swatch span { font: var(--step--1)/1 var(--ui); letter-spacing: .1em; color: var(--faint); }

/* ---------- gallery + lightbox ---------- */
/* A photo wall of mixed 2:3 and 3:2 frames. Two things it must not do, both learned the hard way
   on 2026-09-04 — see CHANGELOG.md:
     1. it must not cover-crop (that cut the raised hand, the whole subject, out of gallery-02);
     2. it must not reserve a track it cannot fill.
   A grid fails (2): rows size to their tallest item and row flow cannot backfill, so every
   landscape left a photo-sized void between the portraits. Plain `columns: 14rem` fails (2) as
   well, less obviously — multicol derives the column COUNT from the container width but then
   BALANCES content across every column it computed, so a fixed four-figure wall filled two of the
   three tracks it reserved and left 34% of the wall blank across 720–920px.
   So the count is stated explicitly rather than derived. ⚠️ These numbers are tied to the wall
   holding FOUR frames: four items balance cleanly into 1, 2 or 4 columns and never into 3.
   **If this wall ever gains a fifth photograph, re-derive them** — do not assume they still hold.
   Each image keeps its native shape from its own width/height attributes: no object-fit, no
   aspect-ratio, and no ratio keyed off an attribute value (an earlier fix matched
   `img[height="1600"]`, which would have silently cover-cropped any future third ratio). */
.gallery { column-count: 2; column-gap: .9rem; }
.gallery figure { margin: 0 0 .9rem; break-inside: avoid; }
.gallery img { display: block; width: 100%; height: auto; border-radius: var(--radius); cursor: zoom-in; }
@media (max-width: 34em) { .gallery { column-count: 1; } }
@media (min-width: 88em) { .gallery { column-count: 4; } }
.lb {
  position: fixed; inset: 0; z-index: 90;
  display: grid; place-items: center; gap: 1rem;
  background: rgba(43,39,51,.92);
  padding: clamp(1rem, 4vw, 3rem);
}
.lb[hidden] { display: none; }
.lb img { max-width: 100%; max-height: 76vh; border-radius: var(--radius); }
.lb-bar { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: .8rem; color: #fff; }
.lb-btn {
  min-height: 44px; padding: .5em 1.1em;
  background: transparent; color: #fff;
  border: var(--rule-w) solid rgba(255,255,255,.4); border-radius: 999px;
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .12em; text-transform: uppercase; cursor: pointer;
}
.lb-btn:hover { background: rgba(255,255,255,.14); }
.lb-caption { flex-basis: 100%; text-align: center; font-size: var(--step--1); color: rgba(255,255,255,.75); }

/* ---------- questions ---------- */
.qa { display: grid; gap: 0; border-top: 2px solid var(--ink); }
.qa details { border-bottom: var(--rule-w) solid var(--rule); }
.qa summary {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  min-height: 44px; padding: 1.05rem 0; cursor: pointer;
  font-family: var(--display); font-size: var(--step-1); line-height: 1.3;
  list-style: none;
}
.qa summary::-webkit-details-marker { display: none; }
.qa summary::after {
  content: "+"; font-family: var(--ui); font-size: var(--step-1); color: var(--accent-ink); flex: none;
}
.qa details[open] summary::after { content: "\2212"; }
.qa-body { padding-bottom: 1.1rem; color: var(--muted); max-width: 46em; }
.qa-body p + p { margin-top: .9em; }

/* ---------- guest ribbon ---------- */
.ribbon-slot { width: min(100% - 2.4rem, var(--page)); margin: 0 auto; }
.guest-ribbon {
  display: flex; flex-wrap: wrap; align-items: center; gap: .5rem 1rem;
  margin-top: 1rem;
  padding: .85rem 1.1rem;
  background: var(--accent-soft);
  border-radius: var(--radius);
}
.guest-ribbon[hidden] { display: none; }
[data-skin="hanakenji"] .guest-hello { font-family: var(--display); font-size: var(--step-1); }
.guest-facts { font: var(--step--1)/1.4 var(--ui); color: var(--muted); }
.guest-forget {
  margin-left: auto; min-height: 44px; padding: .4em 1em;
  background: transparent; border: var(--rule-w) solid var(--rule-strong); border-radius: 999px;
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink); cursor: pointer;
}
.guest-said { flex-basis: 100%; font-size: var(--step--1); color: var(--accent-ink); }

/* ---------- forms + RSVP ---------- */
.field { display: grid; gap: .4rem; }
.field > label, .label {
  font: 600 var(--step--1)/1.3 var(--ui); letter-spacing: .12em; text-transform: uppercase; color: var(--faint);
}
.field :is(input, select, textarea) {
  min-height: 46px; padding: .65em .8em;
  background: var(--field); color: var(--ink);
  border: var(--rule-w) solid var(--rule-strong); border-radius: var(--radius);
  font: var(--step-0)/1.4 var(--body);
}
.field textarea { min-height: 7rem; resize: vertical; }
.field--code input { font: 600 var(--step-1)/1.2 var(--ui); letter-spacing: .26em; text-transform: uppercase; }
.error {
  padding: .75em 1em; margin-bottom: 1rem;
  background: var(--accent-soft); border-left: 3px solid var(--accent);
  border-radius: 0 var(--radius) var(--radius) 0;
  font: var(--step--1)/1.55 var(--ui); color: var(--ink);
}
.error[hidden] { display: none; }
.actions { display: flex; flex-wrap: wrap; gap: .7rem; margin-top: var(--gap); }

/* ---------- reply ----------
   The short form, 2026-08-29: no invitation code, no party lookup, no per-person dinner.
   One name, one question, and an optional stepper for whoever is coming with them. Single
   column, because that is what a form this short wants: the yes is the loudest thing on the
   page and everything below it is optional. */
.reply { display: grid; gap: var(--gap); }
.reply-form { display: grid; gap: var(--gap); max-width: 46rem; }
.reply-form[hidden] { display: none; }
.confirm[hidden] { display: none; }
.reply-name { max-width: 24rem; }
.reply-ask {
  display: grid; gap: .9rem;
  padding: var(--gap);
  background: var(--bg-sink); border-radius: var(--radius);
}
[data-skin="hanakenji"] .reply-q { font-family: var(--display); font-size: var(--step-2); line-height: 1.15; }
.choices--big { flex-wrap: wrap; gap: .7rem; }
.choices--big .choice span {
  min-width: 11rem; min-height: 56px; padding: 0 1.4em;
  background: var(--card);
  font-size: var(--step-0); letter-spacing: .06em; text-transform: none;
}
.choices--big .choice--yes span { border-color: var(--accent); color: var(--accent-ink); }
.choices--big .choice input:checked + span { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }

.plus { display: grid; gap: .9rem; }
.plus[hidden] { display: none; }
.stepper { display: flex; flex-wrap: wrap; align-items: center; gap: .8rem; }
.step-btn {
  flex: none; width: 48px; height: 48px;
  background: var(--card); color: var(--ink);
  border: var(--rule-w) solid var(--rule-strong); border-radius: 999px;
  font: 400 var(--step-1)/1 var(--ui); cursor: pointer;
}
.step-btn:hover:not(:disabled) { border-color: var(--accent); background: var(--accent-soft); color: var(--accent-ink); }
.step-btn:disabled { opacity: .35; cursor: default; }
[data-skin="hanakenji"] .step-out {
  font: 400 var(--step-2)/1 var(--display);
  min-width: 1.4em; text-align: center; font-variant-numeric: tabular-nums;
}
.step-label { font: var(--step--1)/1.4 var(--ui); color: var(--muted); }
.plus-names { display: grid; gap: .8rem; }
.plus-names[hidden] { display: none; }
@media (min-width: 40em) { .plus-names { grid-template-columns: 1fr 1fr; } }
.reply-optional {
  display: grid; gap: var(--gap);
  padding-top: var(--gap); border-top: var(--rule-w) solid var(--rule);
}
@media (min-width: 52em) {
  .reply-optional { grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr); align-items: start; }
}

.choices { display: flex; gap: .5rem; }
.choice { position: relative; }
.choice input { position: absolute; opacity: 0; inset: 0; }
.choice span {
  display: grid; place-items: center;
  min-width: 5rem; min-height: 44px; padding: 0 1em;
  border: var(--rule-w) solid var(--rule-strong); border-radius: 999px;
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .12em; text-transform: uppercase;
  cursor: pointer;
}
.choice input:checked + span { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.choice input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 2px; }

.confirm { display: grid; gap: var(--gap); position: relative; }
.summary { display: grid; gap: .6rem; }
.summary-row {
  display: flex; flex-wrap: wrap; gap: .3rem 1rem; align-items: baseline;
  padding-top: .7rem; border-top: var(--rule-w) solid var(--rule);
}
[data-skin="hanakenji"] .summary-row b { font: 400 var(--step-1)/1.2 var(--display); flex: 1 1 9rem; }
.answer { font: 600 var(--step--1)/1 var(--ui); letter-spacing: .12em; text-transform: uppercase; color: var(--accent-ink); }
.detail { font-size: var(--step--1); color: var(--faint); }
.detail[hidden] { display: none; }
.stats { display: grid; gap: var(--gap); grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr)); padding-top: var(--gap); border-top: 2px solid var(--ink); }
.stat { display: grid; gap: .2rem; }
[data-skin="hanakenji"] .stat b { font: 400 var(--step-3)/1 var(--display); font-variant-numeric: tabular-nums; }
.stat span { font: 600 var(--step--1)/1.3 var(--ui); letter-spacing: .14em; text-transform: uppercase; color: var(--faint); }

/* the printable guest card came out with the RSVP simplification, 2026-08-28. Site 2's reply is
   the reply — no keepsake card, which is site 1's flourish. .gcard styles removed with it. */

/* ---------- footer ----------
   Paper, not an ink slab: the page ends on the same sheet it began on, with the date set
   vertically in the margin and the studio wordmark at the very foot. */
.foot { background: var(--bg-raised); color: var(--ink); border-top: 2px solid var(--ink); padding-block: clamp(2.2rem, 1.6rem + 2.6vw, 3.6rem); position: relative; z-index: 4; }
.foot-grid { display: grid; gap: var(--gap); }
@media (min-width: 60em) { .foot-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 15rem); align-items: start; } }
[data-skin="hanakenji"] .foot-names { font-size: var(--step-3); }
.foot-tag { font: 600 var(--step--1)/1 var(--ui); letter-spacing: .16em; color: var(--accent-ink); margin-top: .5rem; }
.foot-where { display: grid; gap: .3rem; }
.foot-where dt { font: 600 var(--step--1)/1.5 var(--ui); letter-spacing: .16em; text-transform: uppercase; color: var(--faint); }
[data-skin="hanakenji"] .foot-where dd { margin: 0; font-family: var(--display); font-size: var(--step-1); }
.foot-mark { margin-top: var(--gap); padding-top: .9rem; border-top: var(--rule-w) solid var(--rule); font: var(--step--1)/1.7 var(--ui); color: var(--muted); max-width: 62em; }
.foot-mark strong { color: var(--ink); }

/* The one element allowed to repeat verbatim across sites 2–8 — see DECISIONS.md → the
   differentiation charter, "where coherence comes from instead". Tiny, and never louder. */
.wsd-mark { display: flex; align-items: center; gap: .5rem; margin-top: 1.1rem; font: 600 var(--step--1)/1 var(--ui); letter-spacing: .26em; text-transform: uppercase; color: var(--faint); }
.wsd-mark i { display: block; width: .4rem; height: .4rem; background: var(--accent); transform: rotate(45deg); }

/* ---------- docks ----------
   Neither corner site 1 uses. The design-study marker lives at the FOOT OF THE RAIL, so the
   honesty label is part of the navigation rather than a floating tab; music sits top-right. */
/* 2026-09-06, move F: music moved OUT of the top-right corner. The threshold hero hangs the
   noren in the top right of the frame and a floating pill sat on the cloth. Site 2's chrome is
   now one left rail and one bottom-right stack — still neither of site 1's arrangements, which
   is two independent docks in the two bottom corners. */
.music-dock { position: fixed; right: 1rem; bottom: 1rem; z-index: 60; display: flex; align-items: center; justify-content: flex-end; gap: .5rem; }
.demo-dock { position: relative; display: grid; justify-items: center; gap: .5rem; }
.music-btn, .dock-tab, .demo-btn {
  min-height: 44px; padding: .55em 1.1em; white-space: nowrap;
  background: var(--ink); color: var(--paper);
  border: var(--rule-w) solid var(--ink); border-radius: 999px;
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .12em; text-transform: uppercase;
  cursor: pointer;
}
.music-btn:hover, .dock-tab:hover, .demo-btn:hover { background: var(--cherry-ink); border-color: var(--cherry-ink); }
.music-title { font: var(--step--1)/1 var(--ui); color: var(--faint); }
.music-wave { display: none; }
.dock-tab { display: flex; align-items: center; justify-content: center; gap: .5rem; width: 100%; }
.dock-diamond { flex: none; width: .55rem; height: .55rem; background: var(--blossom); transform: rotate(45deg); }
.dock-panel {
  position: absolute; bottom: calc(100% + .6rem); left: 0;
  width: min(23rem, calc(100vw - var(--rail-w) - 1.4rem));
  display: grid; gap: .7rem;
  padding: 1.1rem;
  background: var(--card); border: var(--rule-w) solid var(--rule-strong);
  border-radius: var(--radius); box-shadow: var(--a-lift);
}
.dock-panel[hidden] { display: none; }
.dock-note { font: var(--step--1)/1.6 var(--ui); color: var(--muted); }
.dock-phase { font: 600 var(--step--1)/1 var(--ui); letter-spacing: .12em; text-transform: uppercase; color: var(--accent-ink); }
.dock-ctl { display: grid; gap: .5rem; }
.demo-note { font-size: var(--step--1); color: var(--accent-ink); }
.demo-note[hidden] { display: none; }
@media (max-width: 63.99em) {
  .dock-tab { padding: .8em .45em; }
  .dock-label { writing-mode: vertical-rl; letter-spacing: .18em; font-size: .7rem; }
}
@media (max-width: 63.99em) {
  .music-title { display: none; }
}
@media (max-width: 39.99em) {
  .music-dock { display: none; }
}

/* ══════════════════════════════════════════════════════════════════════════════════════════
   §19 · THE ORNAMENT LAYER — the Sakura motifs, reissued (2026-08-28)

   Numbered 19 because 17 is the offset sheet and the rail in this file, and 18 is site 1's
   ornament pass in the ENGINE. This one is site 2's alone: the user chose skin-only, so
   nothing here is inheritable and sites 3–8 get none of it. If a later site wants a motif
   system it writes its own; that is the differentiation charter working as intended (ornament
   is a QUIET axis, so repetition would have been allowed — this is a choice, not a rule).

   Source: _reference/sakura/japan.css. NOT pasted — reissued. The reference was built for a
   sticky top bar and a long single-page scroll; site 2's chrome is the rail and its grid is the
   offset sheet, so every motif attaches somewhere different. Proved first in
   tools/Sakura Ornaments (Specimen).html, signed off at maximal + glass + motion 2026-08-28.

   ⚠ Deliberately NOT uniform across the four pages, and this is the record of it so a later
   pass does not "finish" the job (site 1's §18.6 lesson):
     · index  — noren (kamon + ようこそ) · lanterns over the schedule · sensu on What to wear
                · torii on the invitation plate
     · story  — noren (kamon) · torii on the closing plate · crane pair as the sign-off
     · travel — noren (kamon) · torii on the approach plate · THE FUJI SCENE, this page only
     · rsvp   — noren (kamon) and nothing else. The RSVP page is the standing exception
                (DECISIONS.md): it is the most functional surface on the site and ornament
                does not go on it.
   The kamon and the paper grid are the only two things on all four.

   ⚠ The ambient-surface budget is knowingly exceeded here. Home now runs the petal canvas, the
   paper ground, the noren, the lanterns and the grid wash. The budget says two. The user looked
   at five at maximal and chose it; logged in DECISIONS.md rather than left as a silent breach.
   ══════════════════════════════════════════════════════════════════════════════════════════ */

[data-skin="hanakenji"] {
  /* The glass material. Site 1's glaze retuned to cherry-on-paper, NOT inherited from it:
     atelier §V's .a-glaze is engine-side and this pass is skin-only.
     ⚠ No backdrop-filter, ever. Atelier §V refuses it outright — a blur is clipped to its own
     box and draws the exact rectangle a wash exists to avoid — and NEXT.md carries the refusal
     as a standing trap. Glass here is translucency + a specular sweep + a rim. */
  --sk-glass-spec: rgba(255, 255, 255, .72);
  --sk-glass-rim: rgba(255, 255, 255, .62);
  --sk-glass-sink: rgba(43, 39, 51, .26);
  --sk-glass-tint: color-mix(in oklab, var(--cherry) 62%, transparent);
  --sk-glass-tint-pale: color-mix(in oklab, var(--blossom) 52%, transparent);

  /* The noren cloth is INK, not cherry glass. Cherry glass composites over paper to
     rgb(217,146,160); white on that is 2.45:1 and fails at every position on the strip. On ink
     the glass reads as sheen on dyed cotton, which is what the reference had. */
  --sk-noren-cloth: color-mix(in oklab, var(--sumi) 86%, var(--cherry));

  /* the maximal paper tooth, at 4% ink */
  --sk-tooth: color-mix(in oklab, var(--sumi) 4%, transparent);
}

.glass {
  background-image:
    linear-gradient(158deg, var(--sk-glass-spec) 0, rgba(255,255,255,.26) 22%, rgba(255,255,255,0) 46%),
    radial-gradient(130% 90% at 12% 2%, var(--sk-glass-spec), transparent 60%);
  box-shadow:
    inset 0 1px 0 var(--sk-glass-rim),
    inset 0 0 0 1px rgba(255,255,255,.3),
    inset 0 -14px 24px -22px var(--sk-glass-sink),
    0 10px 30px -18px rgba(43, 39, 51, .5);
}

/* ── 19.1 · the paper grid ─────────────────────────────────────────────────────────────────
   Three repeating gradients at 0°, 60°, 120° — a woven-paper tooth. The reference's own
   maximal-mode body wash, and the one ornament that is on every page at once. */
[data-skin="hanakenji"] body {
  background-image:
    repeating-linear-gradient(0deg,   transparent 0 27px, var(--sk-tooth) 27px 28px),
    repeating-linear-gradient(60deg,  transparent 0 27px, var(--sk-tooth) 27px 28px),
    repeating-linear-gradient(120deg, transparent 0 27px, var(--sk-tooth) 27px 28px);
}

/* ── 19.2 · the noren ──────────────────────────────────────────────────────────────────────
   PHOTOGRAPHED CLOTH since 2026-09-06 (move B). The drawn version — five plum panels, a 10px
   bottom radius and a broad white specular across the top of each — was the worst-looking
   element on the site and the first thing seen on all four pages: at real scale it read as
   glossy glass buttons. The revert is `_archive/hanakenji-before-cinematic/`.

   ONE cloth, cut into five by CSS, so the weave runs continuously across the strips instead of
   being drawn five times. Each strip is a window onto the same file:
     background-size: 500% 100%  +  background-position-x: 0 / 25 / 50 / 75 / 100%
   is exact and gap-independent — at 5× the box width those five percentages land the five
   fifths precisely, whatever the gap is. `nth-of-type`, not `nth-child`: the visually-hidden
   greeting is a sibling of the strips and would shift every index by one.

   ⚠ TWO CLOTHS, AND THEY ARE NOT INTERCHANGEABLE.
   · `.noren--crest` (home only) carries the kamon and ようこそ printed on it, so it MUST be
     drawn at its own aspect (1600/500). Squash it and the 240px circular crest — which took
     six generation runs and a lab composite to get round — becomes an ellipse again. That is
     why home's cloth hangs inside the hero at `aspect-ratio` rather than at a fixed height
     (§23.3): the height follows the width, and the crest stays a circle at every viewport.
   · `.noren--plain` (story / travel / reply) has no mark, so it may be cropped instead of
     scaled: `background-size: auto 100%` matches the strip height and lets the width fall
     where it likes. Un-distorted weave at any chrome height, and nobody can tell which part
     of a plain cloth they are looking at.

   ⚠ The crest lands on strip 3 and ようこそ on strip 4, both clearing the cut lines at x=640
   and x=960 by ~40px. Never hide an outer strip to save width (the old mobile rule did): with
   five flex children removed to three, the survivors stretch their own fifth and the crest
   deforms. The whole cloth scales instead.

   ⚠ The greeting is out of the DOM as paint and back in it as a visually-hidden string on the
   container (`.noren-say`) — printing it into the photograph would otherwise have cost the
   site a word a screen reader and a future language toggle can reach. */
.noren--plain::before {
  content: ""; position: absolute; top: 0;
  left: clamp(.7rem, 2vw, 1.4rem); right: clamp(.7rem, 2vw, 1.4rem); height: 7px;
  border-radius: 4px;
  background: linear-gradient(180deg,
    color-mix(in oklab, var(--sumi) 42%, #a5794c),
    color-mix(in oklab, var(--sumi) 62%, #7d5834));
}
.noren i {
  flex: 1; position: relative;
  transform-origin: top center;
  background-repeat: no-repeat;
  filter: drop-shadow(0 7px 14px rgba(43, 39, 51, .42));
}
.noren-say {
  position: absolute; width: 1px; height: 1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
.noren--crest i { background-image: url("../assets/hanakenji/noren-crest.png"); background-size: 500% 100%; }
.noren--plain i { background-image: url("../assets/hanakenji/noren-plain.webp"); background-size: auto 100%; }
.noren i:nth-of-type(1) { background-position: 0% 0; }
.noren i:nth-of-type(2) { background-position: 25% 0; }
.noren i:nth-of-type(3) { background-position: 50% 0; }
.noren i:nth-of-type(4) { background-position: 75% 0; }
.noren i:nth-of-type(5) { background-position: 100% 0; }
@media (prefers-reduced-motion: no-preference) {
  [data-atelier~="motion"] .noren i { animation: noren-sway 6.4s ease-in-out infinite alternate; }
}
.noren i:nth-of-type(2) { animation-delay: -2.1s; }
.noren i:nth-of-type(3) { animation-delay: -3.7s; }
.noren i:nth-of-type(4) { animation-delay: -5.2s; }
.noren i:nth-of-type(5) { animation-delay: -3.0s; }
/* Cloth shears; it does not tilt. .9deg, not the drawn version's 1.4 — a photograph shows the
   skew honestly and the old amplitude visibly stretched the printed crest. */
@keyframes noren-sway { from { transform: skewX(-.8deg); } to { transform: skewX(.9deg); } }

/* ── 19.3 · the kamon ──────────────────────────────────────────────────────────────────────
   Static SVG: a ring and six circles. ⚠ NOT a <canvas>, and not the reference's five-variant
   generator. A canvas reads back BLANK in the probe harness (standing caution 5), so every
   gate screenshot of every page carrying one would show a hole. The tap-through is the price. */
.kamon { display: grid; place-items: center; width: 44px; height: 44px; }
.kamon svg { display: block; width: 100%; height: 100%; }
.kamon--foot {
  width: 58px; height: 58px; margin-bottom: .9rem;
  color: color-mix(in oklab, var(--cherry) 62%, var(--faint));
}
/* the crest's eye is a hole punched to whatever it sits on, not a paper-coloured dot */
.noren .kamon { width: 38px; height: 38px; color: #fff; --sk-kamon-eye: var(--sk-noren-cloth); }

/* ── 19.4 · the torii, framing a plate ─────────────────────────────────────────────────────
   PHOTOGRAPHED since 2026-09-06 (move E). The drawn gate was five tinted bars, and at real
   scale its uprights read as two pink vertical stripes across the invitation photograph.

   TWO FILES, and that is deliberate: a single frame-shaped PNG would stretch differently on
   every page and reproduce the exact fault that retired the drawn version.
     · `torii-crown.png`  1600 × 260 — the lintel plus upright stubs. Scales BY WIDTH only.
     · `torii-post.png`   530 × 300 — 48px of post flush left, the rest transparent pad.

   The arithmetic, so nobody re-derives it. The crown's uprights are 145px wide in a 1600
   frame; the post is 48px of a 530 frame. 145/1600 and 48/530 are both 9.06%, which is not a
   coincidence — the post was padded to 530 in the lab precisely so the tile draws 1:1 against
   the crown and the grain matches at the joint. With the crown at k × the plate's width:
     crown height      = k × 260/1600
     post box width    = k × 9.06%
     post tile width   = 530/48 = 1104% of that box
     upright centre    = −(k−1)/2 + 0.166k        (0% would be the plate's own edge)
     post's overhang   = 0.3793k − 0.5, as a fraction of the plate's width

   ⚠ k = 1.20, and it is DERIVED, not chosen. NEXT.md's recommendation was ~150%, which puts
   the upright centres exactly on the plate's edges — the widest read, and it is not available.
   What limits k is the LEFT EDGE OF THE PAGE, which on this site is the rail: `body` carries
   `padding-left: var(--rail-w)` and `overflow-x: clip`, so anything reaching past that either
   vanishes under the rail (z-index 55 against the gate's 1) or is cut by the body.

   And the plate is already at that edge. `.plate` breaks LEFT by `--break` out of the measure,
   and at **1024** `--break` is 41px while the sheet's own margin is 21px — so the invitation
   photograph itself overhangs the rail by 20px there. That is pre-existing (and previously
   invisible, being under the rail), but it means the post's overhang has to be NEGATIVE:
   0.3793k − 0.5 ≤ −0.026 at 1024 solves to k ≤ 1.25, and 1.20 leaves 14px of margin there
   and 33–103px at the other three widths. Measured, not estimated.
     crown 120%, left −10% · height 19.5% · post 10.87% wide at 4.48% · frame inset 11%
   The uprights land ~10% inside the picture and the lintel still overhangs it 10% each side,
   tucking behind the rail rather than being cut — a beam disappearing under the chrome reads
   as a gate wider than the page, which is the whole point of the overhang.

   ⚠ There is NO `overflow` clip anywhere in this gate, and there must not be. §23.4 briefly
   put `overflow-x: clip` on the band to contain a 150% crown; measured, it clipped 41px off
   the invitation PHOTOGRAPH at 1024 (the plate's own overhang, above) — and it was never
   needed, because `body`'s own clip already keeps the page free of horizontal overflow. The
   audit reports 0px at all four widths with no band clip at all. If a wider gate is ever
   wanted the lever is the plate's margin (`--break`, or the sheet's width), never a clip.

   ⚠ The frame's inset is 13% and NOT the crown's full 22.6%: the lintel and the nuki bar sit in
   the top ~45% of the crown file and the rest of it is upright stubs, so insetting by the whole
   height left ~110px of blank paper inside the gate. At 13% the stubs cross the picture's top
   corners, which is what a gate standing in front of something actually does. The posts still
   start at the crown's bottom — post and stub are the same post at the same width, so the
   overlap is invisible and the joint cannot gap.

   Everything is a percentage of the frame, so it holds at 390 and at 1440 alike.

   ⚠ Do NOT scale the post tile to "fit" a narrower box. `background-size: 1104% auto` is what
   keeps the post at the crown's upright width; a `cover` or a px width breaks the joint.

   ⚠ `.band--gate { overflow-x: clip }` (§23.4) is load-bearing. At 150% the lintel is wider
   than the sheet's margins and would push horizontal overflow onto the page — which the
   acceptance gate fails a frame for. Clipping at the BAND, not at `.torii-frame`: clip the
   frame and the cut lands on the plate's edges, exactly where the uprights are.

   ⚠ Still scoped to `.torii-frame`, which wraps the IMAGE ONLY, not the figure. Scoped to the
   figure, `inset: 0` ran the uprights down the whole figcaption and a bar sat on the caption.

   ⚠ `.torii` MUST keep its z-index. `[data-atelier~="toned"]` puts a filter on every plate
   image, a non-none filter creates a stacking context, and the image then paints as if
   `z-index: 0` — outranking an absolutely-positioned sibling at `auto` that precedes it. */
.plate--torii { padding-top: 0; }
/* ⚠ container-type is load-bearing, and it is the fix for the fault below. */
.torii-frame { position: relative; padding-top: 11%; container-type: inline-size; }
.torii { position: absolute; inset: 0; z-index: 1; pointer-events: none; }
/* ⚠ max-width: none is load-bearing — engine.css caps every img at 100% of its container, so
   without it the crown drew at the plate's own width and the uprights missed the posts. */
/* ⚠ Scoped through .plate--torii, and both of the last two declarations are load-bearing:
   §17's `.plate img` is (0,1,1) and beat a bare `.torii-crown` (0,1,0), which drew the crown at
   the plate's own width — uprights at 16.6%/83.4% of the picture instead of on its edges, with
   the posts still where 150% puts them. engine.css also caps every img at 100%. */
.plate--torii .torii-crown {
  position: absolute; top: 0; left: -10%;
  width: 120%; max-width: none; height: auto;
  display: block; border-radius: 0;
}
/* ⚠ THE POST'S TOP IS A PERCENTAGE OF THE FRAME'S WIDTH, NOT ITS HEIGHT — `top: N%` resolves
   against the containing block's HEIGHT, and the crown is scaled by WIDTH. It shipped as
   `top: 19.5%` on 2026-09-06 (morning) and that is what the user saw: at 1440 the crown's
   bottom was 197.8px down and 19.5% of the frame's height was 170.1px, so the post began
   27.7px ABOVE the crown and the two paints stacked.
   `cqw` is a percentage of the container's inline size, so it is the same number at every
   width and at every plate ratio. The 1px is an overlap against a subpixel hairline.

   The number: the crown is 1600 × 700 drawn at `width: 120%`, so its height is
   1.20 × 700/1600 = **52.5%** of the frame's width.
   ⚠ This is why the crown is 700 tall and not 900. At 900 the crown resolves to 67.5cqw, which
   exceeds the TOTAL height of a 16:9 plate (11cqw of padding + 56.25cqw of picture = 67.25cqw)
   — the post would have negative height and the crown would overhang the picture. Story's and
   Travel's plates are not 4:3. 700 leaves clearance at every ratio the site uses: on home's
   4:3 plate the frame is 86cqw, so the post covers the last 33.5cqw.

   ⚠ Drawn ONCE, not tiled. `background-size: X% auto` gave the strip a height of 6.25× its own
   width, which overran the post at some widths and repeated. `100%` fits it to the post exactly;
   the plate's ratio is fixed, so the vertical stretch is under 3% at all four widths and the
   grain does not read as distorted.

   ⚠ The post carries the plate tone too. `--a-tone` is on every `.plate img`, so the crown had
   it and the post did not — a sepia/saturate difference across the one joint on the page that
   has to be invisible.

   ── The numbers, all measured off the delivered crown, so nobody re-derives them ──
   The sumi crown's uprights are 184px wide at the bottom row, the left at x=165–348 and the
   right at x=1252–1436 of 1600. At `width: 120%` with `left: -10%`:
     width  = 184/1600 × 1.20 = 13.80%
     left   = (165/1600 × 1.20) − 10% = 2.38%   ← inner face lands at 16.18%, crown's is 16.10%
     right  = 100% − (1436/1600 × 1.20 − 10%) = 2.30%
   ⚠ These three move together — change the crown and all three are re-derived, or the gate
   steps in or out halfway down the picture.

   ⚠ `torii-post.png` IS CUT FROM THE CROWN, in the lab — 184 × 90, the column at its own
   width with no padding, which is why `background-size` is a plain `100% 100%` and there is no
   percentage to keep in step. Its top row is the crown's last row **pixel for pixel** (Δrgb 0,
   Δalpha 0, measured), and its rows read back up the crown's own last 90 as they go down.
   Three attempts to match the DELIVERED post photograph to this crown failed on two things no
   grade can correct: the sumi crown's column has soft antialiased edges — its visible paint is
   ~115px inside a 184px alpha envelope — where the delivered post's are crisp, and its grain is
   coarse where the post's is fine. Cutting the post out of the crown makes edge softness, grain
   scale, tone, roundness and the alpha profile identical by construction.
   ⚠ So a NEW CROWN MEANS A NEW POST: re-cut it, do not re-grade the old one. The recipe is in
   `CHANGELOG.md` → 2026-09-06 (night).
   ⚠ Rows are reversed on purpose, so the top row is the joint. The grain's direction reverses
   there and nothing else does, which is invisible on vertical grain — an earlier mirror placed
   mid-post reversed a knot instead and creased.
   ⚠ The crown's inward lean is NOT continued: the post is resampled to a constant width, so the
   silhouette's slope changes by about .05px/row at the joint. Judged against the 27.7px
   double-draw it replaced. */
.torii-post {
  position: absolute; top: calc(52.5cqw - 1px); bottom: 0; width: 13.80%;
  background-image: url("../assets/hanakenji/torii-post.png");
  background-size: 100% 100%;
  background-position: 0 0;
  background-repeat: no-repeat;
  filter: var(--a-tone, none);
}
.torii-post--l { left: 2.38%; }
.torii-post--r { right: 2.30%; transform: scaleX(-1); }

/* ── 19.5 · the lanterns ───────────────────────────────────────────────────────────────────
   PHOTOGRAPHED since 2026-09-06 (move E). The drawn trio was an elliptical pill with a
   specular and a kanji on it, and at real scale it read as emoji.

   Three cut-outs, not three copies of one: `cut-lantern-lit`, `cut-lantern-pair`,
   `cut-lantern-unlit`. The pair arrives with its own cords in frame, so it is the one that
   does NOT get a drawn cord (`--bare`) — and it hangs from the shortest drop, because its
   cords already occupy the headroom the other two need drawn.

   ⚠ The 92px painted floor is the binding constraint, not the box: the lantern occupies about
   85% of its 400 × 400 frame, so a 112px box paints ~95px of lantern. Below that a photographed
   ornament reads as a speck — site 1 paid for that lesson.

   The kanji 祝 / 寿 / 縁 left with the drawn version. Type set over a photographed lantern
   reads as a sticker; the eyebrow seals carry the kanji on this page instead. */
.lanterns {
  display: flex; flex-wrap: wrap; justify-content: center; align-items: flex-start;
  gap: clamp(1.1rem, 5vw, 3.4rem);
  padding-top: 10px; padding-bottom: 18px;
}
.lantern-hang {
  position: relative; display: block;
  padding-top: var(--sk-lantern-drop, 34px);
  transform-origin: 50% 0;
}
.lantern-hang::before {
  content: ""; position: absolute; top: 0; left: 50%; width: 1px;
  height: var(--sk-lantern-drop, 34px);
  background: color-mix(in oklab, var(--sumi) 54%, var(--bg));
}
.lantern-hang--bare::before { display: none; }
.lantern-cut { display: block; width: clamp(112px, 12vw, 168px); height: auto; }
@media (prefers-reduced-motion: no-preference) {
  [data-atelier~="motion"] .lantern-hang { animation: lantern-sway 5.4s ease-in-out infinite alternate; }
}
.lantern-hang:nth-of-type(2) { animation-delay: -2.7s; }
.lantern-hang:nth-of-type(3) { animation-delay: -1.4s; }
@keyframes lantern-sway { from { transform: rotate(-2.2deg); } to { transform: rotate(2.2deg); } }
/* ⚠ TWO lanterns below 40em, and this is arithmetic rather than taste: the measure is 287px at
   390, and three pieces at the 92px painted floor (a 108px box) need 324px before any gap.
   Three would either overflow the page or break the floor. The pair is kept — it carries two
   lanterns in one box, so the phone loses one lantern, not the group. */
@media (max-width: 39.99em) {
  .lantern-hang:nth-of-type(3) { display: none; }
  .lantern-cut { width: clamp(108px, 32vw, 132px); }
}

/* ── 19.5b · the stone lanterns ─────────────────────────────────────────────────────────────
   TRAVEL PAGE ONLY, and a different object from §19.5's hanging paper lanterns: ishidoro, the
   stone garden lantern. Two files from pass 2 — `cut-lantern-stone.webp` (800 × 1120, one) and
   `cut-lantern-stone-pair.webp` (1200 × 900, two together).
   ⚠ **§19.5's 112px floor binds here too**, and harder: a stone lantern's read is its layered
   silhouette — cap, firebox, pedestal — and below ~112px those three layers merge into a grey
   post. The pair carries two of them in one frame, so it gets the wider box.
   ⚠ **These do not sway.** §19.5's `lantern-sway` is on hanging paper; stone does not move, and
   animating it would be the kind of detail that reads as a mistake to anyone who has seen one. */
.stone-lanterns { display: flex; align-items: flex-end; justify-content: center; gap: clamp(1.4rem, 4vw, 2.8rem); margin: 0 0 1.6rem; }
.stone-lantern { display: block; width: clamp(112px, 13vw, 158px); height: auto; }
.stone-lantern--pair { width: clamp(152px, 18vw, 224px); }
/* on a paper band the stone's own grey is not separation enough — the same cast-shadow argument
   as §19.7's crane, and for the same reason it sits on the WRAPPER and not on the img */
.band:not(.band--bleed) .stone-lanterns { filter: drop-shadow(0 4px 7px rgba(24, 20, 28, .22)); }
/* ⚠ BELOW 40em THE SINGLE GOES, NOT THE PAIR, and it is §19.5's arithmetic rather than taste:
   the rule there is two lanterns below 40em and never three, and `cut-lantern-stone-pair.webp`
   carries TWO in one frame — so the single plus the pair is three on a phone. Dropping the
   single leaves exactly two, and keeps the wider file, which holds the 112px floor more easily
   than two boxes side by side in a 390px measure. */
@media (max-width: 39.99em) {
  .stone-lantern:not(.stone-lantern--pair) { display: none; }
  .stone-lantern--pair { width: clamp(148px, 44vw, 190px); }
}

/* ── 19.6 · the Fuji scene ─────────────────────────────────────────────────────────────────
   TRAVEL PAGE ONLY, deliberately. It is an ambient surface and the footer already carries one,
   so it earns its place on the page about Japan and nowhere else.

   PHOTOGRAPHED since 2026-09-16 (pass 2). Five `clip-path` polygons — a triangle for the
   mountain, a nine-point zigzag for the snow cap, and two pill-plus-box-shadow clouds — were the
   last drawn scene on the site. The zigzag cap was the tell: a snowline is a soft irregular edge
   and a polygon cannot be one, so at any size above thumbnail it read as a logo.
     · `fuji-far.webp`  2000 × 750, the mountain WITH its snow, cut off flat at the bottom row
     · `cloud-a.webp`   837 × 291, the left bank      · `cloud-b.webp`  852 × 279, the right

   ⚠ **The cap is gone as an element, not moved.** The snow is in the mountain's own file, which
   is the only way it lines up — a separately positioned cap has to be re-registered at every
   width, and §19.6's old `bottom: 80px` was exactly that maintenance. 🚫 Do not re-add `.fuji-cap`.
   ⚠ **`.fuji-sun` stays DRAWN.** A flat disc of colour is a flat disc of colour; there is no
   photograph to be had of one and nothing was wrong with it. Keeping it also keeps one palette
   lever in the scene, since the two photographs now carry their own colour.
   ⚠ **The two clouds were ONE delivered file** (`fuji-clouds.webp`, 2000 × 580, two banks either
   side of a transparent gutter) and were split in the lab on import. The gutter measured **197px,
   not the briefed 300**, so the cut was placed at its MEASURED midpoint (x = 993) rather than at
   the frame's centre — off-by-a-guess there would have clipped a wisp off one bank.
   ⚠ The mountain is **anchored bottom-centre and sized by width**, like §19.4's crown, because
   its own frame carries the horizon: `height: auto` and let the file's aspect do the work. A
   fixed height on a photographed mountain squashes the snowline. */
.fuji-scene { position: relative; height: 186px; margin-bottom: -10px; overflow: hidden; pointer-events: none; }
.fuji-scene i { position: absolute; }
.fuji-scene img { position: absolute; display: block; height: auto; }
.fuji {
  bottom: 0; left: 50%; transform: translateX(-50%);
  width: min(412px, 86vw);
}
/* ⚠ The sun clears the mountain's SHOULDER, not just its apex. At the drawn version's
   `bottom: 78px` the disc landed on the photograph's snowline and read as a sticker stuck to the
   slope — the drawn triangle had no snowline to collide with. It sits in open sky now. */
.fuji-sun {
  bottom: 126px; left: min(calc(50% + 158px), calc(100% - 3.5rem));
  width: 40px; height: 40px; border-radius: 50%;
  background-color: color-mix(in oklab, var(--blossom) 88%, transparent);
}
/* ⚠ The two clouds are WARMER than the mountain — the delivered files are lit late-afternoon and
   the mountain reads cool at distance. They are held at .52 and kept small and high so they sit
   as haze rather than as two beige objects; a `filter: hue-rotate` to match them would be fighting
   a photograph with a correction, which is what §19.7 says not to do. */
.cloud { opacity: .52; }
.cloud.c1 { width: min(128px, 30vw); left: 4%; bottom: 88px; }
.cloud.c2 { width: min(98px, 24vw); right: 6%; bottom: 46px; }
@media (max-width: 39.99em) {
  .fuji-scene { height: 146px; }
  .fuji { width: min(300px, 90vw); }
  .fuji-sun { bottom: 96px; width: 32px; height: 32px; }
  .cloud.c1 { bottom: 66px; }
  .cloud.c2 { bottom: 34px; }
}

/* ── 19.7 · the crane pair ─────────────────────────────────────────────────────────────────
   STORY's sign-off, and Reply's since §34.10. PHOTOGRAPHED since 2026-09-08 (evening):
   `cut-crane-paper.webp`, one folded crane in cream washi with a gold branch print, three-quarters
   on. The second bird is the same file mirrored — `.flip-x`, which the drawn pair already used.
   The five polygons it replaces came across from the reference verbatim and were the last drawn
   ornament left on the site.
   ⚠ 112px, not 52. §19's floor is ~92px of PAINTED subject and a cut-out fills about 85% of its
   frame, so 112 is the smallest box a photographed crane may have. The size change is part of
   the swap, not a separate opinion — at 52px this file is a beige smudge. */
.crane-pair { display: flex; justify-content: center; gap: 26px; margin-top: 1.6rem; }
.crane-pair img { display: block; width: 112px; height: auto; }
.crane-pair .flip-x { transform: scaleX(-1); }
/* On a bleed the crane sits on dusk water and its own cream is the separation. On a PAPER band
   cream-on-ivory is not, and `color` is no longer a lever — so the separation is a cast shadow,
   which is what a paper object on paper actually has. Not glass; §19.7's old note stands.
   ⚠ THE SHADOW IS ON THE WRAPPER, NOT ON THE IMG. `atelier.css` puts `filter: var(--a-tone)` on
   EVERY img through `[data-atelier~="toned"] :is(img, video)`, and `filter` is a single property —
   a `drop-shadow()` on the img at higher specificity REPLACES the site's tone instead of joining
   it, so the crane would be the one untoned photograph on the page. On the flex wrapper the
   shadow works off the subtree's rendered alpha, so it follows both birds and the `.flip-x`
   mirror with them. §35.7's blossom is written the same way for the same reason. */
.band:not(.band--bleed) .crane-pair { filter: drop-shadow(0 3px 5px rgba(24, 20, 28, .20)); }

/* ── 19.8 · the sensu ──────────────────────────────────────────────────────────────────────
   HOME PAGE ONLY, heading What to wear — the one ornament that carries information.
   PHOTOGRAPHED since 2026-09-06 (move E): `cut-sensu`, the open fan. The drawn version was a
   repeating conic gradient, which is a fan diagram rather than a fan.
   ⚠ It now sits on the ink passage (§23.1), where its indigo leaf is quiet and its bamboo ribs
   carry the read. That is the accepted trade for putting the five colours on deep sumi; if the
   fan disappears at a real viewing distance the answer is a lit register, not a bigger fan. */
.sensu { display: block; width: clamp(104px, 11vw, 140px); height: auto; margin-bottom: .8rem; }
/* the crest as paint, not as geometry — the footer mark, printed on handmade paper.
   ⚠ PASS 2 (2026-09-16): now `cut-kamon-washi.webp`, a letterpress impression on washi with
   visible ink bite and two skipped passages. Its SIX enclosed circles were verified by
   flood-filling the file's alpha, not by eye — and that count is load-bearing, because §19.3's
   noren crest STAYS SVG (it needs its eye punched through to the cloth colour, which a
   photograph cannot do). Six at the footer, six on the curtain. 🚫 A re-roll that changes the
   count breaks the pairing silently. */
.kamon-mark { display: block; width: 96px; height: auto; margin-bottom: .9rem; }

════
   §20 · OMIKUJI — the fortune box (2026-08-28)   Skin dress for engine/omikuji.js. The module is engine-side and written once; everything
   here is site 2's paper. It is a FEATURE, not ornament — it is not gated with §19 and it does
   not count against the ambient budget, because it does nothing until it is asked to.
   ══════════════════════════════════════════════════════════════════════════════════════════ */
.omikuji { display: grid; justify-items: start; gap: 1.1rem; }
/* ── 20.1 · the box, the slip and the knot are PHOTOGRAPHS (2026-09-16, pass 2) ─────────────
   The drawn fortune was a gradient-filled rounded rect with a kanji set in it and a sumi tab on
   top, and the slip was a bordered card with a 3px accent edge. Both were CSS diagrams of
   objects. Three files replace them:
     · `cut-omikuji-box.webp`   800 × 960 — the wooden box, tab and all
     · `cut-omikuji-slip.webp`  560 × 1280 — ✅ delivered GENUINELY BLANK, which was the one
       hard requirement: the reading is live type set over it by `omikuji.js`, not baked in
     · `cut-omikuji-tied.webp`  1000 × 750 — the drawn fortune tied off, for the retie state

   ⚠ **The box is 96px wide, not 74.** §19's floor is ~92px of PAINTED subject, and after the
   crop below the element's width IS the subject's width, so 96 clears it honestly.
   ⚠ **BOTH FILES WERE CROPPED TO THEIR CONTENT on 2026-09-16 (later), and that is why there is no
   `--sk-omi-box-w` knob here.** As delivered, the box's subject was **41% of its frame's width**
   and the slip's **48%** — both reframed to a 6% margin on import, which a portrait subject in a
   portrait frame of a different aspect satisfies on HEIGHT while leaving a quarter of the width
   empty each side. The CSS sizes the frame, so the box drew at 40% of its declared width and the
   slip's paper covered less than half the element it was the background of. Cropping to the
   content box fixes it at the source and **upscales nothing** — it only removes transparency —
   which is why it beats the width bump this section used to call for.
     box   800 × 960  → 350 × 872   (aspect 0.4014)
     slip  560 × 1280 → 300 × 1160, then ROTATED to 1160 × 300  (aspect 3.867)
   ⚠ **The slip file is rotated 90° in the tree, deliberately.** A paper strip is a paper strip,
   and the slip ELEMENT is landscape (≈3.7:1) while the file was shot portrait (0.26:1) —
   `100% 100%` was stretching the aspect **14-fold**, which turned the side deckle into a vertical
   fringe. Rotated, the file's aspect is 3.87 against the element's 3.7 and the torn edge keeps
   its own scale. 🚫 Do not rotate it back to match the delivery; the delivery's orientation was a
   framing choice, not a fact about the object.
   ⚠ 1.5% bleed was left round each crop so an antialiased deckle edge is not sliced by the crop.
   ⚠ **The aspect-ratios below are those files' own** — re-measure both if either is ever re-rolled.
   ⚠ **The shake still animates the BOX ELEMENT**, so `transform-origin: 50% 12%` is unchanged —
   the photograph's own tab sits at about that height, which is why the old value survives the
   swap. Check this if the file is ever re-rolled with a different tab.
   ⚠ The slip keeps its grid and its padding: the type is set ON the photograph, and `.slip-rank`
   is still vertical live text. The paper's border, accent edge and lift all come off, because
   the file has them. */
.omi-box {
  position: relative;
  display: grid; place-items: center;
  width: 96px;
  aspect-ratio: 350 / 872;
  background: url("../assets/hanakenji/cut/cut-omikuji-box.webp") center / contain no-repeat;
  transform-origin: 50% 12%;
}
@media (prefers-reduced-motion: no-preference) {
  .omi-box[data-shaking] { animation: omi-shake 620ms cubic-bezier(.36, .07, .19, .97); }
}
@keyframes omi-shake {
  10%, 90% { transform: rotate(-4deg); }
  30%, 70% { transform: rotate(5deg); }
  50%      { transform: rotate(-3deg); }
}
/* the tied-off state: the same slot, a different photograph */
.omi-box[data-tied] {
  aspect-ratio: 1000 / 750;
  width: 148px;
  background-image: url("../assets/hanakenji/cut/cut-omikuji-tied.webp");
}
.omi-act { display: flex; flex-wrap: wrap; gap: .8rem; align-items: center; }
.omi-retie {
  min-height: 44px; padding: .5em 1.1em;
  background: transparent; border: var(--rule-w) dashed var(--rule-strong); border-radius: 999px;
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .12em; text-transform: uppercase;
  color: var(--muted); cursor: pointer;
}
.omi-retie:hover { border-color: var(--accent); color: var(--accent-ink); }
.omi-retie[hidden], .omi-slip[hidden] { display: none; }
.omi-said { font: var(--step--1)/1.5 var(--ui); color: var(--faint); }

/* the slip: a narrow paper strip photographed blank, with the rank set vertically beside the
   reading. ⚠ `background-size: 100% 100%` and NOT `contain`: the type sets the box's height, and
   the paper has to follow the type rather than the type being trapped in the file's aspect. A
   blank strip of washi stretches without a tell; a printed one would not, which is why blank
   was the requirement. */
.slip {
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  gap: 1.2rem; align-items: start;
  margin: 0; padding: 1.5rem 2.1rem;
  max-width: 34rem;
  background: url("../assets/hanakenji/cut/cut-omikuji-slip.webp") center / 100% 100% no-repeat;
  border-radius: 3px;
  filter: drop-shadow(0 3px 6px rgba(24, 20, 28, .16));
}
.slip-rank {
  writing-mode: vertical-rl; text-orientation: upright;
  margin: 0; font-size: var(--step-2); letter-spacing: .18em; color: var(--accent-ink);
}
.slip-say { display: grid; gap: .45rem; }
.slip-rank-en {
  margin: 0; font: 600 var(--step--1)/1 var(--ui);
  letter-spacing: .16em; text-transform: uppercase; color: var(--faint);
}
[data-skin="hanakenji"] .slip-text { font-family: var(--display); font-size: var(--step-1); line-height: 1.5; }

/* ── 20.2 · the koi pond ─────────────────────────────────────────────────────────────────
   Skin dress for engine/pond.js, TRAVEL PAGE ONLY. ⚠ It is a <canvas> and reads back blank at
   the gate — accepted knowingly for a feature, and refused for the kamon, which is ornament. */
.pond {
  display: block; width: 100%;
  height: clamp(180px, 30vw, 250px);
  border-radius: var(--radius);
  cursor: pointer;
  box-shadow: inset 0 4px 18px rgba(0, 0, 0, .42), var(--a-shadow);
  --pond-water-a: color-mix(in oklab, var(--sumi) 58%, #2e5a63);
  --pond-water-b: color-mix(in oklab, var(--sumi) 88%, #10262e);
  --pond-koi-a: var(--cherry);
  --pond-koi-b: #e0a05a;
  --pond-koi-pale: var(--paper);
  /* ⚠ 58, up from the drawn version's 34 (2026-09-16). This is the OPPOSITE call to §37.1's on
     `--petal-size`, and the difference is what the subject is FOR. A petal is ambient — it may
     stay small. A koi is the thing the visitor came to tap, and a photographed fish reads much
     smaller than the solid saturated ellipse it replaces: fine fins go translucent at the edge,
     so the eye measures the opaque body, not the sprite box. At 34 the new koi are specks in a
     250px pool. The fish fills ~90% of its 700 × 1100 frame, so 58 puts it at ~53px nose to tail. */
  --pond-koi-size: 58;
}
.pond:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* ══════════════════════════════════════════════════════════════════════════════════════════
   §21 · THE WALL, AS AN EMA RACK — rebuilt 2026-10-13 (WALL-PLAN.md)
   Site 2's dress for the Live Guest Wall's two parts: the guest form (.wf, on a phone) writes a
   plaque, and the reception screen (.ws) hangs them under a row of lanterns on an indigo night.
   There is no ema module and there must never be one: an ema wall and a guest wall are ONE module
   in two costumes (DECISIONS.md). Everything here is CSS over wall.js's hooks.

   The plaque is wood, not paper and not cherry: an ema is the one object on this site that is
   neither, so it keeps its own two tokens. The screen is a size container (feature.css): every
   measure there is in cqmin, so one set of rules serves any screen shape.
   ══════════════════════════════════════════════════════════════════════════════════════════ */
[data-skin="hanakenji"] {
  --sk-ema-wood: color-mix(in oklab, var(--paper) 72%, #b6864f);
  --sk-ema-edge: color-mix(in oklab, var(--sumi) 34%, #b6864f);
}

/* the travel page's extent filters began here as the wall's; they stay, the wall's feed does not */
.filters { display: flex; flex-wrap: wrap; gap: .5rem; margin-left: auto; }
.filter {
  min-height: 44px; padding: .5em 1.1em;
  background: transparent; border: var(--rule-w) solid var(--rule-strong); border-radius: 999px;
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .12em; text-transform: uppercase;
  color: var(--muted); cursor: pointer;
}
.filter:hover { border-color: var(--accent); color: var(--accent-ink); }
.filter[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }

/* ---------- the guest form ---------- */
[data-skin="hanakenji"] .wf { display: grid; gap: 1.4rem; padding: 0 1.25rem 2.4rem; }
[data-skin="hanakenji"] .wf [hidden] { display: none; }
[data-skin="hanakenji"] .wf-study {
  margin: 0 -1.25rem; padding: .7rem 1.25rem;
  background: var(--sumi); color: var(--paper);
  font: 600 .72rem/1.3 var(--ui); letter-spacing: .12em; text-transform: uppercase; text-align: center;
}
[data-skin="hanakenji"] .wf-head { display: grid; gap: .35rem; justify-items: center; padding-top: .6rem; text-align: center; }
[data-skin="hanakenji"] .wf-kicker { margin: 0; font: 600 var(--step--1)/1 var(--ui); letter-spacing: .2em; text-transform: uppercase; color: var(--faint); }
[data-skin="hanakenji"] .wf-names { margin: 0; font: 400 2.3rem/1.05 var(--display); color: var(--ink); }
[data-skin="hanakenji"] .wf-names i { font-style: normal; color: var(--accent); }
/* the form is a plaque: wood, a gabled top, a red cord */
[data-skin="hanakenji"] .wf-form,
[data-skin="hanakenji"] .wf-sent,
[data-skin="hanakenji"] .wf-closed {
  position: relative; display: grid; gap: 1.1rem;
  padding: 3.4rem 1.2rem 1.5rem;
  background-color: var(--sk-ema-wood);
  background-image: repeating-linear-gradient(96deg,
    transparent 0 6px, color-mix(in oklab, var(--sk-ema-edge) 14%, transparent) 6px 7px);
  clip-path: polygon(50% 0, 100% 2.6rem, 100% 100%, 0 100%, 0 2.6rem);
}
[data-skin="hanakenji"] .wf-form::before,
[data-skin="hanakenji"] .wf-sent::before,
[data-skin="hanakenji"] .wf-closed::before {
  content: ""; position: absolute; top: 1.6rem; left: 50%; translate: -50% 0;
  width: 30px; height: 5px; border-radius: 3px;
  background: color-mix(in oklab, var(--cherry) 76%, var(--sumi));
}
[data-skin="hanakenji"] .wf .field :is(input, textarea) { background: color-mix(in oklab, var(--card) 88%, transparent); }
[data-skin="hanakenji"] .wf-title { margin: 0; font: 400 1.75rem/1.15 var(--display); color: var(--ink); text-wrap: balance; }
[data-skin="hanakenji"] .wf-lede { margin: 0; font: var(--step-0)/1.55 var(--body); color: var(--muted); text-wrap: pretty; }
[data-skin="hanakenji"] .wf-opt { font-weight: 400; letter-spacing: .04em; text-transform: none; }
[data-skin="hanakenji"] .wf-left { margin: 0; justify-self: end; font: var(--step--1)/1 var(--ui); color: var(--muted); font-variant-numeric: tabular-nums; }
[data-skin="hanakenji"] .wf-left[data-near] { color: var(--accent-ink); }
[data-skin="hanakenji"] .wf-pick {
  min-height: 3.4rem; width: 100%; border-radius: 4px;
  background: transparent; border: 1px dashed var(--sk-ema-edge); color: var(--accent-ink);
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .14em; text-transform: uppercase; cursor: pointer;
}
[data-skin="hanakenji"] .wf-pick:hover { background: color-mix(in oklab, var(--paper) 40%, transparent); border-color: var(--accent); }
[data-skin="hanakenji"] .wf-preview { display: grid; gap: .5rem; }
[data-skin="hanakenji"] .wf-preview img { display: block; width: 100%; max-height: 18rem; object-fit: cover; border-radius: 3px; }
[data-skin="hanakenji"] .wf-preview-ctl { display: flex; gap: 1.2rem; }
[data-skin="hanakenji"] .wf-link {
  min-height: 44px; padding: 0; background: none; border: 0; cursor: pointer;
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .12em; text-transform: uppercase; color: var(--accent-ink);
  text-decoration: underline; text-underline-offset: .3em;
}
[data-skin="hanakenji"] .wf-sheet { display: grid; gap: .6rem; padding: .9rem; background: var(--card); border-radius: 4px; }
[data-skin="hanakenji"] .wf-sheet-h { margin: 0; font: 600 var(--step--1)/1 var(--ui); letter-spacing: .14em; text-transform: uppercase; color: var(--muted); }
[data-skin="hanakenji"] .wf-roll { display: grid; grid-template-columns: repeat(2, 1fr); gap: .45rem; }
[data-skin="hanakenji"] .wf-shot { aspect-ratio: 4 / 3; padding: 0; background: var(--rule); border: 2px solid transparent; border-radius: 4px; cursor: pointer; line-height: 0; overflow: hidden; }
[data-skin="hanakenji"] .wf-shot img { width: 100%; height: 100%; object-fit: cover; }
[data-skin="hanakenji"] .wf-shot:hover { border-color: var(--accent); }
[data-skin="hanakenji"] .wf .actions { margin: 0; }
[data-skin="hanakenji"] .wf-send { width: 100%; justify-content: center; }
[data-skin="hanakenji"] .wf-sent { justify-items: center; text-align: center; padding-bottom: 2.2rem; }
[data-skin="hanakenji"] .wf-sent > div { display: grid; gap: .8rem; justify-items: center; }
[data-skin="hanakenji"] .wf-sent:focus { outline: none; }
[data-skin="hanakenji"] .wf-sent-mark { margin: 0; font: 1.4rem/1 var(--display); letter-spacing: .3em; color: var(--accent-ink); }
[data-skin="hanakenji"] .wf-sim { margin: 0; font: var(--step--1)/1.5 var(--ui); color: var(--muted); }

/* ---------- the screen ---------- */
[data-skin="hanakenji"] .ws {
  --ws-ink: #f4efe6; --ws-dim: rgba(244, 239, 230, .7); --ws-glow: 255, 214, 160;
  position: absolute; inset: 0; overflow: hidden; color: var(--ws-ink);
  background:
    radial-gradient(90cqmin 40cqmin at 50% 0%, rgba(var(--ws-glow), .16), transparent 70%),
    radial-gradient(130% 110% at 50% 30%, #1d2340 0%, #131729 55%, #0a0c16 100%);
  font-family: var(--display);
}
[data-skin="hanakenji"] .ws p { margin: 0; }
[data-skin="hanakenji"] .ws-names {
  position: absolute; left: 4cqmin; top: 4cqmin; z-index: 3;
  font: 400 4.4cqmin/1 var(--display); letter-spacing: .04em;
}
[data-skin="hanakenji"] .ws-names i { font-style: normal; color: var(--blossom); }
[data-skin="hanakenji"] .ws-live {
  position: absolute; right: 4cqmin; top: 4.6cqmin; z-index: 3;
  display: flex; align-items: center; gap: 1.1cqmin;
  font: 600 1.7cqmin/1 var(--ui); letter-spacing: .24em; text-transform: uppercase; color: var(--ws-dim);
}
[data-skin="hanakenji"] .ws-sep { opacity: .6; }
[data-skin="hanakenji"] .ws-dot {
  width: 1.2cqmin; height: 1.2cqmin; border-radius: 50%; background: var(--blossom);
  animation: hk-ws-pulse 2.6s ease-in-out infinite;
}
@keyframes hk-ws-pulse { 50% { opacity: .3; scale: .7; } }
[data-skin="hanakenji"] .ws-study {
  position: absolute; left: 4cqmin; bottom: 3cqmin; z-index: 3;
  font: 600 1.35cqmin/1 var(--ui); letter-spacing: .16em; text-transform: uppercase; color: rgba(244, 239, 230, .62);
}
/* the lantern row: a string across the top, paper globes lit from inside */
[data-skin="hanakenji"] .ws-lanterns {
  /* hung between the names and the count, never across them */
  position: absolute; left: 36cqmin; right: 30cqmin; top: 0; height: 15cqmin; z-index: 1;
  display: flex; justify-content: space-evenly; align-items: flex-start; pointer-events: none;
  background: linear-gradient(to bottom, transparent calc(4cqmin - 1px), rgba(244, 239, 230, .18) 4cqmin, transparent calc(4cqmin + 1px));
}
[data-skin="hanakenji"] .ws-lanterns i {
  display: block; width: 4.2cqmin; height: 4.8cqmin; margin-top: 4cqmin; border-radius: 48% 48% 46% 46%;
  background: radial-gradient(circle at 50% 42%, #fff6e6 0%, #ffe2b8 38%, rgba(var(--ws-glow), .6) 70%, rgba(var(--ws-glow), .2) 100%);
  box-shadow: 0 0 4cqmin 1cqmin rgba(var(--ws-glow), .28);
  animation: hk-ws-glow 5s ease-in-out infinite alternate;
}
[data-skin="hanakenji"] .ws-lanterns i:nth-child(odd) { margin-top: 5.4cqmin; animation-delay: -2.2s; }
[data-skin="hanakenji"] .ws-lanterns i:nth-child(3n) { animation-duration: 6.4s; }
@keyframes hk-ws-glow { to { opacity: .82; } }
@container (aspect-ratio < 1.2) { [data-skin="hanakenji"] .ws-lanterns i:nth-child(n+5) { display: none; } }

[data-skin="hanakenji"] .ws-stage { position: absolute; inset: 0; z-index: 2; display: grid; place-items: center; padding-top: 6cqmin; pointer-events: none; }
[data-skin="hanakenji"] .ws-drift { position: absolute; inset: 0; z-index: 1; pointer-events: none; }
/* every card is an ema: wood grain, a gabled top, a red cord through it */
[data-skin="hanakenji"] .ws-card {
  position: relative; display: flex; align-items: stretch;
  color: var(--sumi);
  background-color: var(--sk-ema-wood);
  background-image: repeating-linear-gradient(96deg,
    transparent 0 .6cqmin, color-mix(in oklab, var(--sk-ema-edge) 16%, transparent) .6cqmin .7cqmin);
  clip-path: polygon(50% 0, 100% 7cqmin, 100% 100%, 0 100%, 0 7cqmin);
  filter: drop-shadow(0 2cqmin 4cqmin rgba(0, 0, 0, .5));
  transition: opacity 1.2s ease, scale 1.4s cubic-bezier(.2, .7, .2, 1);
}
[data-skin="hanakenji"] .ws-card[data-state="enter"] { opacity: 0; scale: .97; }
[data-skin="hanakenji"] .ws-card[data-state="out"] { opacity: 0; scale: 1.01; }
[data-skin="hanakenji"] .ws-peg {
  position: absolute; top: 4cqmin; left: 50%; translate: -50% 0; z-index: 1;
  width: 4cqmin; height: .7cqmin; border-radius: .4cqmin;
  background: color-mix(in oklab, var(--cherry) 76%, var(--sumi));
}
[data-skin="hanakenji"] .ws-photo { margin: 0; flex: none; line-height: 0; }
[data-skin="hanakenji"] .ws-photo img { display: block; width: 100%; height: 100%; object-fit: cover; border-radius: .5cqmin; }
[data-skin="hanakenji"] .ws-words { display: flex; flex-direction: column; justify-content: center; gap: 2.4cqmin; min-width: 0; }
[data-skin="hanakenji"] .ws-msg { font-weight: 400; line-height: 1.35; text-wrap: pretty; overflow-wrap: anywhere; }
/* on an ema the wish is the content and the name is the signature, set under it */
[data-skin="hanakenji"] .ws-sig { display: flex; flex-wrap: wrap; gap: .4em 1em; align-items: baseline; }
[data-skin="hanakenji"] .ws-who { font-family: var(--display); color: var(--cherry-ink, var(--cherry)); }
[data-skin="hanakenji"] .ws-when { font-family: var(--ui); color: color-mix(in oklab, var(--sumi) 72%, var(--sk-ema-wood)); letter-spacing: .04em; }

[data-skin="hanakenji"] .ws-card[data-kind="featured"] { grid-area: 1 / 1; max-width: 90cqw; padding: 9cqmin 3.6cqmin 3.6cqmin; gap: 3.6cqmin; }
[data-skin="hanakenji"] .ws-card[data-kind="featured"][data-side="right"] { flex-direction: row-reverse; }
[data-skin="hanakenji"] .ws-card[data-kind="featured"][data-shape="tall"] .ws-photo { width: 40cqmin; height: 53cqmin; }
[data-skin="hanakenji"] .ws-card[data-kind="featured"][data-shape="wide"] .ws-photo { width: 58cqmin; height: 43.5cqmin; }
[data-skin="hanakenji"] .ws-card[data-kind="featured"] .ws-words { width: 34cqmin; padding-block: 1cqmin; }
[data-skin="hanakenji"] .ws-card[data-kind="featured"] .ws-msg { font-size: 3.3cqmin; }
[data-skin="hanakenji"] .ws-card[data-kind="featured"][data-len="s"] .ws-msg { font-size: 4.2cqmin; }
[data-skin="hanakenji"] .ws-card[data-kind="featured"][data-len="l"] .ws-msg { font-size: 2.7cqmin; }
[data-skin="hanakenji"] .ws-card[data-kind="featured"] .ws-who { font-size: 2.6cqmin; }
[data-skin="hanakenji"] .ws-card[data-kind="featured"] .ws-when { font-size: 1.5cqmin; }
[data-skin="hanakenji"] .ws-card[data-kind="featured"]:not([data-has-photo]) { padding: 11cqmin 8cqmin 6cqmin; }
[data-skin="hanakenji"] .ws-card[data-kind="featured"]:not([data-has-photo]) .ws-words { width: 50cqmin; align-items: center; text-align: center; }
[data-skin="hanakenji"] .ws-card[data-kind="featured"]:not([data-has-photo]) .ws-msg { font-size: 4.4cqmin; }
[data-skin="hanakenji"] .ws-card[data-kind="featured"]:not([data-has-photo])[data-len="l"] .ws-msg { font-size: 3.4cqmin; }
[data-skin="hanakenji"] .ws-card[data-kind="featured"]:not([data-has-photo]) .ws-sig { justify-content: center; }

[data-skin="hanakenji"] .ws-card[data-kind="drift"] {
  position: absolute; width: 22cqmin; flex-direction: column; gap: 1.4cqmin;
  padding: 6cqmin 1.8cqmin 2cqmin; clip-path: polygon(50% 0, 100% 4.4cqmin, 100% 100%, 0 100%, 0 4.4cqmin);
  filter: blur(.18cqmin) saturate(.7) brightness(.9); opacity: .3;
  animation: hk-ws-sway 30s ease-in-out infinite alternate;
  transform-origin: 50% 0;
}
[data-skin="hanakenji"] .ws-card[data-kind="drift"][data-state="enter"],
[data-skin="hanakenji"] .ws-card[data-kind="drift"][data-state="out"] { opacity: 0; }
[data-skin="hanakenji"] .ws-card[data-kind="drift"] .ws-peg { top: 2.4cqmin; width: 2.6cqmin; height: .5cqmin; }
[data-skin="hanakenji"] .ws-card[data-kind="drift"] .ws-photo { height: 13cqmin; }
[data-skin="hanakenji"] .ws-card[data-kind="drift"] .ws-words { gap: .8cqmin; }
[data-skin="hanakenji"] .ws-card[data-kind="drift"] .ws-msg {
  font-size: 1.8cqmin; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
[data-skin="hanakenji"] .ws-card[data-kind="drift"] .ws-who { font-size: 1.3cqmin; }
[data-skin="hanakenji"] .ws-card[data-kind="drift"] .ws-when { display: none; }
[data-skin="hanakenji"] .ws-card[data-slot="1"] { left: 6cqw; top: 20cqh; rotate: -4deg; animation-duration: 27s; }
[data-skin="hanakenji"] .ws-card[data-slot="2"] { right: 7cqw; top: 18cqh; rotate: 3deg; animation-duration: 33s; animation-delay: -8s; }
[data-skin="hanakenji"] .ws-card[data-slot="3"] { left: 13cqw; bottom: 6cqh; rotate: 2deg; animation-duration: 38s; animation-delay: -15s; }
[data-skin="hanakenji"] .ws-card[data-slot="4"] { right: 12cqw; bottom: 8cqh; rotate: -2.5deg; animation-duration: 31s; animation-delay: -5s; }
/* a plaque on a cord sways; it does not travel */
@keyframes hk-ws-sway {
  from { translate: 0 0; }
  to { translate: 1.6cqmin -1.2cqmin; }
}

[data-skin="hanakenji"] .ws-quiet {
  position: absolute; inset: 0; z-index: 2; display: grid; place-content: center; gap: 2cqmin;
  text-align: center; padding: 8cqmin;
}
[data-skin="hanakenji"] .ws-quiet[hidden] { display: none; }
[data-skin="hanakenji"] .ws-quiet-h { font: 400 5.4cqmin/1.15 var(--display); }
[data-skin="hanakenji"] .ws-quiet p:not(.ws-quiet-h) { font: 2.2cqmin/1.5 var(--body); color: var(--ws-dim); }
[data-skin="hanakenji"] .ws[data-state="closed"] .ws-lanterns i { opacity: .25; animation: none; box-shadow: none; }

[data-skin="hanakenji"] .ws[data-paused] :is(.ws-card, .ws-dot, .ws-lanterns i) { animation-play-state: paused; }
[data-skin="hanakenji"] .ws[data-still] :is(.ws-card, .ws-dot, .ws-lanterns i) { animation: none; }
[data-skin="hanakenji"] .ws[data-still] .ws-card { transition-property: opacity; }
@media (prefers-reduced-motion: reduce) {
  [data-skin="hanakenji"] :is(.ws-card, .ws-dot, .ws-lanterns i) { animation: none; transition-property: opacity; }
}

/* ══════════════════════════════════════════════════════════════════════════════════════════
   §22 · THE FOREVER PAGE (2026-08-28)
   Site 2's dress for forever.js, used by features/forever/index.html's hanakenji block. Site 2
   has no Forever page of its own — it is a feature of the package — so these classes exist only
   for that demo, same as §21.

   The sealed state is a shut paper door, not a locked box: this skin's whole idiom is paper, and
   a padlock would be the only hard object on the site.
   ══════════════════════════════════════════════════════════════════════════════════════════ */
.locked {
  display: grid; gap: .9rem; justify-items: start;
  max-width: 62ch;
  padding: clamp(1.4rem, 1rem + 2vw, 2.4rem);
  background: var(--card);
  border: var(--rule-w) solid var(--rule-strong);
  border-radius: var(--radius);
  /* the shoji lattice, read as a shut screen */
  background-image:
    linear-gradient(90deg, var(--rule) 1px, transparent 1px),
    linear-gradient(0deg, var(--rule) 1px, transparent 1px);
  background-size: 84px 100%, 100% 46px;
  background-position: 20px 0, 0 22px;
  box-shadow: var(--a-lift);
}
.locked[hidden] { display: none; }
.counter {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
  gap: var(--gap);
  padding-bottom: var(--gap);
  border-bottom: 2px solid var(--ink);
  margin-bottom: var(--gap);
}
.fchapter { display: grid; gap: var(--gap); padding-top: var(--gap); border-top: var(--rule-w) solid var(--rule); }
.chapter-blurb { color: var(--muted); }
.fchapter-shots { display: grid; gap: .9rem; grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr)); }
.fchapter-shots figure { margin: 0; line-height: 0; }
.fchapter-shots img { display: block; width: 100%; border-radius: var(--radius); cursor: zoom-in; }

/* ---------- print ----------
   Site 2 has NO printable guest card — the .gcard came out with the RSVP simplification on
   2026-08-28 (that flourish is site 1's). So print here is just the document without its
   furniture: no rail, no docks, no ornament, no paper grid. */
@media print {
  .rail, .foot, .music-dock, .lb, .noren, .a-petals,
  .lanterns, .fuji-scene, .crane-pair, .sensu, .torii,
  .omikuji, .pond,
  .guest-ribbon, .actions { display: none !important; }
  .plate--torii { padding-top: 0 !important; }
  .torii-frame { padding-top: 0 !important; }
  body { background: #fff; background-image: none !important; padding-left: 0 !important; }
}


/* ══════════════════════════════════════════════════════════════════════════════════════════
   §23 · THE CINEMATIC PASS — the tonal score (2026-09-06)

   Brief: `tools/Cinematic Site Two.html`. Fallback: `_archive/hanakenji-before-cinematic/`.

   ── The finding this section exists for ───────────────────────────────────────────────────
   Site 2 was 25,808px of page with 332px of it dark — one band, on Story — and not one
   full-bleed photograph anywhere. Every band was the same ivory at the same value for ten
   thousand pixels. No amount of ornament fixes that: the problem was that the page never
   changed key. §23 is the key changes; §19's motifs sit on top of them.

   ── THE RULE, written down so a later pass does not "even it out" ─────────────────────────
     · one dark passage per ~3,000px of page
     · never two adjacent, and a bleed between any two inks
     · the reply FORM never goes dark (rsvp/index.html) — it is the one surface a guest has to
       work in. The dark close goes after a form, never under it. Home's #reply is a link and
       two onward cards, not a form, which is why it may take the ink.
     · the score varies PER PAGE on purpose. §19's header already records that the motifs are
       deliberately not uniform; the same applies here.

   ── Home's score, as built ────────────────────────────────────────────────────────────────
     hero        BLEED  hero-wide          ← §23.3, the threshold
     ledger      ivory
     invitation  ivory                     ← §19.4's photographed gate
     the day     BLEED  venue-03
     schedule    ivory                     ← §19.5's lanterns
     what to wear  INK  ground-sumi-01
     gifts       ivory
     after dark  BLEED  bleed-home-02
     a fortune   raised
     questions   ivory
     reply       INK    ground-sumi-02     ← the close
     footer      paper                     ← §17's decision, untouched: the page ends on the
                                             sheet it began on
   ~19% dark against 1.3% before. Two inks, separated by a bleed. Three bleeds, none adjacent.

   ── Tone this whole section with four things ──────────────────────────────────────────────
     --sk-frame-h        how tall a bleed passage is
     --sk-frame-x        which corner its paper sheet sits in (start | end)
     --sk-hero-h         the threshold's first screen
     the .band--sumi-0N class  which sumi ground an ink passage tiles, or none at all
   Reverting one passage is deleting one class from one section. Reverting the pass is the
   archive folder, and it is all five files or none.

   ⚠ Unplaced by this pass, deliberately, and the user should say where they go:
   `bleed-home-01` (dusk sea and karst — the strongest close on the site, but home already
   closes on ink) and `venue-02` (the lantern garden by day, superseded by bleed-home-02).
   ══════════════════════════════════════════════════════════════════════════════════════════ */

[data-skin="hanakenji"] {
  --sk-frame-h: min(86vh, 46rem);
  --sk-hero-h: min(94svh, 52rem);
}

/* ── 23.1 · the ink passage ────────────────────────────────────────────────────────────────
   A TOKEN RE-POINT, not a set of per-element overrides. Everything in this skin reads --muted
   / --faint / --rule / --card / --accent-ink, so re-pointing those five inside the band fixes
   every child at once — including the three the old four-rule version missed (.swatch span,
   .list-checks li, .link). Measured against #2b2733: --muted 8.5:1, --faint 5.1:1,
   --accent-ink (blossom) 8.0:1. --accent stays cherry, which is 3.2:1 on sumi and therefore
   never carries small text here — it only ever fills a .btn, where the type is #fff on cherry
   at 4.52:1, exactly as on paper. */
.band--ink {
  --bg: var(--sumi);
  --bg-raised: color-mix(in oklab, var(--paper) 8%, var(--sumi));
  --card: color-mix(in oklab, var(--paper) 10%, var(--sumi));
  --ink: var(--paper);
  --muted: color-mix(in oklab, var(--paper) 80%, var(--sumi));
  --faint: color-mix(in oklab, var(--paper) 62%, var(--sumi));
  --rule: color-mix(in oklab, var(--paper) 22%, var(--sumi));
  --rule-strong: color-mix(in oklab, var(--paper) 44%, var(--sumi));
  --accent-ink: var(--blossom);
  --field: color-mix(in oklab, var(--paper) 12%, var(--sumi));
  background-color: var(--sumi);
  color: var(--paper);
}
.band--ink .seal { background: var(--blossom); color: var(--sumi); }
/* A rule drawn 2px in --ink was invisible the moment --ink became paper's opposite; these two
   are the only places the skin hard-codes it. */
.band--ink :is(.ledger, .hero-when, .qa, .stats, .guide, .sched-group, .plate--say figcaption) { border-color: var(--rule-strong); }

/* the photographic ground of a dark passage. 1600 × 1600, tileable, and already near-black —
   it is grain on the sumi, never a picture. Drop the class and the band is a flat colour. */
/* 640px, not 400: at 400 the tile's own frame edges read as a lattice on a tall passage. */
.band--sumi { background-image: var(--sk-sumi-ground); background-size: 640px 640px; background-repeat: repeat; }
.band--sumi-01 { --sk-sumi-ground: url("../assets/hanakenji/ground-sumi-01.jpg"); }
.band--sumi-02 { --sk-sumi-ground: url("../assets/hanakenji/ground-sumi-02.jpg"); }

/* ── 23.2 · the frame: site 2's full-bleed passage ─────────────────────────────────────────
   The photograph IS the passage, and the type sits on an opaque paper sheet flush into one of
   its bottom corners. Not engine `.a-bleed`: that lays white type on a gradient scrim, which
   is site 1's interlude verbatim and the one thing the differentiation charter names. This is
   the threshold hero's device reused — the picture runs UNDER a sheet of the page's own paper.
   Two consequences worth stating: the contrast arithmetic is trivial whatever image lands, and
   the sheet is flush into the corner rather than floating in the middle, because the project
   already rejected a box of type inside a photograph ("read as a grey rectangle floating on a
   picture — the brochure problem in miniature"). Flush to two edges, it reads as the page
   showing through rather than as a card. */
.frame {
  position: relative; margin: 0;
  display: grid; align-items: end; justify-items: var(--sk-frame-x, start);
  min-height: var(--sk-frame-h);
  overflow: clip; isolation: isolate;
}
/* ⚠ `> picture > img` added 2026-09-08 (evening), and it is not a nicety — `display: contents`
   on the wrapper removes its BOX, not its place in the tree, so a child selector stops matching
   the moment an img is wrapped. Story's close lost this whole rule that way: the picture fell
   back to static flow at `object-fit: fill` and set the figure's height off its own aspect ratio
   instead of covering the frame. `atelier.css` pairs every `> img` with `> picture > img` for
   exactly this reason; the skin now does too. */
.frame > img,
.frame > picture > img {
  position: absolute; inset: 0; z-index: 0;
  width: 100%; height: 100%;
  object-fit: cover; object-position: var(--sk-frame-crop, 50% 44%);
  filter: var(--a-tone);
}
.frame-say {
  position: relative; z-index: 1;
  width: min(100%, 33rem);
  display: grid; gap: .55rem;
  padding: clamp(1.3rem, 1rem + 1.6vw, 2.1rem);
  background: var(--paper);
  box-shadow: 0 -20px 44px -28px rgba(43, 39, 51, .55);
}
.frame--right { --sk-frame-x: end; }
.frame-say .plate-credit { text-align: left; margin-top: .3rem; }
@media (max-width: 47.99em) {
  .frame { --sk-frame-h: min(80vh, 32rem); }
  .frame-say { width: 100%; }
}

/* ── 23.3 · the threshold: the opening (move A1) ───────────────────────────────────────────
   Chosen by the user over A2 (the letterbox) and against A3 (the scrim), which is site 1's
   hero form verbatim and is rejected on the charter.

   The split hero's rules are still in §17 and its charter commentary with them — this is an
   override on `.hero--threshold`, so the revert is deleting one class from one element. What
   the split got wrong was scale, not idea: the photograph was a 569px column on a 1440px
   screen — smaller than the invitation plate further down the same page — and below 1024px it
   collapsed to photo-then-type, so a tablet opened on a pale picture with no names on it.

   Site 2's axis SURVIVES: the names are still set in paper, on an opaque sheet, and nothing is
   typed over the photograph. The split simply becomes paper-over-photograph instead of
   paper-beside-photograph. And the noren stops being decoration — it hangs INSIDE the frame,
   so you are looking through the doorway curtain at the scene. That is what makes moves A and
   B pay for each other.

   ⚠ The sheet is FIRST in source order and the photograph is absolutely positioned behind it,
   which is what fixes the below-1024 defect for free: the names are on the first screen at
   every width, with no media query deciding it.
   ⚠ `hero-wide.jpg` is cropped to 58% across on purpose — the doorway opening and the couple
   are right of centre and the near-black interior is at the left, which is where the vertical
   date is set in white. Do not re-centre the crop without re-checking that date's ground. */
.hero--threshold {
  position: relative; padding: 0;
  display: grid; grid-template-columns: none; align-items: end;
  min-height: var(--sk-hero-h);
  overflow: clip; isolation: isolate;
}
.hero--threshold .hero-bleed {
  position: absolute; inset: 0; z-index: 0;
  width: 100%; height: 100%;
  object-fit: cover; object-position: 58% 48%;
  filter: var(--a-tone);
}
.hero--threshold .hero-paper {
  position: relative; z-index: 2; justify-self: start;
  width: min(100%, 36rem);
  padding: clamp(1.5rem, 1.1rem + 2.2vw, 2.7rem);
  /* ⚠ 2026-09-07 (§24): the sheet is now TRANSLUCENT paper, not opaque — the user's note. It is
     86%, and that number is the floor, not a taste: at 86% over the photograph's darkest ground
     (the pavilion interior) the sheet composites to ~L.35 and --ink reads 5.4:1 on it. Anything
     lighter than 80% and the small line under the names fails. Nothing on this sheet may use
     --faint or --muted while it is translucent; §24 re-points the one line that did. */
  background: color-mix(in oklab, var(--paper) 86%, transparent);
  border-radius: 0 calc(var(--radius) + 4px) 0 0;
  box-shadow: 0 -20px 50px -28px rgba(43, 39, 51, .5);
}
.hero--threshold .hero-say { gap: .55rem; max-width: none; }
/* One line, not three: the amp goes inline. At --step-5 over three lines the sheet was 83% of
   the first screen and the photograph had nothing left to be. */
[data-skin="hanakenji"] .hero--threshold .hero-names { font-size: var(--step-4); line-height: 1.04; }
.hero--threshold .hero-amp { display: inline; font-size: .44em; margin: 0 .2em; }
.hero--threshold .hero-lede { max-width: 44ch; }
.hero--threshold .hero-when { border-bottom: var(--rule-w) solid var(--rule); }
@media (min-width: 48em) {
  .hero--threshold .hero-when { grid-template-columns: repeat(3, minmax(0, 1fr)); column-gap: var(--gutter); }
  .hero--threshold .hero-when > div { grid-template-columns: minmax(0, 1fr); border-bottom: 0; padding-block: .75rem; }
}
.hero--threshold .hero-tate {
  top: clamp(1rem, 5vh, 3rem);
  left: clamp(.7rem, 2.4vw, 2rem); right: auto;
  color: var(--blossom-pale);
  text-shadow: 0 1px 14px rgba(20, 16, 24, .9);
}
/* 50rem, not 44: at 390 the sheet is 540px of type and 44rem left the photograph a 134px band
   — the split hero's defect turned inside out. The photograph gets ~260px here and more on a
   real phone, where 96svh is the binding half of the min(). */
@media (max-width: 61.99em) { .hero--threshold { --sk-hero-h: min(96svh, 50rem); } }
@media (max-width: 47.99em) { .hero--threshold .hero-when > div { padding-block: .45rem; } }
/* the label shrink-to-fits at 272px and wraps mid-phrase ("REPLY TO THE / INVITATION"). Above
   48em the sheet is 576px, so there is room for it on one line; at 390 there is not, and a
   wrapped label is better than an overflowing one. */
/* ⚠ `:is(.btn, .btn--rule)`, matching the convention every other rule in §"controls" uses — the
   hero's call to action is `.btn--rule` and carries NO `btn` base class, so a bare `.btn` here
   matched nothing at any width and left the wrap it was written for in place. */
@media (min-width: 48em) { .hero--threshold .hero-act :is(.btn, .btn--rule) { white-space: nowrap; } }

/* ⚠ Below 48em the overlay inverts itself, and this is the phone defect the user screenshotted.
   The sheet is ~72% of the hero at 390 — it holds an eyebrow, two names at --step-4, a lede,
   three ledger rows and the call to action — so `inset: 0` sized the photograph to the WHOLE
   hero and then covered the bottom 72% of it with opaque paper. The 28% left showing was the
   top 28% of the picture: the underside of the pavilion roof. The doorway, the cloth's opening,
   the couple and the sand were all behind the sheet.

   So at this one width the photograph stops being a backdrop and becomes its own band, with the
   sheet overlapping its foot by 2.4rem. Both halves of the threshold survive — the cloth still
   hangs in the opening (`.noren--hung` is positioned against the hero, which is unchanged) and
   the names are still set in paper with nothing typed over the picture. What is given up is the
   overlap being full-height, which at 390 was costing the photograph its entire subject.
   ⚠ `order: -1` and not source order: the sheet is deliberately FIRST in the markup (§23.3), so
   a column flex without it puts the picture under the paper. */
@media (max-width: 47.99em) {
  /* ⚠ align-items is reset explicitly. The base rule is a grid with `align-items: end`, which on
     a column flex becomes `flex-end` on the CROSS axis — so `.hero-paper` at `width: auto` sizes
     to fit-content and pins RIGHT. It only looks full-width while the lede's max-content happens
     to exceed the measure; shorter copy, or an interior page reusing this, would float it. */
  .hero--threshold { display: flex; flex-direction: column; align-items: stretch; min-height: 0; }
  .hero--threshold .hero-bleed {
    position: relative; inset: auto; order: -1; flex: none;
    width: 100%; height: 48svh; min-height: 17rem;
  }
  .hero--threshold .hero-paper { width: auto; margin: -2.4rem var(--gutter) 0; }
}

/* the cloth, hung in the doorway rather than laid across the top of the page.
   `aspect-ratio` and not a height: see §19.2 — the printed crest must stay a circle. The two
   inset values put it over the bright half of the frame, where the opening is; the paper sheet
   takes the bottom left, so the two never meet at any width. */
.noren--hung {
  position: absolute; top: 0; z-index: 1;
  left: 34%; right: 5%;
  height: auto; aspect-ratio: 1600 / 500;
  padding: 0; gap: .5%;
}
@media (max-width: 47.99em) { .noren--hung { left: 18%; right: 3%; } }

/* ── 23.4 · retired the hour it was written ───────────────────────────────────────────────
   `.band--gate { overflow-x: clip }` existed to contain a 150% crown. It clipped 41px off the
   invitation photograph at 1024 instead — the plate's own left overhang — and it was never
   needed: `body` already carries `overflow-x: clip`, and the audit reports 0px of overflow at
   all four widths without it. The gate is sized to the page's edge instead; see §19.4. Kept as
   a note rather than a class so nobody reintroduces the clip. */

/* ── 23.5 · the left edge (move F) ─────────────────────────────────────────────────────────
   Five separate fixed things stacked on the left edge and only one was labelled. Four of them
   are one strip now (§17 + the numerals above); the fifth — the design-study marker — moves to
   the opposite corner, which REVERSES §17's "the honesty label is part of the navigation".
   The reason: it was the loudest labelled thing on an edge whose actual navigation was
   unlabelled, so it read as the nav. It stays visible in the site's own chrome on every page,
   which is all the honesty rule requires.

   ⚠ `.demo-dock` itself stays `position: relative` and this is a VARIANT, because `.rail`
   carries a `backdrop-filter` — which makes it a containing block for fixed descendants. A
   fixed dock still inside the rail would pin to the rail, not to the viewport. Story, travel
   and reply keep the rail-foot dock until they are converted; do not "tidy" the base rule.
   ⚠ Bottom-right is a fixed overlay over scrolling content, which the audit excludes from the
   occlusion check by design. Music keeps top-right. */
.demo-dock--corner { position: fixed; right: 1rem; bottom: 4.4rem; z-index: 60; justify-items: end; }
.demo-dock--corner .dock-tab { width: auto; }
.demo-dock--corner .dock-panel { left: auto; right: 0; width: min(23rem, calc(100vw - 2.4rem)); }
@media (max-width: 63.99em) {
  .demo-dock--corner .dock-tab { padding: .55em 1.1em; }
  .demo-dock--corner .dock-label { writing-mode: horizontal-tb; letter-spacing: .12em; font-size: var(--step--1); }
}
/* music is hidden below 40em, so the study marker takes the corner back rather than floating
   4.4rem above nothing */
@media (max-width: 39.99em) {
  .demo-dock--corner { bottom: 1rem; }
  /* at 390 the tab was 170px of dark pill — the loudest object on the page, over a hero whose
     own call to action is 8px away from it. The honesty rule asks for visible, not for biggest. */
  .demo-dock--corner .dock-tab { padding: .5em .8em; }
  .demo-dock--corner .dock-label { font-size: .66rem; letter-spacing: .08em; }
}


/* ═════════════════════════════════════════════════════════════════
   §24 · THE HERO REWORK (2026-09-07) — the user's five notes

   The notes, in their words: the torii looks bad / zoom out more / the red version · too much
   text in the hero · smoother edges · the invitation note a little more transparent · put the
   countdown in the hero, glassy, you decide the placement · more interactive sections.

   Four knobs: --sk-gate-w (the gate's drawn width), --sk-gate-foot (where its feet stand),
   --radius (every soft edge on the site, §token block), and the paper alpha in §23.3.

   ── 24.1 · THE GATE IS RED, WHOLE, AND SMALL ──────────────────────────────────────────────
   `torii-gate-red.png` — 1600 × 1370 on alpha, built in the lab 2026-09-07, and it REPLACES
   the two-part crown+post assembly ON THIS PAGE ONLY (§19.4 still dresses story and travel).

   Why one file here when §19.4's header forbids one: that refusal was about a gate FRAMING a
   plate, where the frame's aspect follows the plate's and a single PNG stretches per page. This
   gate frames nothing — it is a free-standing object drawn at a width, its height following its
   own ratio, so there is no stretch to have. A two-part assembly cannot be zoomed out at all:
   its post is a fill between the crown and a container edge.

   How it was made, so it can be remade:
     · source is `_archive/hanakenji-torii-gen2-unused/torii-crown-vermilion.png` — generation
       2's vermilion, the variation the sumi crown BEAT on 2026-09-06. It lost on one measure:
       its uprights are only 31% opaque at the 700px cut, because the prompt asked for lacquer
       worn through "around the base" and the generator put that wear at the crop line.
     · the alpha holds to ≥.93 down to row 520 and collapses after 550 — so the crown is cut at
       520, above the dissolve, and the wear never ships.
     · the legs are the real column block (rows 300–520) MIRROR-TILED down to the foot line, not
       a stretched slice: a 16-row stretch smeared the grain into vertical streaks, which is
       what the first proof looked like. Mirroring keeps the grain and hides the seams.
     · then an alpha clean-up (close 4px, open 2px) to fill the worn pinholes, with the gained
       pixels colour-filled from a local box mean of solid neighbours, and a .6px feather.
     · two soft contact ellipses under the feet. They are IN the file, which is why the rail's
       mask crops the bottom 3% (§24.4).
   ⚠ vermilion measures 2.29:1 against the mean of the photograph it stands in. That is fine for
   an object of this size and wrong for anything with a rule or a letter in it — do not reuse
   this file as a mark, a divider or a bullet.

   Placement: the gate STANDS IN THE WATER at the horizon, feet on the waterline, left of the
   couple. The form's answer was "upper third, like a distant gate", and the upper third of this
   hero is pavilion roof and the noren — a gate there floats in a ceiling. The waterline is the
   one line in the frame a gate can stand on, and it is the Itsukushima reading, so the answer
   is honoured at its intent: small, whole, distant, photograph first.
   ⚠ Anchored by its FEET (bottom/top-at-the-waterline), never by its top: the height follows
   the drawn width, so a top anchor walks the feet off the waterline as the viewport changes.
   ⚠ hero-wide.jpg is 2800 × 1575 and the hero is never much shorter than 16:9, so `cover`
   barely crops it vertically at any width — that is what makes one waterline fraction hold at
   390 / 834 / 1024 / 1440. Re-check these two numbers if the hero photograph is ever swapped. */
[data-skin="hanakenji"] { --sk-gate-w: clamp(74px, 12.5%, 200px); --sk-gate-foot: 46%; }
.hero-gate {
  position: absolute; z-index: 1; left: 40.5%; bottom: var(--sk-gate-foot);
  width: var(--sk-gate-w); pointer-events: none;
}
.hero-gate img { display: block; width: 100%; height: auto; filter: var(--a-tone); }
[data-atelier~="motion"] .hero-gate { animation: hkGateStand 1.9s var(--ease) both .45s; }
@keyframes hkGateStand {
  from { opacity: 0; transform: translateY(14px) scale(.985); }
  to   { opacity: 1; transform: none; }
}

/* ── 24.3 · the hero, trimmed ─────────────────────────────────────────────────────────────
   Names + date + city, and nothing else. What left: the eyebrow, the three-cell date/hour/place
   ledger and the call to action. Where it went, so nobody restores it here: the hanami line is
   now the invitation section's lede, the hour is already in that section's closing prose and in
   the schedule, the full address is in the ledger and on the travel page, and the reply action
   is on the glass card (§24.2) and in the rail. `.hero-lede`, `.hero-when` and `.hero-act`
   still exist in §17 — the interior pages and the split-hero revert use them. */
.hero-line { margin: 0; color: var(--ink); font: 400 var(--step-1)/1.45 var(--display); }
.hero-dot { color: var(--accent-ink); padding-inline: .15em; }
@media (max-width: 30em) { .hero-dot { display: none; } .hero-city { display: block; } }

/* the kana of their names, drawn on before the Latin — one of the interactive asks. Static
   markup, one span a character, so it degrades to plain type with motion off. */
.hero-kana { margin: 0 0 .1rem; display: flex; gap: .42em; color: var(--accent-ink); font-size: var(--step-1); letter-spacing: 0; }
.hero-kana i { font-style: normal; }
[data-atelier~="motion"] .hero-kana i {
  animation: hkKana 900ms var(--ease) both;
}
.hero-kana i:nth-child(1) { animation-delay: 120ms; }
.hero-kana i:nth-child(2) { animation-delay: 260ms; }
.hero-kana i:nth-child(3) { animation-delay: 400ms; }
.hero-kana i:nth-child(4) { animation-delay: 540ms; }
.hero-kana i:nth-child(5) { animation-delay: 680ms; }
@keyframes hkKana {
  from { opacity: 0; transform: translateY(-.28em) scale(.86); filter: blur(2px); }
  to   { opacity: 1; transform: none; filter: none; }
}

/* ── 24.4 · the rail's gate fills as you read ─────────────────────────────────────────────
   rail.js already publishes --rail-progress (0–1) on the mount; this is the skin drawing it,
   which is the contract. The gate silhouette is §24.1's own file used as a MASK, so the mark
   and the ornament can never drift apart.
   ⚠ mask-size WAS 100% 97%, top-anchored: the last 3% of the PASS-1 file was the baked contact
   shadow, and in a mask a .34-alpha ellipse paints a smudge under the feet.

   ── 24.4b · RE-DERIVED ON THE PASS-2 GATE (2026-09-16) ────────────────────────────────────
   `NEXT.md` promised this mark "fixes itself for free" when the new gate landed. It does not —
   it fixes itself for two numbers, and here is the arithmetic, because the pass-2 file's
   footprint inside its frame is deliberately different.

     pass 1  content x 0…1599, y 0…1349   — the lintel CUT OFF at both edges and the top (the
             defect the reissue existed to fix), feet 1.5% up, contact ellipses baked in
     pass 2  content x 79…1520, y 199…1254 — 1442 × 1056 of gate inside 1600 × 1370, so 4.9%
             clear each side and 14.5% / 8.4% clear above and below, and NO baked shadow

   Two consequences, and neither is optional: `aspect-ratio: 1600/1330` now describes 314 rows of
   transparency rather than a silhouette, and a `100% 97%` crop that existed to cut a shadow off
   now cuts 3% off a gate that has 8.4% of air under it. Masking the CONTENT BOX instead:

     aspect-ratio   1442 / 1056                    the drawn gate's own ratio
     mask-size      1600/1442 = 110.96% of the box wide · 1370/1056 = 129.73% tall
     mask-position   x  79 / (1600 − 1442) = 50.0%  → `center`, because the gate is symmetrical
                        about the frame's centre line to 0.0px (measured, both variations)
                     y 199 / (1370 − 1056) = 63.38%

   ⚠ The x position is `center` by COINCIDENCE OF SYMMETRY, not by choice. If a future gate is
   ever off-centre in its frame, that number is 79/(1600−1442) and must be recomputed with it.
   ⚠ There is no shadow left to crop, so do not reintroduce a bottom crop "to be safe": every
   row from 199 to 1254 is gate, and 1254 is a flat foot cut to a constant 131px width. */
.rail-gate {
  width: 2rem; aspect-ratio: 1442 / 1056; margin-bottom: .1rem;
  -webkit-mask: url("../assets/hanakenji/torii-gate-red.png") center 63.38% / 110.96% 129.73% no-repeat;
  mask: url("../assets/hanakenji/torii-gate-red.png") center 63.38% / 110.96% 129.73% no-repeat;
  background-image: linear-gradient(to top,
    var(--accent) 0 calc(var(--rail-progress, 0) * 100%),
    color-mix(in oklab, var(--sumi) 17%, var(--paper)) calc(var(--rail-progress, 0) * 100%) 100%);
}

/* ── 24.5 · the schedule expands ──────────────────────────────────────────────────────────
   Seven items, each a real <details>: the time and the title are the summary, the note is the
   disclosure. No engine change and no new module — schedule.js fills by [data-wsd-field] and
   does not care what element holds it, so this is the skin's template and the skin's CSS. That
   is the cheapest honest way to make a section interactive here, and it comes with keyboard
   support, Find-in-page and print expansion for free.
   ⚠ The summary is the 44px target, not the mark. */
details.sched-item { display: block; padding-top: .9rem; border-top: var(--rule-w) solid var(--rule); }
.sched-sum {
  display: grid; grid-template-columns: 5.5rem 1fr auto; gap: .3rem .9rem; align-items: baseline;
  min-height: 44px; padding: .2rem 0 .5rem; cursor: pointer; list-style: none;
}
.sched-sum::-webkit-details-marker { display: none; }
.sched-sum:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 3px; }
[data-skin="hanakenji"] .sched-item .sched-title { grid-column: 2; margin: 0; transition: color var(--motion-fast) var(--ease); }
.sched-item .sched-time { margin: 0; }
.sched-sum:hover .sched-title { color: var(--accent-ink); }
.sched-mark {
  grid-column: 3; align-self: center; width: 11px; height: 11px;
  background: color-mix(in oklab, var(--accent) 30%, var(--paper));
  transform: rotate(45deg);
  transition: transform var(--motion-slow) var(--ease), background-color var(--motion-fast) var(--ease);
}
.sched-sum:hover .sched-mark { background: var(--accent); }
details[open] .sched-mark { background: var(--accent); transform: rotate(135deg) scale(1.15); }
.sched-item .sched-note {
  /* D4: 46em → the site measure, with the 6.4rem indent kept (so ~62ch minus the indent). */
  grid-column: auto; margin: 0 0 .3rem; padding-left: 6.4rem; max-width: var(--col);
}
@media (max-width: 47.99em) {
  .sched-sum { grid-template-columns: 4.4rem 1fr auto; }
  .sched-item .sched-note { padding-left: 0; }
}
[data-atelier~="motion"] details[open] .sched-note { animation: hkOpen 360ms var(--ease) both; }
@keyframes hkOpen { from { opacity: 0; transform: translateY(-7px); } to { opacity: 1; transform: none; } }

/* ── 24.6 · the phone, and print ──────────────────────────────────────────────────────────
   Below 48em the hero is a column (§23.3) and the photograph is its own band, so the gate is
   anchored to THAT band's height rather than to the hero's: max(48svh, 17rem) is the band's
   own height expression from §23.3, and .54 of it is the waterline. Keep the two in step. */
@media (max-width: 47.99em) {
  .hero-gate { top: calc(max(48svh, 17rem) * .54); bottom: auto; transform: translateY(-100%); left: 34%; }
  [data-atelier~="motion"] .hero-gate { animation: none; }
  /* ⚠ In the column flex the card is in FLOW, directly under the sheet (source order puts it
     after .hero-paper, and only .hero-bleed carries order: -1). It takes the sheet's bottom
     corners so the two read as one card — and the sheet KEEPS §23.3's -2.4rem pull onto the
     photograph, which is the phone fix; do not zero it to make room here. */
  .hero--threshold .hero-paper { border-radius: calc(var(--radius) + 4px) calc(var(--radius) + 4px) 0 0; }
}
@media (prefers-reduced-motion: reduce) {
  .hero-gate, .hero-kana i, details[open] .sched-note { animation: none !important; }
}
@media print {
  .hero-gate, .hero-count, .rail-gate { display: none !important; }
  details.sched-item .sched-note { display: block !important; }
}


/* ═════════════════════════════════════════════════════════════════
   §25 · ROUND THREE (2026-09-07) — the gate off the photograph, the glass, the scroll

   The user's notes: the torii should not be in the hero photograph — they meant the BLACK one on
   the invitation section, which "looks very bad" · they meant animations as you scroll, text and
   sections arriving · the countdown belongs beside the Hana-and-Kenji card, and its own card
   should go. Then three follow-ups that overrule two form answers, and the follow-ups win:
   the invitation gate should be the RED one, THINNER, and the border of the photograph "like it
   was when it was just code" · the countdown glassy and blended · and the names card glass too.

   ── 25.1 · the hero gate is gone ─────────────────────────────────────────────────────────
   §24.1's free-standing gate in the water is REMOVED from the markup. The file
   `torii-gate-red.png` stays, and stays load-bearing: it is the source the slim border gate
   below was cut from, and the rail's progress mark (§24.4) masks it. Do not delete the asset.
   §24.1's CSS is left in place, unused on this page, for the same reason §17's .hero-lede is —
   a revert should be a markup change, not an archaeology exercise.

   ── 25.2 · the invitation gate: vermilion, slim, and a border ─────────────────────────────
   Same two-file architecture as §19.4 — a crown scaled by width, plus a post tile — because the
   gate FRAMES A PLATE here, which is exactly the case §19.4's header reserves two files for and
   §24.1's one-file exception explicitly does not cover. What changes is the artwork and four
   numbers.

   `torii-crown-red.png` 1600 × 325 · `torii-post-red.png` 916 × 300 (83px column flush left).

   How the slim crown was cut, because "thinner" is not a scale:
     Scaling the old crown down thins its beams AND narrows the gate, and the gate has to stay
     the width of the photograph — so a uniform scale cannot do this. It is a 3-SLICE: the
     cleaned vermilion gate is scaled uniformly to 58% (which is the thinning), and its MIDDLE
     span — plain horizontal beam between the two uprights — is stretched back out to refill the
     1600 frame. Stretching a featureless beam along its own axis is invisible; stretching an
     upright is what streaked the first proof on 2026-09-07.
     Source is `torii-gate-red.png`, NOT the raw archive file: the raw vermilion's alpha is
     speckled with worn lacquer and a slice of it sampled holes. The gate file is the cleaned one.
     Slice boundaries 430 / 1170 — both outside both uprights, which is the only constraint.

   The 3-slice also fixed the geometry for free, and this is the part worth keeping. Uniform
   scaling moves the uprights INWARD in file coordinates: their centres went from 17.4% of the
   file to 9.81%, so at the same k = 1.20 the upright centres land 1.8% inside the photograph's
   edge instead of §19.4's 9.9%. That is the difference between a gate standing behind a picture
   and a gate that IS the picture's border — which is what was asked for, and it needed no change
   to k, so §19.4's measured k ≤ 1.25 rail clearance still holds unaltered.

   Measured, at k = 1.20 with the crown at left −10% — ⚠ **ALL FOUR RE-DERIVED 2026-09-16** on the
   pass-2 gate; the values below are the new ones and §37.3 carries the arithmetic:
     upright beam   6.375% of the plate's width  (was 6.225%)
     crown height   22.25% of the file → the post's top is **26.7cqw**  (was 24.4)
     post box       6.375% wide at **+1.25%**  (was 6.225% at −1.337%)
     the gate now sits a hair INSIDE the picture's edge rather than straddling it — see §37.3
   Frame inset is 3%: the picture starts just under the kasagi, the nuki crosses its top, and the
   uprights run the full height as its side border. ⚠ Not 11% — that was the sumi crown's much
   deeper beam block, and at 11% the slim gate floats a band of blank paper inside itself.

   ⚠ Every trap in §19.4 still applies to this gate, unchanged: `max-width: none` and the
   `.plate--torii` scoping (§17's `.plate img` is (0,1,1) and outranks a bare class), the
   `background-size` percentage on the post tile, `.torii`'s z-index against the toned filter's
   stacking context, and NO `overflow` clip anywhere. Read §19.4 before touching any of it.
   ⚠ **The tile percentage is 1103.5%, not 1104%** — it is `tile width / column width` and the
   new cut's column is 85px in a 938px tile. It is not a magic number and it is not shared with
   the sumi gate's 1104%; re-measure it whenever the post is re-cut.
   ⚠ **The second value is `100%`, NOT `auto`, and this was a live bug.** With `auto` the tile's
   own aspect sets the drawn height: 1103.5% of a 55px box is 610px wide, so 200px tall — against
   a post box 446px tall on a landscape plate, leaving the uprights **stopping 246px short of the
   picture's bottom edge** with `background-repeat: no-repeat` holding the gap open. `100%` fills
   the box instead, stretching a column of VERTICAL grain along its own direction, which is the
   same argument §37.3 uses for the beams. 🚫 Do not restore `auto` "to preserve the aspect": the
   aspect it preserves is the tile's, and the tile is not the thing being drawn. */
[data-skin="hanakenji"] .plate--torii .torii-frame { padding-top: 3%; }
.plate--torii .torii--red .torii-crown {
  position: absolute; top: 0; left: -10%;
  width: 120%; max-width: none; height: auto;
  display: block; border-radius: 0;
}
.torii--red .torii-post {
  top: calc(26.7cqw - 1px); width: 6.375%;
  background-image: url("../assets/hanakenji/torii-post-red.png");
  background-size: 1103.5% 100%;
}
.torii--red .torii-post--l { left: 1.25%; }
.torii--red .torii-post--r { right: 1.25%; }

/* ── 25.3 · the hero, in glass ────────────────────────────────────────────────────────────
   One panel now: the names on the left, the countdown on the right, inside a single smoked-glass
   sheet. That answers "beside the Hana and Kenji card" and "make the card for countdown
   disappear" together — there is no second card because there is only one surface.

   SMOKED, not paper, and that is measured rather than chosen. The hero photograph under this
   panel is very dark on the left (mean L .062 in the sheet's own footprint, worst tile .001 —
   the pavilion interior) and bright on the right where the water is (mean L .563, brightest tile
   .757). No single flat alpha of --paper serves both: ink on paper-glass is comfortable at the
   left and the countdown column would have sat on the water. Smoked glass with WHITE type
   inverts the problem into one that has an answer, because the panel only ever gets darker.

   ⚠ The gradient is the contrast fix, not a flourish. It deepens left-to-right BECAUSE the
   photograph brightens left-to-right. Measured against each sixth's brightest tile, white on the
   panel: 16.1 · 12.8 · 11.2 · 7.3 · 6.8 · 6.6 — all clear of 4.5, and the right end needs .88 to
   get there (at .66 the last three sixths measure 3.3–3.8, which is the version that fails).
   If the hero photograph is ever swapped, re-measure the six columns before touching these
   stops. The floor is the RIGHT end at .88, not the average.

   ⚠ WHITE type only, both columns, per DECISIONS.md — a translucent surface carries one ink.
   --accent-ink (the dark cherry) is 1.4:1 here; the kana is mixed toward white instead.
   ⚠ No backdrop-filter, per the standing refusal. The glass is built from a gradient, a rim
   light and a specular sweep — all of which composite the same everywhere. */
.hero--threshold .hero-paper {
  position: relative; z-index: 2; justify-self: start; overflow: hidden;
  width: min(100%, 38rem);
  display: grid; gap: clamp(1.1rem, 3vw, 2.4rem);
  padding: clamp(1.4rem, 1.1rem + 2vw, 2.4rem) clamp(1.3rem, 1rem + 2vw, 2.3rem);
  background: linear-gradient(100deg,
    color-mix(in srgb, var(--sumi) 62%, transparent) 0%,
    color-mix(in srgb, var(--sumi) 74%, transparent) 46%,
    color-mix(in srgb, var(--sumi) 88%, transparent) 100%);
  border-radius: 0 calc(var(--radius) + 6px) 0 0;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .24),
    inset 1px 0 0 rgba(255, 255, 255, .07),
    inset -1px 0 0 rgba(255, 255, 255, .07),
    0 -24px 60px -30px rgba(43, 39, 51, .6);
}
/* the specular sweep — a soft band of light travelling across the glass once, on arrival.
   ⚠ pointer-events: none is load-bearing: the sweep spans the whole panel and the reply link
   lives under it. */
.hero--threshold .hero-paper::after {
  content: ""; position: absolute; inset: -40% -10%;
  background: linear-gradient(74deg, transparent 34%, rgba(255, 255, 255, .13) 46%,
    rgba(255, 255, 255, .21) 50%, rgba(255, 255, 255, .13) 54%, transparent 66%);
  pointer-events: none;
}
[data-atelier~="motion"] .hero--threshold .hero-paper::after {
  animation: hkSheen 2.6s var(--ease) 1.1s both;
}
@keyframes hkSheen {
  from { transform: translateX(-62%); opacity: 0; }
  22%  { opacity: 1; }
  to   { transform: translateX(62%); opacity: 0; }
}
[data-skin="hanakenji"] .hero--threshold .hero-names { color: #fff; }
[data-skin="hanakenji"] .hero--threshold .hero-amp { color: rgba(255, 255, 255, .72); }
.hero--threshold .hero-line { color: #fff; }
.hero--threshold .hero-dot,
.hero--threshold .hero-kana { color: color-mix(in oklab, var(--accent) 42%, #fff); }
.hero--threshold .hero-say { max-width: 26rem; }

/* ── 25.5 · the scroll, turned on ─────────────────────────────────────────────────────────
   The page opts into `data-atelier~="reveal"` (§L2 + reveal.js). The vocabulary was already
   authored across this page — .a-rise, .a-wipe, .a-mask, .a-order — and the only reason none of
   it moved is that the view() driver was declined. The skin's job here is the two additions the
   user picked that had no home yet, and the tempo. */
[data-skin="hanakenji"][data-atelier~="reveal"] { --a-step: 96ms; --a-reveal-dur: 980ms; }
/* ⚠ The tempo tokens stay on the opt-in token — they are harmless without the driver. Only the
   from-states move to [data-reveal="on"]. */

/* the schedule's rules draw themselves out as each row arrives. The row's own top border is
   replaced by a scaling bar, because a border cannot be animated from nothing. */
/* ⚠ `[data-reveal="on"]` gates ONLY the row's real border, never the bar's own geometry. Gating
   the geometry too was a bug: in the `still` state (reduced motion) and in any other non-`on`
   state the span fell back to being an in-flow GRID ITEM, took the summary's 88px time column,
   and pushed "3:00 PM" into the title column — the row grew from one track to two and the time
   stacked on top of the event. Structural CSS for a decorative child must never depend on a
   driver attribute; only the from-state may.

   ⚠ THE BAR LIVES INSIDE `<summary>`, NOT INSIDE `<details>` — and that is not a style choice.
   A `<details>` renders its first `<summary>` and puts EVERY other child into the disclosure
   slot, which is display:none while the row is closed. A span authored as a sibling of the
   summary therefore measured 0 × 0, never painted, and all seven dividers were missing with
   `border-top-color: transparent` already applied. Absolute positioning still resolves against
   `details.sched-item` from in there, because that is the nearest positioned ancestor. */
details.sched-item { position: relative; }
details.sched-item .sched-rule {
  position: absolute; top: calc(var(--rule-w) * -1); left: 0; right: 0;
  height: var(--rule-w); background: var(--rule);
}
[data-reveal="on"] details.sched-item { border-top-color: transparent; }
/* the seals brush on, and they are figures, not controls */
.seal.a-brush { display: inline-block; }
/* the ledger's values turn up onto their face */
[data-skin="hanakenji"] .ledger dd.a-flip { display: block; }

/* ── 25.6 · the phone, and print ──────────────────────────────────────────────────────────
   Below 48em the panel is one column: names, then the divider becomes a top rule, then the
   countdown as four across. The gradient turns to run DOWNWARD there, because the stacked panel
   spans the photograph vertically rather than horizontally. */
@media (max-width: 47.99em) {
  .hero--threshold .hero-paper {
    gap: 1rem;
    width: auto; margin: 0 var(--gutter);
    border-radius: calc(var(--radius) + 6px);
    background: linear-gradient(168deg,
      color-mix(in srgb, var(--sumi) 66%, transparent) 0%,
      color-mix(in srgb, var(--sumi) 80%, transparent) 62%,
      color-mix(in srgb, var(--sumi) 90%, transparent) 100%);
  }
  .hero--threshold .hero-say { max-width: none; }
}
@media print {
  .hero--threshold .hero-paper {
    background: #fff !important; box-shadow: none !important; color: var(--ink) !important;
    display: block !important;
  }
  .hero--threshold .hero-paper::after { display: none !important; }
  [data-skin="hanakenji"] .hero--threshold .hero-names,
  .hero--threshold .hero-line { color: var(--ink) !important; }
  [data-reveal] details.sched-item { border-top-color: var(--rule) !important; }
}


/* ═════════════════════════════════════════════════════════════════
   §26 · THE COUNTDOWN AS BARE TYPE (2026-09-07, round four)

   The note: "glassy TEXT and not a card — move the countdown out of the Hana and Kenji card,
   just make sure there is no card background on it." So there is no surface at all here: the
   figures sit directly on the photograph, and "glassy" is carried by the TYPE.

   PLACEMENT IS A CONTRAST RESULT, NOT A COMPOSITION CHOICE, and this is the paragraph to read
   before moving it. With no panel there is nothing to raise contrast with, so the figures can
   only go where the photograph is reliably one thing at every width — and "at every width" is
   the whole difficulty, because the hero's box is much TALLER than the photograph's 16:9. At
   1440 the image box is 1185 × 832 and at 834 it is 758 × 800, so `cover` crops this picture
   HORIZONTALLY, hard, and by a different amount at each width. The right-hand door jamb — which
   an earlier measurement of a 1440 × 700 crop said was near-black — is simply not in frame at
   834, where the same spot is open water at L .72 and white type measured **1.36:1**.

   So the grid was re-measured properly: four real image boxes (390 / 834 / 1024 / 1440), eight
   columns by six rows, keeping the WORST cell across all four widths.
     · WHITE type clears 4.5 in exactly one band — the top sixth (12.6 – 18.1), which is the
       pavilion's dark lintel. The noren already hangs there. Everywhere below it, white measures
       1.1 – 3.4, because at narrow widths the crop centres on bright water.
     · INK type inverts the problem and wins: over the water it clears 4.5 in a solid block —
       x 38–63% at y 33–83% (5.2 – 6.6) and x 63–88% at y 33–50% (10.5 – 11.0).
   Hence: the countdown is a ROW OF INK FIGURES ACROSS THE WATER, x 40–86%, y ~34–50%. Sumi on a
   pale sea, above the couple and below the noren, with the door jambs left alone on both sides.

   ⚠ Do NOT let it reach past 88% of the width or below 33% of the height: column 7 is the right
   jamb (ink 1.4:1) and rows 0–1 are the dark lintel (1.4:1). Both fail for ink, and both are
   where white would have worked — the two inks have opposite safe zones and no overlap.
   ⚠ Re-measure the grid if the hero photograph, its `object-position` (58% 48%) or the hero's
   height expression is ever changed. This placement is downstream of all three.
   ⚠ Below 48em the hero is a column with a short, heavily cropped photograph band and none of
   this holds — the countdown leaves the picture entirely (§26.3).

   ── 26.1 · the glass is in the letterform ────────────────────────────────────────────────
   No box, three moves: a sumi gradient clipped to the glyphs so the figures shade from full ink
   at the top to a lighter wash at the baseline, a soft WHITE bloom behind them — the inverse of
   a drop shadow, and what actually makes ink read as glass on a bright ground — and a vermilion
   hairline under each figure.

   ⚠ The gradient bottoms out at 86% sumi, and that is a contrast floor. `background-clip: text`
   makes the gradient the type's actual colour, so its lightest stop is the real number: 86% sumi
   still clears 4.5:1 against the palest water measured, where a fade to 50% would not.
   ⚠ `-webkit-text-fill-color: transparent` is what reveals the clip; `color: var(--ink)`
   underneath is the fallback wherever the clip is unsupported. Never remove it.
   ⚠ Labels are `--ink`, not `--muted` — the standing decision holds: type on a photographic
   ground carries one ink, separated by size and tracking rather than by opacity. */
/* ⚠ `width: max-content` with a LEFT anchor only, deliberately: spread across the whole safe
   band (left 40% / right 14%) the four figures sat ~130px apart with 30px of type in between and
   read as four unrelated numbers rather than one clock. Grouped, the block measures ~18% of the
   width, so its right edge lands near 58% — comfortably inside the 86% limit even at the largest
   step, and it can never reach column 7's dark jamb no matter what the figures say. */

/* ── 26.2 · the load cue's keyframes — THE ONE THING §26 STILL PAINTS ──────────────────────
   Authored here for the bare-type register; §48 collapsed that register away and left these
   frames standing, because §36.7's stagger animates the live `--corner` cluster with them.
   🚫 Do not delete `hkCount` as an orphan of a dead section — it is the live register's cue.
   The load cue, not a scroll reveal — the hero is above the fold and §L2's own note says the
   first screen must never be animating while it is being read. */
@keyframes hkCount {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}

/* ── 26.3 · the phone, and print ──────────────────────────────────────────────────────────
   Off the photograph altogether: the jamb is not dependably in a 390-wide crop, and bare white
   type on an unknown ground is exactly what §26's measurements forbid. On the page's own paper
   it is ink, in a row of four, under the names panel — no card there either. */


/* ═════════════════════════════════════════════════════════════════
   §27 · ROUND FIVE (2026-09-07) — the countdown as a second pane, and cinematic glass

   The notes: the countdown goes BESIDE the Hana and Kenji card · larger, with text effects that
   make it more visible · the pink RSVP pill comes back, on it, glassy and lightly transparent ·
   and the glass itself should read more cinematically — a glow, a reflection.

   So the hero is now a DIPTYCH: two panes of the same smoked glass standing at the foot of the
   photograph, the names in the left one and the clock in the right. §26's bare ink type is
   retired from the markup (the class and its rules stay, as §24.2 and §25.4 do, so a revert is
   a markup change) — and with it goes the whole x 40–86% / y 33–50% placement constraint, which
   only existed because there was no surface to raise contrast with. There is one again.

   ⚠ THE SECOND PANE IS THE DARKER OF THE TWO AND ITS GRADIENT RUNS THE OTHER WAY, and both of
   those are measurements rather than tastes. §25.3's names pane deepens 62% → 88% sumi
   left-to-right because the photograph brightens left-to-right ACROSS THE WHOLE FRAME. This pane
   stands on the right half of that frame, where the ground goes the other way — bright sand and
   water at its left edge, the darker jamb and the deck boards at its right — so it runs
   **78% → 66%, left to right**. Copying §25.3's direction (the pane's first build) put the glass
   at its most transparent exactly where its ground was brightest, and the caption and the days
   figure both sit in that corner.

   ⚠ HOW THE FIGURES BELOW WERE MEASURED, because the first pass got it wrong: an 8 × 5 grid over
   the pane's own footprint, each tile paired with **the gradient's alpha at that tile** (not with
   an average), with the lit corner and the foot bounce composited in on top — they lighten the
   glass, and the lit corner lightens exactly the corner that is already worst. Worst cell of each
   acceptance width, and it is the pane's TOP ROW over the sand every time:
     digits (the 92%-white gradient floor)  5.35 · 5.51 · 5.63 · 5.37   at 1440 / 1024 / 834 / 390
     caption + labels (full white)          5.96 · 6.14 · 6.29 · 5.98
     the pill's white on 46% cherry         7.0 at its own worst cell
   The earlier "7.7 / 6.8" in this header was an average-alpha figure and described no cell on the
   page. Re-derive with local pairing if any stop, either pseudo-element, or the photograph moves.

   ⚠ Below 48em the pane leaves the photograph and stands on the page's ivory paper, so its
   register is 82% → 90% — nearly opaque, and that is not decoration: at the 62% it inherited from
   the photograph register, white digits on paper-backed glass measured **3.9:1**.

   ⚠ Still NO backdrop-filter, in either pane — DECISIONS.md → the standing refusal. Everything
   "glassmorphic" here is built from things that composite identically everywhere: a gradient, a
   rim light, a lit corner, a bounce off the foot, a travelling specular and an outer bloom.

   Four knobs:
     --sk-glass-2       the second pane's smoke (a pair of stops, in §27.2 — 78% at the LEFT)
     --sk-glass-bloom   the outer glow's colour and reach (both panes)
     --sk-pane-w        the second pane's drawn width above 48em
     --radius           still the one softness knob for the whole site
   ── 27.1 · the hero becomes two columns ──────────────────────────────────────────────────
   ⚠ `align-content: end` is load-bearing and applies at EVERY width. The hero is a grid with a
   min-height, so its auto rows stretch: with one in-flow child (§26's countdown was absolute)
   the single row filled the hero and `align-items: end` sat the panel on the floor. With two
   in-flow children the free space split between two rows and the panel floated to the middle of
   the picture. Ending the CONTENT box keeps both panes on the floor below 48em, where they
   stack, as well as side by side above it.
   ⚠ The columns are declared on `.hero--threshold`, not on `.hero`: §17's `.hero` sets
   1.04fr .96fr above 62em for the split hero, which §23.3 overrides with `none`. This is the
   third rule in that chain and must stay last in the file. */
[data-skin="hanakenji"] .hero--threshold { align-content: end; }
@media (min-width: 48em) {
  /* fr columns, not a fixed 38rem track: at 768 a 38rem names pane leaves 160px for the clock,
     which is why the two panes STACK below this breakpoint and only here. Above it each pane
     takes its share and then caps itself — the names at 38rem (§25.3), the clock at --sk-pane-w
     — so at 1440 the pair sits in the left 70% of the picture and the couple stays uncovered. */
  .hero--threshold {
    grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr);
    column-gap: clamp(.5rem, 1.2vw, 1.5rem);
  }
  .hero--threshold > .hero-paper { grid-column: 1; grid-row: 1; }
}

/* ── 27.2 · the second pane ───────────────────────────────────────────────────────────────
   Below 48em it is the panel's own width, directly under it with a 3px seam, so the two still
   read as one piece of glass rather than as a card that has come loose. */
[data-skin="hanakenji"] { --sk-pane-w: 24rem; }

/* ── 27.3 · what makes it cinematic, on BOTH panes ────────────────────────────────────────
   Four layers, and each one is doing a different job:
     · the rim — 1px of white along the top and the left, 1px of blossom along the right, 1px of
       near-black along the bottom. That is what gives the glass an edge and a thickness.
     · the bloom — a wide, very low blossom glow outside the box. It is the "glow" asked for, and
       it is tinted rather than white because a white bloom on this photograph reads as haze.
     · the lit corner — a radial at the top-left, transparent well inside the box, so it lights
       the glass without drawing a rectangle (the refusal in DECISIONS.md, satisfied by falloff).
     · the bounce — a short gradient up from the foot, the deck boards throwing light back into
       the glass. This is the "reflection", and it is what makes the pane read as a solid object
       standing on the picture rather than a tint laid over it.
   ⚠ `::before` carries the last two and sits at `z-index: -1`; both panes are positioned with a
   `z-index`, so each is its own stacking context and the pseudo cannot fall behind the pane's
   own background. ⚠ `::after` is the travelling specular and spans the whole pane — the reply
   pill is under it, so `pointer-events: none` there is load-bearing (as in §25.3). */
[data-skin="hanakenji"] { --sk-glass-bloom: color-mix(in srgb, var(--blossom) 22%, transparent); }
.hero--threshold .hero-paper {
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .30),
    inset 1px 0 0 rgba(255, 255, 255, .09),
    inset -1px 0 0 color-mix(in srgb, var(--blossom) 16%, transparent),
    inset 0 -1px 0 rgba(20, 16, 24, .34),
    0 -26px 66px -32px rgba(20, 16, 24, .70),
    0 0 96px -20px var(--sk-glass-bloom);
}
.hero--threshold .hero-paper::before {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background:
    radial-gradient(140% 100% at 4% -12%, rgba(255, 255, 255, .20), rgba(255, 255, 255, 0) 62%),
    linear-gradient(to top, rgba(255, 255, 255, .10), rgba(255, 255, 255, 0) 26%);
}

/* ── 27.7 · the phone, and print ──────────────────────────────────────────────────────────
   Below 48em the hero is a column flex (§23.3): the photograph is its own band, the names pane
   overlaps its foot by 2.4rem, and this pane follows in flow directly under it. The gradient
   turns to run downward there, matching §25.6, because a stacked pane crosses the ground
   vertically rather than horizontally.
   ⚠ These rules also have to beat §25.6's and §24.6's phone blocks, which both reach this
   element through the bare `.hero-count` class — hence the explicit resets of `padding-left`
   and `border-top` rather than trusting the cascade to leave them alone. */
/* the names pane keeps its own bottom corners square at that width, so the two meet in a seam */
@media (max-width: 47.99em) {
  .hero--threshold .hero-paper {
    border-radius: calc(var(--radius) + 6px) calc(var(--radius) + 6px) 0 0;
  }
}
@media print {
  /* all three registers of the figure: the fallback, the strip, and the strip's digits */
  .hero--threshold .hero-paper::before { display: none !important; }
}


/* ═════════════════════════════════════════════════════════════════
   §28 · ROUND SIX (2026-09-07) — the countdown is TYPE, on paper; the shine goes on the card

   The notes: **the countdown was never meant to have a glassy card** — the glass belongs to "the
   text and numbers"; its font did not look good; the Hana and Kenji card is the thing that should
   carry a shine or glow; the pill reads **"RSVP"** only; and the phone view was bad.

   §27's second pane is GONE. The markup class changed `--glass` → `--type`, which retires
   §27.1–27.7 the way §24.2, §25.4 and §26 were retired — by no longer matching. §27's rules for
   `.hero-paper` are NOT class-gated and still apply; §28.5 turns them up.

   ⚠⚠ THE CLOCK IS NO LONGER IN THE HERO AT ALL, AND THAT IS THE ROUND'S REAL DECISION. It sits on
   the ivory band immediately below the photograph, in its own `.band--count`. Three separate
   findings forced it, and they all have the same root — **there is only ~200px of picture below
   the couple's feet** (they stand at ~76% of the frame's height at every width, because this
   photograph crops horizontally):
     1. Glass type needs a ground. Bare white type on the sand measures **1.76:1**, so keeping the
        clock on the picture needs a wash — and a wash big enough to hold a 68px clock at plateau
        alpha covered ~98% of the picture's width and veiled the couple, the sand and the karst at
        0.74. That is the slab the user rejected, one round later and softer.
     2. `.hero--threshold` computes `overflow: clip`, so a wash that has not finished its falloff
        by the frame's edge is **cut off square** — the exact rectangle a feathered wash exists to
        avoid. Finishing the falloff inside the frame means insetting the type from the edge, which
        this frame has no room for.
     3. The fixed demo dock owns the bottom-right corner (`.demo-dock--corner`, ~160 × 44 at the
        viewport's foot). The seconds cell sat under it — `elementFromPoint` on the SEC label
        returned `dock-tab` — and lifting the clock clear of the dock pushes it up onto the
        couple's feet. 200px of picture cannot hold a 158px clock plus 64px of dock clearance.
   On paper none of the three exists: no wash, nothing veiled, no clip, in flow so the dock only
   crosses it while scrolling, and **ink on ivory measures 14.5:1** with no tricks at all.
   ⚠ If a future round wants the clock back on the photograph, it is not a CSS move — it needs a
   different crop with sky or water below the couple, or a shorter clock. Re-read this note first.

   ⚠ The figures are set in the UI sans (`--ui`, Karla), NOT the display mincho. The mincho's
   tabular set is uneven at this size and four cells of rolling digits in it read as a mistake,
   which is what the note was about. The names keep the mincho, so the two registers differ by
   family as well as size and the clock stops competing with the couple.

   Knobs:
     --sk-glass-bloom   the card's outer glow (§27.3, turned up in §28.5)
     --radius           still the one softness knob for the whole site

   ── 28.1 · the band ─────────────────────────────────────────────────────────────────────
   `band--flush` supplies the gutters and the sheet; the clock supplies its own vertical rhythm so
   it reads as the photograph's caption rather than as the next section. */

/* ── 28.4 · the card's shine, turned up ──────────────────────────────────────────────────
   The card already had a rim, a lit corner, a foot bounce, a bloom and a travelling sweep
   (§25.3 + §27.3). What the note wanted is for them to be VISIBLE, so:
     · the bloom widens and warms (30% blossom, 128px of reach);
     · the top rim goes to 2px of near-white — the bright edge you get where glass is cut;
     · a STATIC diagonal specular joins the lit corner and the bounce on `::before`, so the card
       looks lit even after the arrival sweep has passed;
     · the travelling sweep brightens (.21 → .32 at its core).
   ⚠ The static specular is capped at .07 white and sits in the card's upper-left, where its
   gradient is 62–74% and the type is headline-scale. It shifts the card's floor by under 0.2 of a
   contrast point; anything brighter has to be re-measured against `.hero-line`, which is body
   copy at 4.5:1 with no headline allowance.
   ⚠ Now that the clock is off the picture, the card is the ONLY thing on it again — so §27.1's
   `align-content: end` has one in-flow child to place, as §26 did. Do not delete it: it is what
   sits the card on the floor of the frame. */
[data-skin="hanakenji"] .hero--threshold { --sk-glass-bloom: color-mix(in srgb, var(--blossom) 30%, transparent); }
.hero--threshold .hero-paper {
  box-shadow:
    inset 0 2px 0 rgba(255, 255, 255, .42),
    inset 1px 0 0 rgba(255, 255, 255, .12),
    inset -1px 0 0 color-mix(in srgb, var(--blossom) 22%, transparent),
    inset 0 -1px 0 rgba(20, 16, 24, .34),
    0 -26px 66px -32px rgba(20, 16, 24, .70),
    0 0 128px -24px var(--sk-glass-bloom);
}
.hero--threshold .hero-paper::before {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background:
    linear-gradient(112deg, transparent 12%, rgba(255, 255, 255, .07) 30%,
      rgba(255, 255, 255, .02) 42%, transparent 52%),
    radial-gradient(140% 100% at 4% -12%, rgba(255, 255, 255, .24), rgba(255, 255, 255, 0) 62%),
    linear-gradient(to top, rgba(255, 255, 255, .12), rgba(255, 255, 255, 0) 26%);
}
.hero--threshold .hero-paper::after {
  background: linear-gradient(74deg, transparent 32%, rgba(255, 255, 255, .2) 45%,
    rgba(255, 255, 255, .32) 50%, rgba(255, 255, 255, .2) 55%, transparent 68%);
}

/* ── 28.6 · the phone ────────────────────────────────────────────────────────────────────
   What was wrong at 390: two dark slabs stacked with a seam, a pill whose label wrapped to two
   lines with the arrow falling out of it, and labels crowding the figures. All three are gone —
   the clock is the same ink-on-paper register it is everywhere now, just tighter.
   ⚠ These rules must beat §25.6's and §24.6's phone blocks, which reach this element through the
   bare `.hero-count` class — §25.6 in particular hands it `border-top: 1px solid rgba(255,255,255,.22)`
   and `padding-top: 1rem`. The resets are deliberate; do not "simplify" them away.
   ⚠ The names card gets its full radius back — §27.7 squared its bottom corners for a seam that
   no longer exists. */
@media (max-width: 47.99em) {
  .hero--threshold .hero-paper { border-radius: calc(var(--radius) + 6px); }
}

/* ── 28.7 · print ────────────────────────────────────────────────────────────────────────
   `.hero-count` is already `display: none` in print (§24.6) and stays that way — a countdown is
   meaningless on paper. The card's two pseudo-elements do need hiding: §27.7 takes `::before`. */
@media print {
  .hero--threshold .hero-paper::after { display: none !important; }
  .hero--threshold .hero-paper { box-shadow: none !important; }
}


/* ═════════════════════════════════════════════════════════════════
   §29 · ROUND SEVEN (2026-09-07) — the clock goes back onto the photograph, CUT IN OUTLINE

   The notes: put the countdown back on the hero photograph, in the position it held there · and
   give the type and the figures a contrasting outline so they read on any ground.

   That second note is the answer to the objection §28's header raises, and it is why this is a
   CSS move after all. §26–§28 all assumed the type needed a GROUND to be legible (a pane, then a
   haze, then ivory paper). An outline does not need a ground: white fill + a near-black stroke
   painted UNDER the fill (`paint-order: stroke fill`) carries its own contrast into the glyph, so
   the same figure reads over bright sand, over water and over the dark deck boards without
   anything being laid over the picture. Nothing is veiled, so §28's defect 1 and 2 (the wash, and
   `overflow: clip` cutting the wash square) cannot occur — there is no wash.

   ⚠ §28's defect 3 — the fixed corner dock owning the bottom-right of the viewport — is REAL and
   is handled by geometry, not by hope: the block is lifted off the frame's floor by
   `--sk-cut-lift` (7.4–8.4rem — above the dock STACK, which is the study tab 4.4rem up
   with the music dock beneath it, ~114px of viewport foot rather than the ~76px one tab
   suggests) and the RSVP pill is moved into
   the CAPTION row, at the top of the block, so the lowest thing in the bottom-right is a label.
   ⚠ Re-check `elementFromPoint` on the SEC label and the pill after any change to the lift, the
   figures' clamp, or the dock's own bottom offset.

   Position: the clock is back in the hero as a direct child, in §27.1's second column — the
   place it stood in rounds five and six, at the foot of the picture beside the names card. §28's
   `.band--count` section is gone from the markup; the class changed `--type` → `--cut`, which
   retires §28.1–28.3 and §28.6 the way §24.2, §25.4, §26 and §27 were retired — by no longer
   matching. §28.4's card shine is NOT class-gated and stays exactly as it is.

   ⚠ THE VISIBLE FIGURE IS `.a-odo`, NOT `.count-num` — the note in §27.4 and §28.2 still stands.
   The stroke has to be declared on `.a-odo-col > span` (the digit that is actually painted) as
   well as on `.a-odo` and `.count-num`, and `-webkit-text-fill-color` has to be set back to #fff
   because the previous registers clipped a gradient into the glyph with a transparent fill.
   ⚠ `.a-odo-win` is `overflow: hidden` with `min-width: .62ch`, which clips a 1.5px outward
   stroke on the first and last digit — hence the `min-width` bump here. Vertically 1.16em leaves
   ~4px of headroom at 54px, which the stroke fits inside; a heavier stroke would not.

   Knobs:
     --sk-cut-ink     the outline's colour and strength (near-black at 92%)
     --sk-cut-lift    how far the block sits above the frame's floor — dock clearance lives here
     --sk-cut-w       the block's drawn width in the second column

   ── 29.1 · placement ────────────────────────────────────────────────────────────────────── */
/* ⚠ ONE TOKEN SURVIVES §48's COLLAPSE, AND IT IS LIVE: §36.3 strokes the caption and the labels
   with `--sk-cut-ink`. `--sk-cut-lift` (the dock clearance of a register that no longer
   exists) and `--sk-cut-w` went with the rules — §36 has `--sk-corner-lift` and
   `--sk-corner-w`, measured for a cluster pinned `end`. 🚫 Never re-point §36 at a `--cut`
   number; that mistake has been made once already (see §36.1's comment). */
[data-skin="hanakenji"] {
  --sk-cut-ink: rgba(18, 15, 22, .92);
}

/* ── 29.4 · the phone: on the photograph too ──────────────────────────────────────────────
   The mobile hero is a column flex (§23.3) — picture band, then the names card — and round six
   left the clock in a band of its own BELOW that card, flush against the rail with no gutter of
   its own (`band--flush` plus §28.6's `padding-left: 0`), so at 390 the caption and the days
   figure ran into the rail's edge. It is now inside the hero at this width as well, laid over
   the bottom of the picture with the gutters the rest of the page uses.
   ⚠ ORDER, not source order: the markup is card → clock → picture, so the picture takes -2 and
   the clock -1. Equal order values would put the clock ahead of the picture (it is earlier in
   the source) and the overlay would land on paper.
   ⚠ The negative margin is what puts it on the photograph — NOT an absolute inset against a
   `48svh` expression, which is what §25's `.hero-gate` has to do and what has to be kept in step
   with the band's height by hand. In flow the overlap survives any change to the band.
   ⚠ These rules must beat §25.6's and §24.6's phone blocks, which reach this element through the
   bare `.hero-count` class — §25.6 hands it a top rule and 1rem of padding. The resets are
   deliberate; do not "simplify" them away.
   ⚠ The band is 54svh here (was 48): the block occupies the bottom ~9rem of the picture, and at
   48svh that ate into the couple. The two numbers move together — §25's `.hero-gate` reads the
   old expression and is not on this page. */
@media (max-width: 47.99em) {
  .hero--threshold .hero-bleed { order: -2; height: 54svh; min-height: 19rem; }
  .hero--threshold .hero-paper { margin-top: 1rem; }
}

/* ── 29.5 · it arrives with the page ─────────────────────────────────────────────────────── */


/* ═════════════════════════════════════════════════════════════════
   §30 · ROUND EIGHT (2026-09-07) — the phone hero becomes ONE picture, and the glass turns chrome

   Two notes. (1) The mobile hero looked wrong: the picture was a short band and the Hana & Kenji
   card sat on the ivory paper BELOW it — "the card has to be inside the hero photo, which should
   basically cover the hero section." (2) A trial: make the card read as shiny metal, semi
   transparent, and put the same metal on the countdown's own text and figures — with no card or
   backdrop of any kind behind the clock.

   Both are class-gated so the whole round reverts by editing markup, as every round since §24
   has been: `.hero-paper--chrome` on the card, `.hero-count--chrome` alongside `--cut` on the
   clock. Remove the two classes and §25.3/§28.4/§29 come back untouched.

   ⚠ 2026-09-07, later the same day: the metal was REVERTED on the note "revert back the metallic
   to glass." Both classes are off `index.html`, so §30.2, §30.3 and §30.4 are now INERT — kept as
   the record of a trial that was tried and declined, not as live style. §25.3's smoked glass card
   and §29's white cut clock are what ship. §30.1 is UNAFFECTED and still live: the phone hero is
   one photograph with the card and clock on top of it, which was the part the note kept.
   Do not re-add the classes without a note asking for metal again.

   ── 30.1 · the phone hero: the photograph IS the section ─────────────────────────────────
   §23.3 turned the hero into a column flex below 48em with the picture as a 48–54svh BAND, and
   §29.4 then pulled the clock up onto its foot with a negative margin. That is the layout the
   note rejects: two registers (picture, then paper) with the card in the paper one.

   It goes back to the desktop model at every width — the photograph absolutely covers the hero
   box, the card and the clock stand on its floor as grid rows — which is also why this block is
   mostly RESETS of §23.3 and §29.4 rather than new geometry. Both of those reach these elements
   at (0,2,0) and (0,1,0) with `!important` on §29.4's margin, so the resets are explicit and
   equally important. Do not "simplify" them away — that is the third time this file has said so.

   ⚠ THE DOCK CLEARANCE MOVES FROM THE ELEMENT TO THE SECTION. §29's `--sk-cut-lift` lifted the
   clock off the frame's floor above 48em; on the phone the lift is the hero's own padding-bottom,
   because both rows have to clear the dock stack (the study tab ~4.4rem up, the music dock under
   it — ~114px of viewport foot). clamp(7rem, 16svh, 8.4rem) is 112–134px. Re-check
   `elementFromPoint` on the SEC label after any change to it.
   ⚠ The couple stand at ~76% of the frame's height and the card now crosses that line. That is
   the trade the note asks for, and it is only survivable because the card is glass: they read
   THROUGH it. If the card is ever made opaque on the phone, this layout has to change with it. */
@media (max-width: 47.99em) {
  [data-skin="hanakenji"] .hero--threshold {
    display: grid; grid-template-columns: minmax(0, 1fr);
    align-content: end; row-gap: .85rem;
    min-height: min(92svh, 46rem);
    padding: 0 0 clamp(7rem, 16svh, 8.4rem);
  }
  .hero--threshold .hero-bleed {
    position: absolute; inset: 0; order: 0; z-index: 0;
    width: 100%; height: 100%; min-height: 0;
    object-fit: cover; object-position: 58% 46%;
  }
  .hero--threshold .hero-paper {
    grid-row: 1; order: 0;
    /* ⚠ `justify-self` is what sizes this, not the width. The desktop register sets
       `justify-self: start`, and start + `width: auto` makes a grid item FIT-CONTENT — the card
       shrink-wrapped to the "Hana and Kenji" string (265.7px) instead of filling the 276px its
       own margins leave, so it sat 10px short of the clock's right edge while their left edges
       agreed. It also made the card's width a function of the couple's names, which would resize
       it silently on any copy change. `stretch` is the fix and must stay. */
    justify-self: stretch;
    width: auto; margin: 0 var(--gutter);
    border-radius: calc(var(--radius) + 6px);
  }
}

/* ── 30.2 · the card, in chrome ───────────────────────────────────────────────────────────
   Metal is not a colour, it is an ANISOTROPIC gradient: bright at one edge, a dark shine line
   across the middle, bright again below it, with a fine brushed grain running one way. So the
   card keeps §25.3's smoked base — the contrast floor lives there — and gains three layers over
   it: a brushed grain, a chrome horizon, and a sharper rim.

   ⚠ The base smoke is UNCHANGED in strength (60 → 74 → 88% at the left, 66 → 90% stacked on the
   phone) and it still deepens toward the brighter side of the photograph. Every white-type
   measurement in §25.3's header is downstream of those stops; the metal only tints them cool
   (--sk-chrome-ink is sumi mixed 14% toward a steel blue) and adds highlight BANDS.
   ⚠ The bands are capped: +.16 white at the top edge where the type is headline-scale, +.12 at
   the foot, and the mid-card band — where `.hero-line` sits, body copy with no headline
   allowance — is the DARK one (16% near-black), so the card's worst cell for body copy gets
   darker, not lighter. Do not swap the horizon's direction without re-measuring that line.
   ⚠ These layers go in `background`, not on `::before`: §27.3's `::before` is at z-index -1,
   BEHIND the card's own background, so a highlight painted there is invisible. */
[data-skin="hanakenji"] {
  --sk-chrome-ink: color-mix(in srgb, var(--sumi) 86%, #8496ad);
  --sk-chrome-grain: rgba(255, 255, 255, .05);
}
[data-skin="hanakenji"] .hero--threshold .hero-paper--chrome {
  background:
    repeating-linear-gradient(101deg,
      var(--sk-chrome-grain) 0 1.5px, rgba(255, 255, 255, 0) 1.5px 6px),
    linear-gradient(184deg,
      rgba(232, 239, 248, .16) 0%, rgba(232, 239, 248, .05) 30%,
      rgba(14, 11, 18, .16) 51%, rgba(232, 239, 248, .11) 63%,
      rgba(232, 239, 248, .02) 88%, rgba(232, 239, 248, .12) 100%),
    linear-gradient(100deg,
      color-mix(in srgb, var(--sk-chrome-ink) 60%, transparent) 0%,
      color-mix(in srgb, var(--sk-chrome-ink) 74%, transparent) 46%,
      color-mix(in srgb, var(--sk-chrome-ink) 88%, transparent) 100%);
}
/* the cut edge: metal has a bright, hard rim rather than glass's soft one.
   ⚠ (0,2,0) MINIMUM on every block in §30.2 — §25.3 and §28.4 both target
   `.hero--threshold .hero-paper`, so a bare `.hero-paper--chrome` at (0,1,0) loses BOTH
   properties whatever the source order. §30.3's clock rules carried the skin prefix from the
   start; this block did not, and was inert until it did. */
[data-skin="hanakenji"] .hero--threshold .hero-paper--chrome {
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .66),
    inset 0 2px 0 rgba(255, 255, 255, .18),
    inset 1px 0 0 rgba(255, 255, 255, .22),
    inset -1px 0 0 rgba(232, 239, 248, .26),
    inset 0 -1px 0 rgba(255, 255, 255, .17),
    0 -26px 66px -32px rgba(20, 16, 24, .70),
    0 0 128px -24px var(--sk-glass-bloom);
}
@media (max-width: 47.99em) {
  [data-skin="hanakenji"] .hero--threshold .hero-paper--chrome {
    background:
      repeating-linear-gradient(101deg,
        var(--sk-chrome-grain) 0 1.5px, rgba(255, 255, 255, 0) 1.5px 6px),
      linear-gradient(184deg,
        rgba(232, 239, 248, .15) 0%, rgba(232, 239, 248, .04) 28%,
        rgba(14, 11, 18, .14) 50%, rgba(232, 239, 248, .10) 62%,
        rgba(232, 239, 248, .02) 88%, rgba(232, 239, 248, .11) 100%),
      linear-gradient(168deg,
        color-mix(in srgb, var(--sk-chrome-ink) 66%, transparent) 0%,
        color-mix(in srgb, var(--sk-chrome-ink) 80%, transparent) 62%,
        color-mix(in srgb, var(--sk-chrome-ink) 90%, transparent) 100%);
  }
}

/* ── 30.3 · the clock, cut from the same metal ────────────────────────────────────────────
   No card, no wash, no backdrop — the note is explicit, and §29's argument for why the clock can
   live on bare photograph still carries it. What changes is the recipe inside the glyph: §29's
   flat white fill becomes a chrome gradient, and §29's 3px `-webkit-text-stroke` is REPLACED by
   a drop-shadow halo.

   ⚠⚠ THE STROKE HAD TO GO, AND NOT FOR TASTE. A clipped gradient needs
   `-webkit-text-fill-color: transparent`, and a transparent fill inside a centred stroke leaves
   an outline-only letter (§29.2 says so from the other side). The dark edge is therefore drawn
   from the glyph's own composited alpha with three tight `drop-shadow`s — which read as an
   outline, ride the gradient, and cannot eat the counters the way 3px of stroke can at 12px.
   ⚠ `text-shadow` CANNOT be used for that halo. With `background-clip: text` the glyph's colour
   IS the element's background, and a text-shadow paints ABOVE the element background — the halo
   would cover the metal it is meant to sit behind. Hence `filter`, and hence `text-shadow: none`
   as an explicit reset of §29.2's. Do not put one back.
   ⚠ The filter goes on `.a-odo` and on `.count-num`, never inside `.a-odo-win` — it is
   `overflow: hidden` and crops anything drawn in there (§27.4). The GRADIENT, though, must be on
   `.a-odo-col > span`: a rolling column is ten glyphs tall, so one gradient across the column
   would shade the roll instead of the figure.
   ⚠ THE VISIBLE FIGURE IS `.a-odo`, NOT `.count-num`. Fourth round in a row this has mattered.

   Contrast, measured the same way §29.2 was (6 × 3 tiles over the block's footprint, frame
   composited at 58% cover): the metal's own range is #fff at the cap height down to a 52% shine
   line at #8d99a8 and back up, so the FILL alone is 1.3–6.4:1 on this ground — as bare white was.
   The halo is what carries it: near-black at .95 alpha, 1.4px in three directions, giving
   14.9–17.6:1 of glyph-against-its-own-edge everywhere, independent of the picture. Same
   complementary pair as §29: bright ground → the halo reads; dark ground → the metal reads.
   ⚠ The shine line is 4% of the glyph's height and never touches its outer edge. Widening it, or
   dropping it below #8d99a8, breaks the pair — the middle of the figure would then be neither
   light enough to read on the deck boards nor dark enough to read on the sand. */
/* ⚠ `--sk-chrome-halo` and `--sk-chrome-face` were removed by §48 with the chrome clock's
   rules — nothing read them once the register went. The CARD's `--sk-chrome-ink` and
   `--sk-chrome-grain` (§30.2) are untouched: that register is inert by markup, not deleted,
   and the metal verdict was "revert to glass", not "delete the record". */
/* the caption and the labels are 12–13px, so their halo is tighter and their metal is the top
   two thirds of the same gradient — the shine line at 50% would sit on the x-height and read as
   a smudge at that size. ⚠ Both stay FULL-strength metal: separated from the figures by size and
   tracking, never by opacity (DECISIONS.md). */
/* the RSVP pill keeps §29.3's solid cherry — it is a control, not a figure, and metal on a
   control reads as disabled. Its label is re-declared only because the rule above sets
   `-webkit-text-fill-color` on descendants of the block. */

/* ── 30.4 · print (INERT with the rest of §30.2 — see the header) ─────────────────────────
   The clock is already `display: none` in print (§24.6 + §29.5). The card has to drop its metal
   with its glass: §25.6 and §28.7 flatten it to white paper, and these two clear the layers
   those blocks do not name. */
@media print {
  [data-skin="hanakenji"] .hero--threshold .hero-paper--chrome {
    background: #fff !important; box-shadow: none !important;
  }
}


/* ═══════════════════════════════════════════════════
   §31 · STORY, CONVERTED (2026-09-07) — moves B · C · D · E on one page

   Home was judged (§§23–30) and the user's verdict on the interior pages came with it: **keep the
   split hero and the sumi ornaments — home stays the exception on purpose** — and **strongly
   varied**, each page with its own opening and rhythm. Story is the first page converted on those
   answers. Travel and Reply are NOT converted; they still wear the drawn cloth and the rail-foot
   dock, and §23.5's caution about the base `.demo-dock` still protects them.

   ── 31.1 · Story's score, as built ───────────────────────────────────────────────────────
     open        ivory   ← type only, no photograph. THE VARIATION: home opens on a 52rem
                           threshold picture, Story opens on a sentence. `hero-01` is NOT
                           dropped in here — DECISIONS.md 2026-09-06 (late) holds it as a spare
                           precisely because site 2's weight is front-loaded and an interior
                           hero flattens that axis.
     chapter 01  BLEED   chapter-01  ┐
     prose       ivory               │ §31.2, move D. Picture / prose / picture / prose — three
     chapter 02  BLEED   chapter-02  │ passages that alternate their corner, not three cards.
     prose       ivory               │
     chapter 03  BLEED   chapter-03  ┘
     prose       ivory
     chapter four  INK   ground-sumi-01   ← the one dark passage: a pause, not a close
     party       ivory
     gallery     raised
     close       BLEED   bleed-home-01    ← §31.3, the placement decided 2026-09-06 (late)
     footer      paper
   Four pages, four closes: home on ink, Story on a bleed, Travel on the Fuji scene, Reply on the
   form. One ink on ~5,300px, no two inks at all, and every bleed separated by paper — §23's rule
   holds without needing its "never two adjacent" clause.

   ── 31.2 · the ornament roster, and the one deliberate absence ───────────────────────────
   Gained: the photographed cloth (`.noren--plain`, §19.2 — markup only, the rules were already
   written and tested), the crest as a cut-out in the footer (`cut-kamon`, §19.8, replacing the
   drawn SVG that used to be printed on strip 3 of the drawn cloth), the three chapter frames,
   the `a-brush` seals, and the reveal token (§L2 + `reveal.js`).
   Kept: the petals, and the crane pair — §19.7's page-only sign-off, which moves ONTO the close's
   paper sheet (§31.4) rather than being retired.
   ⚠ **NOT carried: the torii.** §19.4's sumi gate frames a PLATE, and its geometry is a set of
   percentages of the plate's width with the lintel overhanging 10% each side. Story's last
   picture is now a full-bleed passage — a 120% crown on the page's own width would reach past
   `body`'s clip and be cut, which §19.4 forbids in three separate places. Story has no plate
   left to gate, so the sumi gate is TRAVEL'S alone. That is a variation, not a loss: §19's header
   says the four pages are deliberately not uniform, and Story's signature ornament is the cranes.
   Do not "restore" it by giving Story a plate back.

   ⚠ `story.js` IS NOT TOUCHED, and must not be. It fills two templates that live in the page, so
   the whole of move D is a template change plus the rules below. The one page-side edit is
   `site.js`: each chapter's `media` now points at `chapter-0N.jpg` (2400 × 1350, shot wide FOR
   this move) instead of `story-0N.jpg` (1600 × 1067, composed for the 370px card that move D
   retires). `story-01.jpg` still ships — home's onward card uses it; `story-02/03` are spares.

   ⚠ `story.js` ALSO SETS `data-reveal=""` ON EVERY CHAPTER, and that is not this file's driver.
   §L2's from-states are keyed on `[data-reveal="on"]` on <html>; a bare `data-reveal` on an
   article matches neither and hides nothing. The two mechanisms share a name and nothing else.
   Do not rename either — and do not add a skin rule keyed on the chapter's own `data-reveal`
   or `data-in`, or Story gets two drivers for one entrance.
   ═══════════════════════════════════════════════════════════════════ */

[data-skin="hanakenji"] { --sk-chapter-h: min(58vh, 30rem); }

/* ── 31.2 · the chapter as a passage (move D) ──────────────────────────────────────────────
   The defect the specimen sheet measured: a 370 × 247 photograph in column three, the same size
   in the same column three chapters running — specimens pinned beside the text. The move is two
   scales and no others: a photograph either bleeds the page or it is the 1014px plate.

   ⚠ 2026-09-08: the plate is now **90% paper with a 6px backdrop blur and one rounded outer
   corner** (user's note: soften the edges, blend it a little). It is still PAPER, not glass —
   the 10% is a blend, not a scrim, and everything below still holds. Do not read the translucency
   as licence to set type on the photograph itself.

   ⚠ THE TYPE DOES NOT GO ON THE PHOTOGRAPH, and this reverses the specimen sheet's own drawing
   of move D. The sheet showed the chapter type set on the picture behind a scrim; §23.2 then
   settled the general question the other way — white type on a gradient scrim is site 1's
   interlude verbatim and the one thing the differentiation charter names — and every bleed built
   since (§23.2, §23.3) sets its words on an opaque sheet of the page's own paper instead. A
   chapter is not an exception to that. So the chapter is §23.2's device with one addition: the
   plate carries the folio, the date, the title and the blurb, and the BODY PROSE drops out of it
   into a paper row underneath, at the page's own measure.
   Three things fall out of that, all of them wanted:
     · the arithmetic is trivial at every width — ink on paper, on a sheet that is never
       translucent, so no crop, no swap and no re-grade can ever put type on a photograph here;
     · the plate stays SHORT (a folio and three short lines, ~170px), so the picture keeps its
       subject at 390 as well as at 1440. The phone inversion §30.1 was written to fix cannot
       happen here, because the element that grows with the copy is not on the picture;
     · the frames' delivered faults stop mattering. `chapter-03`'s top 18% came back at
       luminance 129 and the batch notes say "the build owes that one frame a scrim" for its
       corner label. There is no corner label and no scrim: the folio is on the paper.

   ⚠ The alternation is `nth-of-type(even)`, so it is 01 left · 02 right · 03 left. That is what
   makes three passages a sequence rather than a repetition, and it costs one declaration.
   ⚠ (0,2,0) MINIMUM on every rule here, and (0,3,0) where it overrides `.chapter-shots img`
   — the retired register at §"story" is all descendant selectors, and it is one media query
   deeper as well (`@media (min-width: 56em)` puts the shot in column 3). §30.2's whole bug. */
[data-skin="hanakenji"] .chapter--frame {
  position: relative;
  display: grid; grid-template-columns: minmax(0, 1fr);
  gap: 0; padding: 0; border-top: 0;
  isolation: isolate;
}
[data-skin="hanakenji"] .chapter--frame > .chapter-shots {
  grid-area: 1 / 1; position: relative; z-index: 0;
  display: block; gap: 0;
  min-height: var(--sk-chapter-h);
  overflow: clip;
}
[data-skin="hanakenji"] .chapter--frame .chapter-shot {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; object-position: var(--sk-chapter-crop, 50% 46%);
  border-radius: 0;
  filter: var(--a-tone);
}
/* the paper plate, flush into one bottom corner of the picture — §23.2's sheet, at caption size */
[data-skin="hanakenji"] .chapter--frame > .chapter-plate {
  grid-area: 1 / 1; position: relative; z-index: 1;
  align-self: end; justify-self: var(--sk-frame-x, start);
  width: min(100%, 27rem);
  display: grid; gap: .3rem;
  padding: clamp(1.1rem, .9rem + 1.4vw, 1.8rem);
  /* 2026-09-08, user's note: soften the corners and let the paper blend a little into the
     picture. 90% ivory + a 6px backdrop blur — the plate reads as wet-laid paper on the frame
     rather than a pasted rectangle, and the ink stays safe: over the darkest ground in these
     three frames the mixed paper lands at ~L 85%, which is still ~10:1 against --ink. The 10%
     is the whole budget; a lower alpha crosses the line §31.2's header draws.
     ⚠ `backdrop-filter` is the ENHANCEMENT, never the mechanism — a browser that declines it
     shows 90% flat paper, which is the same reading. */
  background: color-mix(in srgb, var(--paper) 90%, transparent);
  backdrop-filter: blur(6px);
  border-radius: 3px 14px 3px 3px;
  box-shadow: 0 -20px 44px -28px rgba(43, 39, 51, .55);
}
/* the radius follows the corner: the OUTER top corner is the one that rounds, so the plate keeps
   its two page-edge corners square and reads as sitting IN the frame, not floating on it. */
[data-skin="hanakenji"] .chapter--frame:nth-of-type(even) > .chapter-plate {
  border-radius: 14px 3px 3px 3px;
}
[data-skin="hanakenji"] .chapter--frame:nth-of-type(even) { --sk-frame-x: end; }
/* ⚠ The folio is --accent-ink here and NOT §"story"'s --blossom. Blossom on paper is 1.4:1: it
   was a tint behind a column of body copy before, and on this plate it is the largest thing in
   the corner of a photograph. A chapter number is read, so it takes the same dark cherry as the
   date beneath it — separated from it by face and size (display at --step-3 against uppercase
   --ui at --step--1), never by colour alone. */
[data-skin="hanakenji"] .chapter--frame .chapter-no {
  color: var(--accent-ink); line-height: .9;
}
[data-skin="hanakenji"] .chapter--frame .chapter-blurb { margin-top: .2rem; }
/* the prose, on the page's own paper at the page's own measure */
[data-skin="hanakenji"] .chapter--frame > .chapter-body {
  grid-area: 2 / 1;
  width: min(100% - 2.6rem, 60rem); margin-inline: auto;
  padding-block: clamp(1.5rem, 1.1rem + 2vw, 2.6rem);
}
[data-skin="hanakenji"] .chapter--frame > .chapter-body > p { max-width: 62ch; }
/* ⚠ The last chapter's prose runs straight into the ink passage below it, so it keeps its own
   bottom padding; the FIRST chapter's picture butts the opening band with none. Both are the
   band's own rhythm (`.band--bleed` is `padding-block: 0`), not a per-chapter margin. */
@media (max-width: 47.99em) {
  [data-skin="hanakenji"] .chapter--frame { --sk-chapter-h: min(58vh, 24rem); }
  [data-skin="hanakenji"] .chapter--frame > .chapter-plate { width: 100%; }
}

/* ── 31.3 · the close ──────────────────────────────────────────────────────────────────────
   `bleed-home-01` (2400 × 1350, dusk sea and karst) lands here, per DECISIONS.md 2026-09-06
   (late): the folder's strongest photograph, on the page whose close was the quietest of the
   four. It is §23.2's `.frame` unmodified — `.frame--right` so the sheet sits in the END corner,
   which is the opposite corner to home's threshold and to chapters 01 and 03.
   The crop is 50% 56%: the horizon sits high in this frame and the sheet takes the bottom, so
   the water — not the sky — is what the sheet is flush against.
   ⚠ The old close was a `.plate--say .plate--torii` with `story-close.jpg`. That file is now
   unplaced on Story; it is still the Forever feature's chapter-01 frame, so it stays in the tree.

   ── 31.4 · the cranes, on the sheet ──────────────────────────────────────────────────────
   §19.7 hangs the pair centred under the closing plate, on ivory. There is no ivory under a
   bleed, so they move inside the sheet and sit above the eyebrow — left-aligned with the type,
   because a centred ornament over left-aligned copy reads as a divider. Still flat, still
   `aria-hidden`. ⚠ "Still blossom" was true until 2026-09-08 (evening) — §19.7's crane is a
   photograph now and carries its own cream. */
[data-skin="hanakenji"] .frame-say > .crane-pair {
  justify-content: flex-start; gap: 24px;
  margin: 0 0 .1rem;
}

/* ── 31.5 · what the reveal token changes on this page ─────────────────────────────────────
   `data-atelier` gains `reveal` and the page loads `reveal.js`, so Story's entrances run off the
   same driver home's do rather than off `animation-timeline: view()` alone. The vocabulary moved
   with it, and this is a MARKUP change with no rules of its own:
     · the gallery's figures were `.a-unveil` (view()-only, so silent in the browser the user
       reported) — they are `.a-mask` inside an `.a-order` now, which §L2 drives either way;
     · the chapter shot is `img.a-mask` — §L2 handles the bare-img form, so the template needs no
       wrapper figure. `story.js`'s `WSD.$('img', s) || s` accepts a template whose root IS the
       image, which is why the wrapper could go;
     · the seals gained `.a-brush`, matching home.
   ⚠ `.a-reveal` and `.a-unveil` have NO from-state under `[data-reveal="on"]` — they are
   view()-driven only. That is why swapping them for `.a-mask` was necessary and also why leaving
   one behind is harmless: an undriven token is a still element, never an invisible one.
   ⚠ The chapters are written by `story.js` after boot, so they are collected by `reveal.js`'s
   MutationObserver rather than at start-up. That path is load-bearing here — it is the same one
   home's seven schedule rows depend on. */


/* ═════════════════════════════════════════════════════════════════
   §32 · TRAVEL, CONVERTED (2026-09-08) — the same four moves, a different rhythm

   Travel takes the round-nine answers exactly as Story did, and lands somewhere else on purpose
   ("strongly varied — each page has a distinct opening and rhythm"):

     open        ivory   split hero + THE TORII PLATE  ← Travel's signature, and Story's absence
     every leg   ivory   the ruled guide
     the bay     BLEED   bleed-travel-01 + §23.2's paper sheet, corner START
     what to book raised  the four dates + venue-02 matted
     where to stay ivory  two columns
     the words    INK    band--sumi-02  ← the page's one dark passage, and it is a WHOLE SECTION
     the koi      edge   the pond
     close        paper  interlude-night plate, under the Fuji scene
   Four pages, four closes: home on ink, Story on a bleed, Travel on the Fuji scene, Reply on the
   form. One ink, one bleed, and paper between them.

   ⚠ **THIS SECTION HAS NO RULES, AND THAT IS THE RESULT.** Travel converted on markup alone:
   §19.2's photographed cloth, §19.8's crest cut-out, §23.1's ink tokens, §23.2's `.frame`,
   §L2's reveal vocabulary and the corner dock were all written and measured on home and Story.
   Nothing here needed a new declaration. If a future page needs one, that is the signal to check
   whether it is really a new device or a copy of one of these.

   ── 32.1 · why the ink passage is a whole section here ────────────────────────────────────
   Story's ink is a four-line pause ("Chapter four"). Travel's is *Words you will hear on the
   day* — a four-card glossary, the page's densest block, on deep sumi. That is the variation,
   and it is safe for one reason: **§23.1 is a token re-point, not a set of heading overrides.**
   `.word` reads `--card`, `.word-jp` reads `--ink`, `.word-say` reads `--accent-ink` and
   `.word p` reads `--muted`, so all four invert with the band — measured on sumi at
   --muted 8.5:1, --accent-ink (blossom) 8.0:1, and the card ground 10% paper in oklab.
   ⚠ `--accent` (cherry) stays cherry inside an ink band and is 3.2:1 there. Nothing in this
   section may put small type in it; §23.1's header carries the same warning.
   ⚠ `band--sumi-02`, not `-01`. Story already tiles 01, and the two grounds differ enough that
   using one twice would read as the same passage repeated across two pages.

   ── 32.2 · the torii stayed, and the geometry is why ──────────────────────────────────────
   §19.4's gate is a set of percentages of a PLATE's width with the lintel overhanging 10% each
   side. Travel still opens on `.plate--torii` — a 1014px plate inside the sheet — so the
   overhang has somewhere to go. Story's close is full-bleed and had nowhere, which is the whole
   reason the ornament did not travel there (`DECISIONS.md`, round nine). **The torii is now
   Travel's alone, and it is the strongest single argument for the varied-pages answer.**

   ── 32.3 · what moved, for the record ─────────────────────────────────────────────────────
   · the drawn `.noren` + inline kamon SVG → `.noren--plain` (photographed, five strips, no mark)
   · the footer's drawn kamon SVG → `.kamon-mark` (`cut-kamon.webp`)
   · the dock OUT of `.rail` → `.demo-dock--corner`, as home and Story (§23.5's caution about
     the base `.demo-dock` applied to exactly this markup; Reply is the last page still on it)
   · the rail: `rail-brand` with `brand-full`/`brand-short`, `rail-no` folios in `rail-here`
     (`rail-tick` retired here), `rail-gate` + `rail-rsvp-by` in `rail-foot`
   · `.a-reveal` / `.a-unveil` → `.a-order` + `.a-rise` / `.a-mask`. ⚠ The guide, the dates and
     the words were `.a-reveal`, which has NO §L2 from-state — they were the silent blocks the
     user reported. Each is now an `.a-order` whose rows are `.a-rise`, so the row arrives with
     an authored per-child offset.
   · `data-atelier` gains `reveal`; `reveal.js` loads after `motion.js`
   · placed: `bleed-travel-01` (the bay, at sea level — deliberately not `travel-01`'s aerial)
     and `venue-02` (the lantern garden by day), the last of the two held frames
   ⚠ `engine/pond.js` is untouched, and the `.pond` canvas keeps `.a-rise`: §L2 moves and fades,
   never resizes, so the 44px+ canvas target the gate measures is unaffected.
   ═════════════════════════════════════════════════════════════════ */


/* ═════════════════════════════════════════════════════════════════
   §33 · TRAVEL'S TWO INTERACTIVE PICKS (2026-09-08) — the route map, and the at-a-glance

   Two of the user's four remaining picks. Placement was delegated to us ("decide for me on the
   travel section"), and both answers are about what a block is NEXT TO rather than how it looks.

   ── 33.1 · the map sits INSIDE the guide, above the rows ──────────────────────────────────
   The map answers "where is any of this", which is the question §32's guide silently assumes
   from its very first row ("into the country"). Above the rows the two read as one object —
   picture, then legend — and the rows caption the map for free. After the bleed it would have
   restarted the page: the bleed is Travel's emotional pivot (*the reason for the two flights*),
   and a scale diagram immediately after it is a second opening.

   ⚠ NO NEW MODULE WAS WRITTEN. This is `engine/venuemap.js` — built for site 1 at the scale of
   one walled city — mounted at the scale of an archipelago. The ONE engine change is its
   nice-number ladder: `NICE` stopped at 2 km, so a 590 km box asked for a 2 km grid and the
   loop drew ~660 lines. Six values were appended (5 km … 500 km). No projection, markup or
   contract changed, and site 1's map is untouched — its extents still pick 2 km or less.

   ⚠ Every landmark is real and at its published coordinates; every distance on screen is
   COMPUTED by haversine in the engine, never typed. The two Casa Marikit venues carry invented
   coordinates on a real beach and are flagged `invented` in the data, which is what puts the
   cherry rule on the detail panel.
   ⚠ `garden` is on NEITHER extent. It is 50 m from `pavilion` — one pixel at either scale — and
   `DECISIONS.md`'s *two labels may not share the same square metre* holds across extents too.
   ⚠ The label is capped at 5.5rem and centred on its dot, so at 390 (plot ≈ 340px) a label
   reaches 44px either side of the mark inside the projection's own 18% of empty padding. That
   is why the plot is NOT clipped: nothing needs to be, and clipping would eat a label whole.
   ⚠ The aspect clamp is the DEFAULT 0.82 on purpose. The journey's true box is 0.605 (Manila to
   Puerto Princesa is far more north-south than east-west), and letting it stay that tall left a
   324px-wide map in a 560px column at 1440 — the padding the engine adds to reach 0.82 is empty
   ground, so the projection is not distorted by widening it, only better company for the rows.

   ── 33.2 · the at-a-glance sits inside "where to stay", not in its own band ───────────────
   The destination info-design (money, timing, power, the small plane) is practical copy, and
   §32's score has no room for a fourth ivory band before the ink passage without flattening the
   page. It lands under `.cols` in `#stay`, where the four practical bullets already were — it
   **replaces** them rather than lengthening the page: six measured cells instead of a list, and
   the one line that did not fit a cell (phone signal) moved into the column beside it.
   ⚠ Nothing in it is a price for this wedding. The single figure that touches money is the
   public arrival fee, written as an approximation, and the block's closing line says so.

   ── 33.3 · what is deliberately absent ───────────────────────────────────────────────────
   No coastline, no roads, no runway, no tiles. `venuemap.js` refuses to draw geography it
   cannot source offline, and an invented coastline on the page about *getting here* is worse
   than an empty ground. The empty ground is the honest drawing.
   ═════════════════════════════════════════════════════════════════ */

.vmap { display: grid; gap: var(--gap); }
.vmap-head { display: flex; flex-wrap: wrap; align-items: end; gap: .7rem var(--gutter); }
.vmap-head .eyebrow { margin: 0; }
.vmap-body { display: grid; gap: var(--gap); }
@media (min-width: 62em) {
  .vmap-body { grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr); align-items: start; }
}

.vmap-plot {
  position: relative;
  /* the engine writes the projected box's ratio into --vmap-a; capping the WIDTH by it is the
     only way to bound the height without distorting the projection. */
  width: min(100%, calc(72vh * var(--vmap-a, 1)));
  margin-inline: auto;
  aspect-ratio: 1;                    /* the engine overwrites this */
  background: var(--bg-raised);
  /* ⚠ the plot is a RAISED ground inside an ivory band, so it carries §23.1's raised-ground
     correction itself: measured on it, `--faint` is 4.28:1 (the documented failure) and
     `--muted` is 5.44:1. Re-pointed exactly as `.band--raised` does — a token, never the
     scale bar's own colour, or the next mark added to the plot inherits the failure. */
  --faint: color-mix(in oklab, var(--sumi) 66%, var(--paper));
  border: var(--rule-w) solid var(--rule-strong);
  border-radius: var(--radius);
}
.vmap-lines { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.vmap-lines .vmap-grid { stroke: var(--rule); stroke-width: 1; }
.vmap-lines .vmap-line { stroke: var(--accent); stroke-width: 2; stroke-dasharray: 6 5; }
.vmap-marks { position: absolute; inset: 0; }

.map-mark {
  position: absolute; transform: translate(-50%, -50%);
  width: 44px; height: 44px; padding: 0;
  display: grid; place-items: center;
  background: none; border: 0; cursor: pointer;
}
.map-dot {
  width: 12px; height: 12px; border-radius: 50%;
  background: var(--accent-ink);
  box-shadow: 0 0 0 4px var(--bg-raised);
}
.map-mark[data-kind="airport"] .map-dot { border-radius: 2px; background: var(--ink); }
.map-mark[data-kind="island"] .map-dot { background: var(--bg-raised); border: 2px solid var(--rule-strong); }
.map-mark[data-invented] .map-dot { background: var(--accent); }
.map-label {
  position: absolute; top: calc(50% + 10px); left: 50%; transform: translateX(-50%);
  width: max-content; max-width: 5.5rem; text-align: center; text-wrap: balance;
  font: 600 var(--step--1)/1.25 var(--ui); letter-spacing: .08em; text-transform: uppercase;
  color: var(--muted);
}
.map-mark:hover .map-label,
.map-mark:focus-visible .map-label,
.map-mark[data-selected] .map-label { color: var(--accent-ink); }
/* On the `lio` extent the airport and the venue are 1.3 km apart — 5.6% / 9.2% of the box, or
   about 30px — so their two labels met. The venue's label is hung ABOVE its dot instead of
   below, which separates the pair by the height of a label without moving either mark. */
.map-mark[data-kind="venue"] .map-label { top: auto; bottom: calc(50% + 10px); }
.map-mark[data-selected] .map-dot { box-shadow: 0 0 0 4px var(--bg-raised), 0 0 0 7px var(--accent); }

.vmap-dist {
  position: absolute; transform: translate(-50%, -50%); margin: 0;
  padding: .2em .5em; background: var(--bg-raised);
  font: 600 var(--step--1)/1 var(--ui); letter-spacing: .1em; color: var(--accent-ink);
}
.vmap-north {
  position: absolute; top: .6rem; right: .8rem; margin: 0; text-align: center;
  font: 600 var(--step--1)/1.1 var(--ui); letter-spacing: .1em; color: var(--faint);
}
.vmap-north::before { content: "\2191"; display: block; font-size: 1.2em; }
.vmap-scale {
  position: absolute; bottom: .7rem; left: .9rem; margin: 0;
  display: flex; align-items: center; gap: .5rem;
}
.vmap-bar { display: block; height: 6px; min-width: 12px; border: 1px solid var(--ink); border-top: 0; }
.vmap-bar-label { font: 600 var(--step--1)/1 var(--ui); letter-spacing: .1em; color: var(--faint); }

.vmap-side { display: grid; gap: var(--gap); align-content: start; }
.vmap-detail { display: grid; gap: .35rem; padding-top: .9rem; border-top: 2px solid var(--ink); }
[data-skin="hanakenji"] .vmap-detail h3 { font: 400 var(--step-2)/1.15 var(--display); }
.vmap-detail .meta { letter-spacing: .14em; text-transform: uppercase; }
.vmap-detail p { color: var(--muted); }
.vmap-detail[data-invented] { border-top-color: var(--accent); }
/* the connector's origin has no distance to itself, and the engine writes '' rather than
   inventing one — so the sentence is removed instead of being left half-written. */
.vmap-detail:has(> .vmap-away > b:empty) .vmap-away { display: none; }
.vmap-away b { color: var(--accent-ink); }
.vmap-legend { font: var(--step--1)/1.7 var(--ui); color: var(--muted); max-width: 40em; }
.vmap-legend strong { color: var(--ink); }

/* ---- the at-a-glance (§33.2) ----------------------------------------------
   A ruled ledger of six, not cards: the page already has a ruled guide (four columns) and the
   dates (label + block rows), so this one is a GRID of measured cells and reads as neither. */
.atlas-head { display: grid; gap: .3rem; margin-top: var(--gap); }
[data-skin="hanakenji"] .atlas-head h3 { font: 400 var(--step-2)/1.15 var(--display); }
.atlas { display: grid; border-top: 2px solid var(--ink); }
@media (min-width: 40em) { .atlas { grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr)); } }
/* D3 (design read, site 2): six cells must never sit in four tracks — the second ruled row then
   runs two cells and a few hundred px of empty rule (480px at 1440, ~434px at 924).
   ⚠ The threshold is NOT a nice round breakpoint and NOT 64em: `auto-fit` above fits a fourth
   track the moment the ATLAS is 4 × 13rem = 832px, and in this band the atlas runs about 57px
   narrower than the viewport — so four tracks first fit at ~889px, well below the rail register.
   The override is pinned at 53em (848px) to sit under that: at 848 the grid already resolves to
   three tracks naturally (atlas ~791), so this changes nothing below ~889 and forecloses the
   fourth track everywhere above it. 🚫 Do not "tidy" this to 64em — that was the round-1 guess and
   it left 889–1023 broken. */
@media (min-width: 53em) { .atlas { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.atlas-cell {
  display: grid; gap: .25rem; align-content: start;
  padding: var(--gap) var(--gutter) var(--gap) 0;
  border-bottom: var(--rule-w) solid var(--rule);
}
[data-skin="hanakenji"] .atlas-val {
  font: 400 var(--step-3)/1 var(--display); font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.atlas-key { font: 600 var(--step--1)/1.3 var(--ui); letter-spacing: .14em; text-transform: uppercase; color: var(--accent-ink); }
.atlas-cell p:last-child { font: var(--step--1)/1.65 var(--ui); color: var(--muted); }


/* ═════════════════════════════════════════════════════════════════
   §34 · ROUND TWELVE (2026-09-08) — THE MOBILE PASS

   Site 2's four pages were reviewed at 390 for the first time (CHANGELOG, round twelve). The
   probe had them clean at four widths and they are: this section is not a defect sweep, it is
   the phone's own proportions. Every block is width-gated, so the desktop registers that were
   signed off at 1440 are untouched.

   ⚠ HOW THE MEASUREMENTS WERE TAKEN, because it changes what is trustworthy. The rig
   (tools/mob-sim.js) renders the page in a 390px container-query box, and 'vh' / 'svh' in it
   resolve against the RIG's viewport, not a phone's. Three "findings" from the first pass were
   that artifact and are NOT fixed here, because there was nothing wrong: the bleeds are
   min(80vh, 32rem) = 512px on a real 844-tall phone (not the 432 the rig showed), and the
   chapter frames and hero were mis-sized the same way. Everything below is arithmetic done at
   390 x 844, the real thing.

   ── 34.1 · the hero: the couple come back into frame, and the clock gets a ground ─────────
   TWO defects, one of them the user's own note ("the couple are cropped out").

   (1) THE CROP. hero-wide.jpg is 2800x1575 and the couple stand at x 62-73%. §30.1 cropped the
   phone hero at 58% 46%, which was measured against the rig. On a real phone the hero box is
   min(92svh, 46rem) = 736px tall, so cover scales the picture to 1309px wide and the 390px
   window sees only 29.8% of it: at 58% that window is x 40.7-70.5% and it cuts the man in half
   at its right edge. At 72% it is x 50.6-80.4% — both of them, with the karst still behind.
   ⚠ Do not put this back toward centre without redoing that arithmetic. 58% is not "nearly
   right"; it is a different photograph.
   ⚠ The vertical stays 50%: the box is portrait and the picture is 16:9, so cover crops the
   WIDTH only and the vertical value does nothing at this width. It is kept for the tablet band.
   ⚠ §23.3's warning still holds and is why the horizontal moved and nothing else: the vertical
   date is set in white at the top-left, and at 72% the window's left edge is the pavilion
   ceiling — mid-dark, which is the ground that date needs. Re-check it if this number moves.

   (2) THE CLOCK'S GROUND. §29 put the cut clock on the bare photograph and measured it there;
   §30.1 then moved it to the phone hero's bottom row, which on this crop is the sunlit sand.
   The audit measured the 13px cap and the 32px figures at 1.08:1 — every ancestor up to <main>
   is transparent, so there was no ground under them at all. This is the one measured contrast
   failure on site 2.

   The fix is the scrim the user chose: dark, rising from the photograph's bottom edge, under
   both rows. It is NOT §28's rejected wash — that one covered 98% of the picture's width at
   0.74 and veiled the couple, the sand and the karst. This one is bottom-anchored and finishes
   its falloff at 66% of the frame's height, and its PLATEAU is placed where the type is rather
   than at the floor: on a real phone the hero is 736px with 134px of dock clearance under it, so
   the clock's two rows sit 18-33% up from the bottom edge — 27-50% of the scrim's own height,
   which is where the .72 plateau runs. Above 62% it is already falling away, so the couple's
   heads (~50% of the picture) keep their own tone.
   ⚠ Do not tune these stops in the 390 rig. Its hero is 497px, not 736, because svh resolves
   against the rig's viewport — the plateau lands in the wrong place there by ~15% and looks
   weak. The arithmetic above is 390 x 844.
   ⚠ It must finish INSIDE the frame. `.hero--threshold` computes `overflow: clip`, so a wash
   still carrying alpha at its top edge is cut off square — the exact rectangle a feathered
   scrim exists to avoid. Hence `transparent` at 100% of a 56% band, not a full-height gradient.
   ⚠ z-index 1 is load-bearing: the picture is 0, the glass card is 2. A scrim at 3 would sit on
   the card and turn the glass grey.
   ⚠ Measured after: the cap is 8.1:1 and the figures 11.4:1 against the scrimmed sand, and the
   couple's own tonal range is unchanged above 56%. */
/* ⚠ ROUND FOURTEEN (2026-09-08, evening) RETIRED (1). `hero-tall.jpg` landed — 1600 x 2000,
   the same scene composed portrait — and home's hero now serves it through a <picture> source
   at this width (§35.1). The couple are CENTRED in that frame, so the crop is 50% 50% and the
   arithmetic above is history rather than instruction: it is kept because the 736px box, the
   29.8% window and the scrim's plateau are all still the numbers this hero is measured at.
   ⚠ 72% is now WRONG, not merely superseded: on the portrait frame it walks the window off the
   couple in the other direction. Do not restore it with the landscape file. */
@media (max-width: 47.99em) {
  .hero--threshold .hero-bleed { object-position: 50% 50%; }
  .hero--threshold::after {
    content: ""; position: absolute; inset: auto 0 0 0; z-index: 1;
    height: 66%; pointer-events: none;
    background: linear-gradient(to top,
      rgba(18, 15, 22, .60) 0%,
      rgba(18, 15, 22, .72) 14%,
      rgba(18, 15, 22, .72) 46%,
      rgba(18, 15, 22, .44) 62%,
      rgba(18, 15, 22, .16) 82%,
      rgba(18, 15, 22, 0) 100%);
  }
}
@media print { .hero--threshold::after { display: none !important; } }

/* ── 34.2 · the plate credit gets leading ─────────────────────────────────────────────────
   `line-height: 1` was safe while every credit was one line, which is all any of them ever got
   at 1440. At 390 home's letterpress credit wraps to two lines and Travel's garden credit to
   three — letterspaced uppercase, right-aligned, with the lines touching. Leading and a left
   edge, at the width where they wrap; the desktop setting is untouched. */
@media (max-width: 47.99em) {
  .plate-credit { line-height: 1.45; text-align: left; }
}

/* ── 34.3 · Story's chapter frames ────────────────────────────────────────────────────────
   The frame was min(58vh, 24rem) = 384px on a phone and the plate over its foot is 194px of
   that, leaving ~190px of photograph — a letterbox under a caption card. The picture is the
   chapter; it gets min(66vh, 27rem) = 432px, and the plate loses a little of its own padding,
   so ~250px of image survives.
   ⚠ The plate stays 90% paper. Making it opaque on the phone is the other way to solve this and
   it is the wrong one — §31.2's 10% is the whole translucency budget, and the user was asked and
   chose the taller frame instead. */
@media (max-width: 47.99em) {
  [data-skin="hanakenji"] .chapter--frame { --sk-chapter-h: min(66vh, 27rem); }
  [data-skin="hanakenji"] .chapter--frame > .chapter-plate { padding: 1rem 1.1rem 1.05rem; }
}

/* ── 34.4 · the map's bottom-left corner ──────────────────────────────────────────────────
   `Puerto Princesa` is the one label on the plot that wraps to two lines, and at 390 its second
   line runs into the scale bar's `100 km`. The bar moves to the other corner: it is a legend,
   it has no position of its own, and the mark cannot move without lying about geography.
   ⚠ The north arrow is top-right, so the bar goes bottom-right and the two do not meet. */
@media (max-width: 47.99em) {
  .vmap-scale { left: auto; right: .9rem; }
}

/* ── 34.5 · the wedding party stops being six cards ───────────────────────────────────────
   Six ivory cards on ivory, one per row, 1092px of a single rhythm — and the hairline that is
   supposed to separate them is 1px of --rule on --card, which at phone size reads as nothing.
   They become ruled rows: the card, its border and its padding go, a 1px rule opens each row,
   and the initials chip drops to the cap-height of the name it sits beside instead of being a
   2.9rem disc above it. Same content, half the height, and the rhythm now comes from the type.
   ⚠ NOT two-up. At 390 a two-column card is 168px — 22 characters — and every one of these
   rows carries a sentence. The column count was the obvious fix and it is the wrong one. */
@media (max-width: 47.99em) {
  .party-grid { gap: 0; grid-template-columns: minmax(0, 1fr); }
  .party-card {
    grid-template-columns: auto minmax(0, 1fr);
    align-items: baseline;
    gap: .25rem .7rem;
    background: none; border: 0; border-radius: 0;
    border-top: var(--rule-w) solid var(--rule);
    padding: .95rem 0 1.05rem;
  }
  .party-card .pav {
    grid-row: 1; grid-column: 1;
    width: 1.9rem; height: 1.9rem; margin: 0;
    font-size: .62rem; letter-spacing: .06em;
    align-self: center;
  }
  .party-card b { grid-row: 1; grid-column: 2; }
  .party-card .role { grid-row: 2; grid-column: 2; }
  .party-card p { grid-row: 3; grid-column: 2; margin-top: .15rem; }
}

/* ── 34.6 · the gallery keeps two columns on a phone ──────────────────────────────────────
   The wall is a two-column CSS-columns layout that fell to ONE below 34em, which at 390 makes
   four full-width frames — 559px, 249px, 559px, 249px — of the same gesture, 1.7k of scroll.
   Two columns at 390 gives 171px frames: the portrait ones read at 256px tall, the landscape
   ones at 114px, and the wall reads as a wall again.
   ⚠ Columns, not a grid, and the reason is written at the gallery's own rules: a grid reserves
   a track it cannot fill and row flow cannot backfill. Nothing here cover-crops either — that
   is what cut the raised hand out of gallery-02 in September. */
@media (max-width: 34em) {
  .gallery { column-count: 2; column-gap: .6rem; }
  .gallery figure { margin-bottom: .6rem; }
}

/* ── 34.7 · the Fuji close, at phone scale ────────────────────────────────────────────────
   The scene is five CSS shapes, and below 40em it was scaled by height alone: a 92px mountain
   under a 34px sun with two 10px cloud bars, which at 390 reads as loose slabs rather than a
   horizon. The sun tucks in behind the peak's right shoulder (it is a rising disc, not a
   floating dot), the clouds shorten and sit lower so they read as banks rather than rules, and
   the scene gains 18px so the silhouette has somewhere to be.
   ⚠ .fuji-scene keeps its -10px bottom margin: the mountain's base is meant to run into the
   band below it, not stop above it. */
@media (max-width: 39.99em) {
  .fuji-scene { height: 150px; }
  .fuji { width: min(316px, 84vw); height: 104px; }
  .fuji-cap { bottom: 74px; width: min(80px, 20vw); height: 26px; }
  .fuji-sun {
    bottom: 66px; left: min(calc(50% + 96px), calc(100% - 3.4rem));
    width: 30px; height: 30px;
  }
  .cloud { height: 8px; }
  .cloud.c1 { width: min(104px, 27vw); left: 8%; bottom: 44px; box-shadow: 24px -11px 0 -1px color-mix(in oklab, var(--sumi) 9%, var(--bg)); }
  .cloud.c2 { width: min(78px, 21vw); right: 9%; bottom: 92px; box-shadow: -20px 10px 0 -2px color-mix(in oklab, var(--sumi) 9%, var(--bg)); }
}

/* ── 34.8 · Reply's two choices become a pair ─────────────────────────────────────────────
   180px and 176px, stacked left — near-equal, which reads as an accident rather than a pair.
   They go full-width and equal. Stacked rather than 50/50 because "Yes, I'll be there" wraps
   inside a 168px pill and the yes/no on this page is the one tap the whole demo is about. */
@media (max-width: 47.99em) {
  .choices--big { display: grid; grid-template-columns: minmax(0, 1fr); gap: .6rem; }
  .choices--big .choice { width: 100%; }
  .choices--big .choice span { width: 100%; min-width: 0; }
}

/* ── 34.9 · the noren, with a drape and its crest, at phone size ──────────────────────────
   The three interior pages hang `.noren--plain`: an uncrested weave, cropped rather than scaled
   (its own rules say why). At 1440 that is five panels of cloth reading as cloth. At 390 each
   panel is 61px and the whole thing reads as five flat rectangles — there is not enough of it
   left for the weave to carry.

   Two changes, phone only. (1) It hangs the CRESTED cloth instead: the mark is a circle centred
   on the middle panel and at 61px panels it lands exactly where home's does, which is the one
   width where the crest is worth more than the weave. (2) Each panel gets fold shading — a dark
   edge inside both sides and a slightly heavier hem — so the five panels read as one cloth
   gathered on a rod rather than as a row of boxes.
   ⚠ The sprite is 1600x500 at `background-size: 500% 100%`, five panels of 320px. Do not change
   the size here: the panel positions (0/25/50/75/100%) are keyed to it.
   ⚠ The insets are on the panels, never on `.noren` — a shadow on the container would draw
   across the gaps between the strips, which are where the wall shows through. */
@media (max-width: 47.99em) {
  .noren--plain i {
    background-image: url("../assets/hanakenji/noren-crest.png");
    background-size: 500% 100%;
  }
  .noren i {
    box-shadow:
      inset 5px 0 10px -7px rgba(16, 13, 20, .55),
      inset -5px 0 10px -7px rgba(16, 13, 20, .55),
      inset 0 -7px 12px -8px rgba(16, 13, 20, .5);
  }
}

/* ── 34.10 · two new ornaments: the kumo rule and the brush stroke ────────────────────────
   §19's ornament set is deliberately NOT uniform across the four pages, and its header says so
   in order to stop a later pass "finishing" the job. This is not that pass: the phone shows one
   column with no side margin to carry rhythm, so the band-to-band joins are the only place left
   for it, and both of these are joins rather than decoration on a surface.

   THE KUMO RULE — three stylised cloud banks, narrowing upward, centred on the seam between two
   paper bands. Drawn, not photographed, and flat: it is a rule, and a rule with a shadow reads
   as an object. It inherits its ink from the band it sits in, so it needs no ink-band variant
   beyond the one re-point below.

   THE BRUSH STROKE — one tapered mark opening an ink passage, in the pale blossom the ink bands
   already set their eyebrows in. The taper is a clip-path rather than a gradient because a
   gradient's ends fade to the ground and a brush's ends do not: they thin and lift.
   ⚠ Both are aria-hidden ornaments in markup and both are display: none in print, with the rest
   of §19's set. */
/* ⚠ ROUND FOURTEEN (2026-09-08, evening): BOTH OF THESE ARE NOW PAINT, and the geometry below
   is gone rather than overridden. `cut-kumo-bank.webp` (900 x 420) and `cut-brush-stroke.webp`
   (1200 x 220) landed, and each one is carried as a **MASK** on the element that used to be the
   drawing — background-color: currentColor, mask-image: the cut-out. That is site 1's §19 render
   mode and it is the only route that keeps what §34.10 was built on: both of these inherit their
   ink from the band they sit in, so both need to recolour with one `color` declaration. A plain
   <img> cannot, and an ink-band variant of a grey wash is invisible on near-black.
   ⚠ The wash's own tonality SURVIVES the mask (alpha carries it), which is why the two colour
   values below are stronger than the drawn versions': the mask multiplies them down.
   ⚠ Alpha under 20/255 is clipped to zero in the delivered files — site 1's lesson, and the kumo
   arrived with 103,891 pixels of removal matte, which as a mask paints a tinted 900px box. */
.kumo {
  display: flex; justify-content: center;
  padding-block: clamp(1.4rem, 4vw, 2.4rem);
  color: color-mix(in oklab, var(--sumi) 34%, var(--bg));
}
/* ONE bank, not three. The drawn version stacked three narrowing bars because a bar is free;
   the smallest was 38px, which is a third of §19's 92px painted floor and would have read as a
   speck the moment it became paint. The ask's own ceiling is one bank at 108px minimum. */
.kumo i {
  display: block;
  width: clamp(132px, 34vw, 190px); aspect-ratio: 900 / 420;
  background-color: currentColor;
  -webkit-mask: url("../assets/hanakenji/cut/cut-kumo-bank.webp") center / contain no-repeat;
  mask: url("../assets/hanakenji/cut/cut-kumo-bank.webp") center / contain no-repeat;
}
.band--ink .kumo { color: color-mix(in oklab, var(--blossom-pale) 62%, transparent); }

/* 1200 x 220 at 148px wide is 27.1px tall — the height below is that arithmetic, not a taste.
   The stroke is anchored LEFT because it opens a left-aligned eyebrow and the mark's loaded end
   is its left end; centring it would hang the taper's thin tail under the type. */
.brush-rule {
  display: block; width: 148px; height: 27px; margin: 1.1rem 0 0;
  background-color: currentColor;
  color: color-mix(in oklab, var(--blossom-pale) 78%, transparent);
  -webkit-mask: url("../assets/hanakenji/cut/cut-brush-stroke.webp") left center / contain no-repeat;
  mask: url("../assets/hanakenji/cut/cut-brush-stroke.webp") left center / contain no-repeat;
}
.band--raised .brush-rule,
.band:not(.band--ink) .brush-rule { color: color-mix(in oklab, var(--accent-ink) 44%, transparent); }

/* ⚠ ROUND FOURTEEN RETIRED the colour re-point that used to live here. The crane is a
   PHOTOGRAPH now (§35.4) and `color` reaches nothing inside it — the paper's own cream and its
   gold branch print carry the read, and a cast shadow is what separates it from an ivory band.
   It is masked NOWHERE, deliberately: the fold shadow is the whole reason to photograph a crane,
   and a mask throws exactly that away. See §35.4 for why the two ornaments went opposite ways. */

@media print { .kumo, .brush-rule { display: none !important; } }


/* ══════════════════════════════════════════════════════════════
   §35 · ROUND FOURTEEN (2026-09-08, evening) — THE PORTRAIT FRAMES LAND

   Seven files arrived: three portrait photographs and four ornaments. §34 was the pass that
   could be done with CSS alone and it said so at every point where the answer was really a
   photograph. This is the other half, and MOST OF IT IS DELETION — §34.1's crop arithmetic,
   §34.10's two drawings and §19.7's five polygons are gone, not overridden. The ornaments are
   handled where they already lived (§19.7, §34.10); what is left below is the imagery.

   ⚠ What did NOT change, and the reason, so it is not re-proposed: the scrim (§34.1's second
   defect is contrast, not composition, and the portrait frame's foot is still sand under a glass
   card), Story's chapter frames (they cover-crop inside a fixed box — same originals, taller
   window), and the Fuji close (five CSS shapes, under the painted floor by construction).

   ── 35.1 · home's hero, in portrait ─────────────────────────────────────────────
   `hero-tall.jpg`, 1600 x 2000, served by a <picture> source at this width. The arithmetic that
   matters is the WINDOW: the phone hero is min(92svh, 46rem) = 736px tall at 390 wide, and a 4:5
   picture covering that box scales to 588.8px wide, so the window sees 66.2% of the frame,
   centred at x 16.9-83.1%. The couple were briefed centred and land inside it whole. That is the
   whole reason §34.1's 72% is retired rather than re-tuned — there is nothing left to tune.
   ⚠ The three overlay geometries the hero carries are all UNMOVED and all still correct against
   this frame: the scrim (bottom-anchored, 66% band — measured against the CLOCK, not the
   picture), the glass card and the clock. Home has no `.hero-gate`; the waterline anchoring in
   §24.6 is Travel's and §19.4's warning about swapping the hero does not reach this file.
   ⚠ The vertical date is set in white over the window's left edge, which on this frame is dark
   teak doorframe for the first ~25% — briefed at 12% and delivered wider. Re-check it if the
   source is ever swapped again; it is the one thing in the hero a crop can silently break.
   ⚠ `hero-wide.jpg` STAYS the source above 48em and is not superseded. Two frames, two widths.

   ── 35.2 · Travel's opener stops being a strip ────────────────────────────────────
   `travel-01-portrait.jpg` at this width. `travel-01.jpg` is 1600 x 900 and rendered 348 x 196 —
   a 196px strip, and the only picture on Travel above the fold. The portrait frame renders
   348 x 435 in the same column.
   ⚠ The aspect override is NOT belt-and-braces. A <picture> whose <img> carries width/height
   attributes inherits THOSE as its aspect-ratio in the UA sheet, so the portrait source would be
   squashed into 16:9. Both the <source>'s own width/height and this rule exist for that; keep
   both, because the source attributes are honoured unevenly.
   ⚠ §26's gate survives the change by construction and this is worth knowing before touching
   either: every number in it is a `cqw` — a percentage of the frame's WIDTH — so the crown still
   lands at 24.4cqw and the posts still run `bottom: 0`. A portrait picture makes the gate TALLER,
   which is the direction a torii wants to go. Nothing in §19.4/§26 needed re-deriving.

   ── 35.3 · Story's close, and a phone crop that stops overwriting the desktop one ───────
   `bleed-story-tall.jpg` (1800 x 2200) at this width. In the phone's 390 x 512 frame it covers by
   height and loses 7% of its width — essentially the whole photograph, with the near island in
   the middle third and the type's foot on quiet wet sand.

   AND A DEFECT §34 LEFT BEHIND. Round thirteen re-cropped this figure to `32% 52%` on its INLINE
   `--sk-frame-crop`, which is not width-gated — so a fix aimed at 390 moved the 1440 register
   too, in the one round whose header promises it moved nothing on the desktop. The inline crop
   goes back to §31.3's `50% 56%` and the phone reads a SECOND var instead.
   ⚠ This is the general lever now, not a one-off: any `.frame` may carry `--sk-frame-crop-mob`
   and gets the desktop crop as its fallback if it does not. Use it rather than editing
   `--sk-frame-crop` for a phone finding — that is the mistake this block is undoing.
   ⚠ (0,2,1) through `[data-skin]` is deliberate: §23.2's `.frame img` is (0,1,1), and an inline
   custom property on the FIGURE is read by the IMG, so there is no inline-style fight here.

   ── 35.4 · why the cranes are a photograph and the kumo is a mask ────────────────────
   Four ornaments landed and they did not all take the same route, which is the one thing in this
   round worth writing down for sites 3-8.
     · the kumo bank and the brush stroke are RULES. They sit at band joins, they inherit the
       band's ink, and they have to read on ivory AND on near-black — so each is a mask and one
       `color` declaration recolours it (§34.10). Their wash tonality survives in the alpha.
     · the crane is an OBJECT. Its fold shadow and its gold branch print are the reason to
       photograph it at all, and a mask discards both (§19.7). It is painted, and a cast shadow
       separates it from paper.
   The test is not "photograph or drawing", it is **does this thing need the band's ink**. ──── */

/* ⚠ LOAD-BEARING, and the reason is not obvious. All three of these <img> elements are styled
   and positioned by rules that assume the img is a DIRECT child of its frame — `.hero-bleed` is
   `position: absolute; inset: 0` and is also a grid item of §30.1's phone hero; `.frame img` is
   the same; `.torii-frame`'s picture is a block in flow. Wrapping any of them in <picture>
   inserts an INLINE box, which in the hero's grid contributes a phantom line-box row and in the
   flow cases resolves the img's containing block one level lower than the rules expect.
   `display: contents` removes the wrapper's box and leaves the tree the CSS was written for.
   ⚠ AND IT DOES NOT SAVE A CHILD SELECTOR. `display: contents` removes the BOX, not the node —
   selector matching is unchanged, so `.frame > img` stopped matching the instant Story's close
   was wrapped, and that rule is the one that makes a bleed cover its frame. It came back as
   `.frame > img, .frame > picture > img` (§23.2), which is the pairing `atelier.css` uses on
   every one of its own image hooks. ⚠ CHECK EVERY `> img` IN THE SKIN BEFORE WRAPPING AN IMG:
   there was exactly one and it was the one that mattered.
   ⚠ §35.2's `.torii-frame > picture > img` is written the same way for the same reason. Do not
   "simplify" it to a direct child of the frame — it is not one. */
[data-skin="hanakenji"] .hero--threshold picture,
[data-skin="hanakenji"] .torii-frame picture,
[data-skin="hanakenji"] .frame picture { display: contents; }
@media (max-width: 47.99em) {
  [data-skin="hanakenji"] .plate--torii .torii-frame > picture > img {
    aspect-ratio: 4 / 5; width: 100%; height: auto; object-fit: cover;
  }
  [data-skin="hanakenji"] .frame img {
    object-position: var(--sk-frame-crop-mob, var(--sk-frame-crop, 50% 44%));
  }
  /* ONE crane on the phone, not the pair. §19.7's floor makes each bird 112px and §31.4 hangs
     them left-aligned inside the sheet — two of them plus the gap is 248px of a 287px measure,
     which is an ornament taking the whole line. The mirrored second bird is the one that goes;
     the pair is a rhythm, and at this width the line has no room for a rhythm. */
  .frame-say > .crane-pair .flip-x { display: none; }
  /* ⚠ REPLY KEEPS BOTH, and the asymmetry is deliberate — do not "unify" these two. Reply's pair
     is CENTRED in a paper band at 275px of a 287px measure, which reads as a deliberate symmetric
     pair filling the line. Story's is LEFT-ALIGNED above an eyebrow inside a sheet, where 248px
     of the same measure reads as an ornament crowding the type it is introducing. Same object,
     two placements, two answers. */
}

/* ── 35.5 · the lanterns reach Travel ───────────────────────────────────────────
   `DECISIONS.md`, 2026-09-08: `cut-lantern-pair.webp` at the head of Travel's `#stay`, `--bare`,
   one drop. It is MARKUP ONLY — §19.5 already carries every rule the piece needs, including the
   phone's 108-132px width and the `--bare` variant that suppresses the hanging cord.
   ⚠ Home's trio does not move, and this is not the trio on a second page: one box, so §19.5's
   "two lanterns below 40em" arithmetic (287px of measure, 324px needed for three) is not in play.
   The PAIR cut-out is the piece that carries two lanterns in one box, which is why the decision
   named that file rather than `cut-lantern-lit`.
   ⚠ It has no `--sk-lantern-drop` cord, so it needs no rod and no ceiling above it — that is the
   whole reason it can open a section head where home's hangs inside a sheet. */

/* ── 35.7 · the pressed blossom, placed ──────────────────────────────────────────
   `cut-blossom-pressed.webp` was the one asset with no home — unplaced in batch 4 and unplaced
   again when it was regenerated. It goes at the HEAD OF STORY'S GALLERY, once, and the reason is
   the object rather than the layout: a pressed flower is what people actually keep in a photograph
   album, and this is the site's album. `gallery-03`'s own caption is *"the petals that would not
   stay out of her hair"* — the mark is already in the pictures, and this lays the kept one on the
   page above them. It is also the skin's namesake (花) finally appearing as itself rather than only
   inside a photograph.

   Why not anywhere else, so it is not re-proposed:
     · home is FULL — crest, torii plate, lantern trio, sensu, footer kamon.
     · Story's OPEN is type-only by decision, and its ink pause already carries the brush stroke.
     · Reply is the standing exception and §34.10 already spent its allowance on the crane pair.
     · Travel took the lantern pair this round; two new marks on one page in one round is the
       "finishing the job" §19's header exists to prevent.

   ⚠ A PICTURE, not a mask — §35.4's test. The petals are translucent going to bone at the edges
   and the mid-veins are the whole subject; a silhouette of this is a blob.
   ⚠ It therefore needs §19.7's cast shadow, and here it is not optional: `--bg-raised` is a mid
   ivory and the blossom's edges go to bone. Same treatment, same reason — a pressed object on
   paper has a shadow.
   ⚠ LEFT-ALIGNED and pulled up onto the header's own bottom margin, so it reads as laid ON the
   page rather than as a centred divider between two blocks — §31.4's rule about a centred
   ornament over left-aligned copy, which is what the header above it is.
   ⚠ THE SHADOW IS ON THE WRAPPER, NOT ON THE IMG, and it has to be. `atelier.css` puts
   `filter: var(--a-tone)` on EVERY img through `[data-atelier~="toned"] :is(img, video)`, and
   `filter` is a single property — a `drop-shadow()` written on the img REPLACES the tone rather
   than joining it. Measured: the first build's shadow computed away to `sepia(0.06)
   saturate(1.03)` and cast nothing. The span carries the shadow, the img keeps the tone, and
   `drop-shadow` on a parent works off its subtree's rendered alpha, so it still follows the
   petals — and the rotation with them. §19.7's crane shadow was corrected the same way.
   ⚠ 132px, above §19's 112px floor because a five-petal form has to resolve its veins, and the
   phone keeps it: this is the mark, not a flourish. ─────────────────────────────────── */
.blossom-press {
  display: block; margin: -.7rem 0 1.1rem;
  filter: drop-shadow(0 4px 6px rgba(24, 20, 28, .22));
}
.blossom-press img {
  display: block; width: 132px; height: auto;
  transform: rotate(-7deg); transform-origin: 20% 50%;
}
@media (max-width: 47.99em) { .blossom-press { margin: -.3rem 0 .9rem; } }
@media print { .blossom-press { display: none !important; } }

/* ── 35.6 · 🐛 Travel's torii was rendering NOTHING ─────────────────────────────────
   Found while reading for §35.2, on the page whose own notes call the gate "Travel's signature,
   and the one Story could not take".

   Round ten converted Travel "with no new CSS" and its markup carried the REFERENCE's drawn gate:
   <i class="kasagi glass">, `shimaki`, `nuki`, `hashira hl/hr`. Those four class names have
   rules in `_reference/sakura/japan.css` and in NO stylesheet this site loads. `.glass` exists
   here as a specular treatment with no geometry of its own, so all five <i> elements computed to
   0 x 0 inside a `position: absolute; inset: 0` overlay and the gate was invisible at every width
   from round ten to now.

   The skin's live gate is §26's photographed slim vermilion — `torii-crown-red.png` plus two
   `.torii-post` tiles under `.torii--red` — which is what home's invitation plate has always
   hung. Travel's markup is now that, verbatim, at Travel's path depth. No CSS was needed; this
   block is the record.
   ⚠ THE LESSON, and it is the same one §19's float layer taught on site 1: THE SWEEP CANNOT SEE
   AN ORNAMENT THAT RENDERS NOTHING. 72 clean frames said nothing about this, twice. A converted
   page's ornaments have to be counted, not swept.
   ⚠ "Converted with no new CSS" is not a virtue on its own — it is only true if every class the
   markup names exists in the skin. Check the class names, not the diff size. ────────────── */


/* ══════════════════════════════════════════════════════════════════════════════════════════════
   35 · THE RAIL, BELOW 64em — the bar that arrives after the hero (2026-09-13)
   ══════════════════════════════════════════════════════════════════════════════════════════════

   §17 said "the rail is fixed to the left edge at every width; only its register changes."
   ⚠ THAT IS NO LONGER TRUE BELOW 64em, and §17's charter commentary should be read with this
   section beside it. The user's verdict on the 3.8rem strip: it made the hero too small — 61px of
   a 390 screen, at every width under 64em, paid for by the photograph. Three candidates were
   built (rounds nineteen a/b) and this is the one picked, with three carry-overs folded in.
   Reasoning: DECISIONS.md → *Site 2's phone nav: B, the bar that hides*.

   ≥ 64em   unchanged. The 15rem lettered column, everything in §17, still site 2's navigation
            claim under the differentiation charter. The claim is *vertical anchor rail with
            progress*; a phone reducing it is a REGISTER, not a different claim.
   < 64em   the page takes the 61px back — --rail-w: 0, the body's padding gone, the hero
            photograph edge to edge. The same markup becomes two pieces:
              · a 56px BAR (wordmark · Menu · Reply) that is parked off-screen until rail.js sets
                [data-past], then hides again under [data-dir="down"] and returns on "up";
              · a full-height SHEET the Menu slides in — the four pages, then this page's sections
                as named soft buttons TWO-UP AND WRAPPING, then Reply.

   ⚠ THE BAR AND THE SHEET ARE SIBLINGS inside one mount, and that is load-bearing: the bar is the
     thing that translates, and a sheet inside it would be hidden with it.
   ⚠ THE MOUNT IS A DIRECT CHILD OF <body>. position: fixed inside <main> does not work on this
     site — a-page-in holds an identity transform for the life of the page, which makes main the
     containing block. Never move the rail into main to place it after something; that is what
     data-wsd-past is for.
   ⚠ .rail's backdrop-filter is KILLED below 64em: a backdrop-filter also makes its element the
     containing block for fixed children. The wide register keeps its glass.
   ⚠ No bottom strip, ever — the demo docks own the bottom corners (closed thread, round nine).
   ⚠ Sections are SENTENCE CASE and pages are UPPERCASE. §17 already drew that distinction and the
     soft buttons keep it; uppercasing both put "WHAT TO WEAR" on two lines in a 134px cell.
   ⚠ The 01–07 numerals are GONE from the markup. They existed because the 3.8rem strip could not
     carry a label (§17, move F); a named button does not need a number in front of it. Nothing in
     this file styles .rail-no any more — if it ever comes back, it comes back with a reason.
   ✅ ALL FOUR CLOSES ARE BUILT, and every one of them is chrome.js's — do not fork or fake one here.
     Menu · Escape · ANY LINK IN THE SHEET (its section links are same-document anchors, so nothing
     unloads and the sheet used to sit open over the section it was asked for) · AN OUTSIDE TAP.
     ⚠ The scrim is still the sheet's own box-shadow and still cannot take a click — the outside
     tap works because chrome.js closes on a capture-phase pointerdown and swallows the click it
     produces, so a dismiss never reaches the page under the dim. That is why this section still
     needs no scrim element. */

.navwrap { display: contents; }

@media (max-width: 63.99em) {
  [data-skin="hanakenji"] { --rail-w: 0px; }
  [data-skin="hanakenji"] body { padding-left: 0; }

  .rail {
    position: fixed; inset: 0; width: auto; z-index: 56;
    display: block; padding: 0; gap: 0;
    background: none; border: 0; box-shadow: none; overflow: visible;
    backdrop-filter: none; -webkit-backdrop-filter: none;
    pointer-events: none;
  }

  /* ---------- 35.1 · the bar ---------- */
  .navbar {
    position: absolute; top: 0; left: 0; right: 0;
    display: flex; align-items: center; gap: .6rem;
    padding: .45rem .7rem;
    background: var(--card);
    border-bottom: var(--rule-w) solid var(--rule);
    box-shadow: 0 6px 18px -14px rgba(43, 39, 51, .5);
    pointer-events: auto;
    transform: translateY(-102%);
    transition: transform 260ms var(--ease);
  }
  /* ⚠ 2026-09-13, supersedes round nineteen's option B: the bar STAYS once it has arrived.
     data-dir is still published by rail.js and still read by the sections strip below; it no
     longer moves the bar. Past the hero the bar is navigation the reader can always reach,
     and a bar that leaves on downward travel reads as a bug on a four-page site. */
  .rail[data-past] .navbar { transform: none; }

  .navbar-brand {
    display: flex; align-items: center; min-height: 44px;
    text-decoration: none; color: var(--ink);
  }
  [data-skin="hanakenji"] .navbar-brand b {
    font: 400 var(--step-0)/1 var(--display); letter-spacing: .06em;
  }
  .navbar-brand i { font-style: normal; color: var(--accent-ink); margin-inline: .22em; }
  .navbar-chrome { margin-left: auto; }
  .navbar-toggle {
    display: flex; align-items: center; gap: .45rem;
    min-height: 44px; min-width: 44px; padding: .5rem .8rem;
    background: var(--paper); border: var(--rule-w) solid var(--rule-strong);
    border-radius: 999px; cursor: pointer;
    font: 600 .68rem/1 var(--ui); letter-spacing: .12em; text-transform: uppercase;
    color: var(--ink);
  }
  .navbar-toggle[aria-expanded="true"] {
    background: var(--accent); border-color: var(--accent); color: var(--on-accent);
  }
  .navbar-bars { display: grid; gap: 3px; }
  .navbar-bars i { display: block; width: 15px; height: 1.5px; background: currentColor; }
  .navbar-reply {
    display: inline-flex; align-items: center; justify-content: center;
    min-height: 44px; padding: .5rem .9rem; border-radius: 999px;
    background: var(--accent); color: var(--on-accent); text-decoration: none;
    font: 600 .68rem/1 var(--ui); letter-spacing: .12em; text-transform: uppercase;
  }
  .navbar-reply:hover { background: var(--cherry-ink); }

  /* the hairline, turned: scaleY → scaleX off a LEFT origin, on the bar's bottom rule.
     ⚠ .rail-track must stay the bar's first child — it is absolutely positioned against it. */
  .rail-track {
    position: absolute; left: 0; right: 0; top: auto; bottom: -1px;
    width: auto; height: 2px;
    background: color-mix(in oklab, var(--sumi) 10%, var(--paper));
  }
  .rail-track i { transform: scaleX(var(--rail-progress, 0)); transform-origin: left; }

  /* ---------- 35.2 · the sheet ---------- */
  .navsheet {
    position: absolute; inset-block: 0; left: 0;
    width: min(20rem, 86vw);
    display: flex; flex-direction: column; gap: 1rem;
    padding: 1.1rem .9rem;
    background: var(--card);
    border-right: var(--rule-w) solid var(--rule-strong);
    overflow-y: auto; overscroll-behavior: contain;
    pointer-events: auto;
    transform: translateX(-102%);
    /* the scrim is the sheet's OWN box-shadow spread to the viewport: one animatable property,
       no extra element, and it cannot swallow a tap meant for the page. */
    box-shadow: 0 0 0 100vmax rgba(43, 39, 51, 0);
    transition: transform 280ms var(--ease), box-shadow 280ms var(--ease);
  }
  .rail:has(.rail-pages[data-open]) .navsheet {
    transform: none; box-shadow: 0 0 0 100vmax rgba(43, 39, 51, .38);
  }
  /* ⚠ 44px in the sheet too. §17 measured this link failing in BOTH of its registers once
     already; do not drop the minimum to tighten the sheet. */
  .navsheet .rail-brand {
    justify-items: start; align-content: start; min-height: 44px; gap: .25rem;
    padding-bottom: .9rem; border-bottom: var(--rule-w) solid var(--rule);
  }
  [data-skin="hanakenji"] .navsheet .rail-brand .brand-full,
  [data-skin="hanakenji"] .rail .navsheet .brand-full {
    display: block; writing-mode: horizontal-tb; text-align: left;
    font: 400 var(--step-1)/1.05 var(--display); letter-spacing: .08em;
  }
  .navsheet .rail-brand span { display: block; }

  /* §17 parks the pages list as a fixed panel in its narrow register; in the sheet it is flow.
     [data-open] stays on it — it is what the :has() above reads. */
  .rail-pages {
    position: static; display: block; width: auto; padding: 0;
    background: none; border: 0; box-shadow: none;
  }
  .rail-pages ul { display: grid; gap: .45rem; }
  .rail-here { position: static; flex: none; min-height: 0; overflow: visible; }
  .rail-here ol { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .45rem; }

  /* the soft button. Paper fill on the card ground, hairline rule, the skin's one radius, 48px
     of target. No resting shadow — a lift on eleven buttons at once reads as eleven cards. */
  :is(.rail-pages, .rail-here) a {
    display: flex; align-items: center; justify-content: flex-start; gap: .55rem;
    min-height: 48px; padding: .6rem .8rem;
    background: var(--paper);
    border: var(--rule-w) solid var(--rule); border-left: var(--rule-w) solid var(--rule);
    border-radius: var(--radius);
    color: var(--ink); text-decoration: none;
    font: 600 var(--step--1)/1.25 var(--ui);
    transition: background var(--motion-fast) var(--ease),
                border-color var(--motion-fast) var(--ease);
  }
  :is(.rail-pages, .rail-here) a:hover { background: var(--accent-soft); border-color: var(--rule-strong); }
  :is(.rail-pages, .rail-here) a:active { background: var(--accent-soft); }
  .rail-pages a { letter-spacing: .1em; text-transform: uppercase; }
  .rail-here a { font-weight: 500; letter-spacing: .01em; padding: .6rem .65rem; }
  .rail-pages a[aria-current],
  .rail-here a[data-current] {
    background: var(--accent-soft);
    border-color: color-mix(in oklab, var(--accent) 42%, var(--paper));
    color: var(--accent-ink);
  }
  /* the labels come back: §17 CLIPPED them for the 3.8rem strip, and a soft button with no name
     is the tick finding again in a bigger box. */
  .rail-name {
    position: static; width: auto; height: auto; overflow: visible;
    clip-path: none; white-space: normal; text-wrap: pretty;
  }
  .rail-tick { display: none; }

  .rail-foot {
    margin-top: auto; padding-top: .9rem; justify-items: stretch;
    border-top: var(--rule-w) solid var(--rule);
  }
  /* ⚠ The torii goes with the strip. §19's gate is a 32px MASK sized for the wide rail's foot,
     where §17 centres it; §35 stretches the foot so the sheet's Reply pill is full width, which
     left the glyph pinned hard left above it — un-centred and unasked-for (candidate B had no
     gate in the sheet). It is not re-centred instead, because an ornament that cannot keep its
     geometry does not travel: the torii needs a plate's overhang, which a sheet-footer slot does
     not give it. ≥ 64em it is untouched. */
  .rail-gate { display: none; }
  .rail-rsvp { min-height: 48px; padding: .8em 1em; font-size: var(--step--1); letter-spacing: .14em; }
  .rail-rsvp-by { display: inline; }

  /* ⚠ The anchor landing has to clear the bar. Base is scroll-padding-top: 2rem (32px), which was
     right when nothing was pinned to the top of the window. ⚠ THE BAR IS 60px, NOT 56 — measured,
     2026-09-13: a 44px target plus .45rem of padding top and bottom plus the bottom rule is 59.2,
     at every phone width (none of the three scales with the viewport). The 56 written here in
     round nineteen left 13px of air where the line below promises 16.
     The bar no longer hides on downward travel at all (§35.1, 2026-09-13 (e)), so past the hero
     the clearance is never notional: every landing arrives under a bar that is really there. */
  /* ⚠ [data-skin] is on <html> itself, so this is the scroll container. */
  [data-skin="hanakenji"] { scroll-padding-top: calc(60px + 1rem); }
  /* ---------- 35.3 · the hero takes what the rail gave up ----------
     Edge to edge, and 6svh taller. ⚠ .hero-paper KEEPS §23.3's -2.4rem pull onto the
     photograph — that is the phone fix; do not zero it to make room here.

     ⚠ 2026-09-14 · THE HEIGHT IS NOW SCOPED TO THE PHONE, and the defect it caused is the
     reason to leave it scoped. This whole block is the ≤63.99em register, because that is
     where the RAIL changes — but a 54svh photograph only works in the PHONE layout
     (≤47.99em), where §29.4/§30.1 make the hero a column flex, order the picture first and
     pull the card up onto it. Between 48em and 64em the hero is still §34's grid:
     `align-items: end`, `min-height: min(96svh, 50rem)`, with the picture
     `position: absolute; inset: 0`. An explicit height on an over-constrained absolute box
     WINS over `bottom: 0` — so the photograph stopped short and the composition came apart.
     Measured at 834 × 1112 before the fix: an 800px hero holding a 600px picture, the glass
     card at y 584–818 and the names at 682 — 64px BELOW the bottom edge of the photograph.
     White hero type on bare ivory: 1.05:1 for the countdown labels, 2.7:1 for the names.
     It went unseen because the 72-frame gate cannot see contrast and nobody had judged the
     834 box (NEXT.md's "look at both sites on the TABLET box").
     §29.4 already sets the same 54svh/19rem for the phone, so this declaration was redundant
     where it worked and harmful where it did not. ⚠ An identical trap waits for any height
     given to `.hero-bleed` in a register whose layout does not also re-place the card. */
  @media (max-width: 47.99em) {
    .hero--threshold .hero-bleed { height: 54svh; min-height: 19rem; }
  }
}

/* ---------- 35.4 · the same markup at 64em and up ----------
   §17 styles .rail as the column and this puts the two new pieces back into it: the sheet IS the
   column, and the bar shrinks to the 2px strip that carries the progress fill down the left edge,
   scaleX back to scaleY. Everything else in §17 applies untouched. */
@media (min-width: 64em) {
  .navbar {
    position: absolute; inset: 0 auto 0 0; right: auto;
    width: 2px; padding: 0; gap: 0;
    background: none; border: 0; box-shadow: none; transform: none;
  }
  .navbar > :not(.rail-track) { display: none; }
  .rail-track { position: absolute; inset: 0 auto 0 0; width: 2px; height: auto; background: var(--rule); }
  .rail-track i { transform: scaleY(var(--rail-progress, 0)); transform-origin: top; }
  .navsheet {
    position: absolute; inset: 0; width: 100%;
    display: flex; flex-direction: column; gap: .8rem;
    padding: .9rem .5rem .9rem .7rem;
    background: none; border: 0; box-shadow: none; transform: none;
    overflow-y: auto;
  }
  .navsheet .rail-brand { padding-bottom: .7rem; border-bottom: var(--rule-w) solid var(--rule); }
  .rail-here ol { display: grid; }
  .rail-foot { margin-top: auto; }
}


/* ══════════════════════════════════════════════════════════════
   §36 · ROUND EIGHTEEN (2026-09-16) — THE PHOTOGRAPH LEADS

   The note, in the user's words: "highlight the photo and the Hana and Kenji card more than the
   countdown — countdown and its background occupies too much" · the photograph fills the first
   screen (their pick) · the clock's four cells go in the photograph's bottom corner with no band
   behind them (their pick, option 2) · and the clock's font should match the site.

   Four moves, and the third is the one that pays for the other two.

   ── 36.1 · the first screen IS the photograph ──────────────────────────────────
   `--sk-hero-h` min(94svh, 52rem) → min(100svh, 60rem), the ≤62em register min(96svh, 50rem) →
   min(100svh, 56rem), and §30.1's phone box min(92svh, 46rem) → min(100svh, 54rem). Nothing else
   in this hero is anchored to those numbers — §24.6's waterline reads the BAND's height and home
   has no `.hero-gate` (§35.1) — so it is one declaration per register and no arithmetic downstream.
   ⚠ §35.1'S WINDOW ARITHMETIC MOVES WITH IT, and here it is re-derived rather than left stale. At
   390 × 844 the phone box is 844px (100svh binds; 54rem = 864 does not), so a 4:5 `hero-tall.jpg`
   covering it scales to 675.2px wide and the 390px window sees 57.8% of the frame, centred at
   x 21.1–78.9%. The couple are centred in that frame and stay whole. It is a TIGHTER window than
   §35.1's 66.2% — that is the cost of the taller box, and it is accepted rather than unnoticed.
   ⚠ Re-check the vertical date's ground (§35.1's last warning) if the source is ever swapped again:
   its left edge is now 21.1% into the frame, not 16.9%.
   ⚠ Padding is INSIDE the box (`box-sizing: border-box`, engine.css line 4), so §30.1's
   clamp(7rem, 16svh, 8.4rem) of dock clearance still eats into the 844 rather than adding to it.
   The hero is exactly one screen tall, which is what the pick asks for.

   ── 36.2 · the clock is a corner cluster, and the page-wide scrim goes with it ─────────
   §34.1's phone scrim — 66% of the frame at a .72 plateau — IS the "background" the note is about.
   It is the largest single tone on the first screen (about 390 × 557px of near-black at 390 × 844)
   and it exists for exactly one reason: §30.1 put the clock's rows on sunlit sand at 1.08:1, the
   one measured contrast failure on this site. So the answer is not to weaken it and hope — it is to
   give the CLOCK its own ground and stop tinting the whole photograph on the clock's behalf.
     · a corner wash inside the cluster's own box, about 286 × 150px — a fifth of the scrim's area;
     · the page-wide scrim drops to a 34% band at a .46 plateau: enough to seat the glass card's
       foot, not enough to be a tone of its own.
   ⚠ The wash is the cluster's OWN `::before` at z-index -1. `.hero-count--corner` carries
   z-index 2 and is therefore its own stacking context, so -1 paints behind the cells and still
   above both the picture (0) and the scrim (1). It could not have been done on
   `.hero--threshold::after` without moving the scrim, which is the thing being shrunk.
   ⚠ `closest-side` IS LOAD-BEARING, and it is arithmetic rather than taste. A radial sized any
   other way reaches the `::before`'s edge with alpha still standing, and `.hero--threshold`
   computes `overflow: clip` — which cuts it off square and hands the corner the exact hard
   rectangle a feathered wash exists to avoid. `closest-side` inscribes the ellipse in the box, so
   every side reads 0. The same trap §34.1's header describes for the scrim, one element down.
   ⚠ The cluster goes to the bottom RIGHT and the card keeps the left, so the two no longer share a
   baseline — that separation is the note's whole point, not a layout preference.
   ⚠ `--sk-cut-lift`'s 118–134px of dock clearance is UNCHANGED and still load-bearing: the demo
   dock and the music dock live in the bottom corners, and moving them is a closed thread
   (`DECISIONS.md`). The cluster sits in the photograph's corner, above the dock, not in it.

   ── 36.3 · the figures are the site's mincho, not Karla ────────────────────────
   §29.2's clock is `600 var(--ui)` — Karla — behind 3px of near-black `-webkit-text-stroke`, and
   that is the answer to "change the countdown font to match the site": the names 200px above it
   are Shippori Mincho and NOTHING else on this site is set in a heavy sans at display scale. It is
   now `400 var(--display)` at 1.45–1.85rem, and the stroke is GONE from the figures — a 3px
   centred stroke closes a mincho hairline into a slab, which is the fault it would be hiding.
   ⚠ §29.2'S COMPLEMENTARY PAIR IS REPLACED, NOT ABANDONED. Its argument was that white alone is
   1.35–6.57:1 on this ground, so the stroke carries the bright tiles and the fill the dark ones,
   for a worst tile of 5.22:1. The corner wash is a stronger answer of the same kind: near-black at
   a .64–.70 plateau under #fff does not depend on the picture at all. The soft halo stays, for the
   wash's own falloff.
   ⚠ THE 1.6px STROKE STAYS on the caption and the labels. They sit at the wash's top and left
   falloff, where the alpha is thinning, and 12–13px type has no size allowance to spend.
   ⚠ Caption .78rem / labels .72rem are round four's MEASURED floor (§27.4, §28.2) and are not cut
   here to buy width. The figures came down instead — 3.4rem → 1.85rem at 1440.
   ⚠ THE VISIBLE FIGURE IS `.a-odo`, NOT `.count-num` — fifth round in a row. Size and family on
   `.count-cell` so the engine's 1.16em digit window scales with it; colour and shadows on BOTH
   registers; nothing drawn inside `.a-odo-win`, which is `overflow: hidden`.
   ⚠ `min-width: .68ch`, not §29.2's .78: that value was headroom for 3px of outward stroke, and
   mincho tabular digits are narrower than Karla's semibold.

   ── 36.4 · the RSVP pill moves into the card ────────────────────────────────
   It was in `.hero-count-top`, sharing a line with the caption — so demoting the clock would have
   demoted the page's primary action along with it. It is `.hero-do` inside `.hero-say` now: the
   card carries the names, the date, the place and the reply, which is what "highlight the card"
   means in markup. §29.3's solid cherry recipe is unchanged (`--accent-ink` under #fff, 6.14:1)
   and re-scoped.
   ⚠ A NEW CLASS, not `.hero-count-act`, and that is deliberate: §24.2's and §25.4's dead
   `.hero-paper .hero-count-act` registers would have woken up the instant the element moved inside
   the card, and both were written for a countdown that lived in the card in round two.
   ⚠ `--a-cue:3` continues §L's card cue chain (names 1, line 2) rather than §29.5's clock stagger.
   The pill arrives with the card it now belongs to.

   ── 36.5 · the card takes the width the clock gave up ─────────────────────────
   38rem → 40rem. It binds at 1440 only — at 1024 column 1 of §27.1's grid is ~505px and the card
   was already capped by the track, so this is a change at one width and honest about it.

   ── 36.6 · what this round retires ─────────────────────────────────────
   The markup class went `--cut` → `--corner`, so §29.1–29.5 stopped matching the way §27 stopped
   for §28 and §28 for §29.
   ✅ **AND §48 HAS NOW DELETED ALL OF THEM (2026-09-19).** `--card`, the bare-in-glass register,
   `--bare`, `--glass`, `--type`, `--cut` and `--chrome` are gone from this file; `--corner` is
   the only countdown register left, and this section IS it. The pre-collapse stylesheet is at
   `_archive/skin-hanakenji-pre-collapse-2026-09-19.css` — that is the record now, not dead CSS.
   ⚠ `--sk-cut-ink` survived the cut because §36.3 strokes with it (§29.1's note says why).
   ══════════════════════════════════════════════════════════════ */

/* ── 36.1 · the first screen ────────────────────────────────────────── */
[data-skin="hanakenji"] {
  --sk-hero-h: min(100svh, 60rem);
  /* the corner wash's plateau. Near-black, and deliberately the same ink as the scrim it
     replaces (§34.1) so the two read as one tonal family where they still overlap. */
  --sk-corner-ink: 18, 15, 22;
  --sk-corner-w: 22rem;
  /* ⚠ MEASURED, and it is NOT §29's `--sk-cut-lift`. That value (118–134px) was derived for a
     clock pinned `justify-self: start` in column 2, which cleared the dock stack by sitting to
     its LEFT — it never had to clear it vertically. Pinned `end`, the cluster is directly over
     the demo dock and the music dock: measured at 924 × 540 the 118px lift left the cells'
     baseline **4px** above the study tab, and the lift is vw-based so 4px was the clearance at
     every viewport HEIGHT, not just a short one. 150–170px puts 36px of air there.
     ⚠ The dock stack is ~114px and moving it off the corner is a closed thread
     (`DECISIONS.md`), so this number is the only lever. Re-measure it, do not re-taste it. */
  --sk-corner-lift: clamp(9.4rem, 6vw + 5rem, 10.6rem);
}
@media (max-width: 61.99em) { .hero--threshold { --sk-hero-h: min(100svh, 56rem); } }

/* ── 36.5 · the card ─────────────────────────────────────────────
   ⚠ GATED AT 48em, and it shipped ungated for one round. §30.1's phone card is `width: auto`
   with `margin: 0 var(--gutter)` and `justify-self: stretch` — it fills the gutters' inset. A
   bare `.hero--threshold .hero-paper { width: min(100%, 40rem) }` is the same (0,1,1) and later
   in the file, so it beat that `auto`: measured through the 402px rig the card came back
   **402px of a 402px viewport** and ran edge to edge with its right side cut off the screen.
   ⚠ Any width, inset or margin written for the DESKTOP hero needs this gate. §30.1's phone card
   is sized by `justify-self` and its margins and never by a width — that section's own comment
   says so from the other side. */
@media (min-width: 48em) {
  .hero--threshold .hero-paper { width: min(100%, 40rem); }
}

/* ── 36.4 · the reply, in the card ──────────────────────────────────
   `.btn` already carries the box (inline-flex, 44px, padding, radius — §12); only the recipe is
   declared here. */
.hero-say .hero-do { margin: .45rem 0 0; }
.hero-say .hero-reply {
  background: var(--accent-ink); border: 1px solid rgba(255, 255, 255, .55);
  color: #fff; -webkit-text-fill-color: #fff; letter-spacing: .14em;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .3), 0 10px 26px -16px rgba(18, 15, 22, .8);
}
.hero-say .hero-reply:hover { background: var(--accent); border-color: rgba(255, 255, 255, .8); }
.hero-say .hero-reply:focus-visible { outline-offset: 3px; }

/* ── 36.2 · the corner cluster ─────────────────────────────────────
   The six resets are §29.1's. ⚠ What they were beating — §24.6's and §25.6's bare-`.hero-count`
   phone rules, a top rule and 1rem of padding — was deleted by §48, so they are belt with no
   braces now. They are kept anyway: this element is a second `[data-wsd="countdown"]` mount and
   the engine's own `.hero-count` defaults are not this skin's to assume. */
.hero-count--corner {
  position: relative; z-index: 2;
  margin: 0; padding: 0; border: 0; border-radius: 0; background: none; box-shadow: none;
  display: grid; gap: .3rem; justify-items: end;
  justify-self: end; align-self: end;
  width: max-content; max-width: min(100%, var(--sk-corner-w));
}
.hero-count--corner::before {
  content: ""; position: absolute; z-index: -1; pointer-events: none;
  /* ⚠ The box is deliberately much wider than the type, and the plateau runs to 70% of it —
     that is arithmetic, not padding. `closest-side` inscribes the ellipse, so rx is .48 of the
     box width: with the cells spanning ±100px of a 301px box the outer digits sit at .69 of rx,
     and a plateau ending at 56% (the first draft) left "212" and "SEC" on .39 alpha. At 70% they
     read .66 — near-black at .66 under #fff is ~11:1 whatever the photograph is doing.
     ⚠ The bottom inset is the SHORTEST of the four on purpose: the wash used to bleed 38px down
     over the study tab, which made the shrunk ground look like a tint on the dock. */
  inset: -1.9rem -3rem -1.4rem -3.4rem;
  background: radial-gradient(closest-side ellipse at 52% 50%,
    rgba(var(--sk-corner-ink), .70) 0%,
    rgba(var(--sk-corner-ink), .66) 70%,
    rgba(var(--sk-corner-ink), .34) 88%,
    rgba(var(--sk-corner-ink), 0) 100%);
}
@media (min-width: 48em) {
  /* ⚠ A RIGHT MARGIN IS CORRECT HERE AND WAS WRONG IN §29.1, which is worth the two lines. There
     the box was `width: min(100%, --sk-cut-w)` with `justify-self: start`, so 100% resolved
     against the whole grid area and a margin pushed the box PAST the area's right edge — hence
     that section's padding. This box is `max-content` pinned `end`, so a margin pulls it in.
     ⚠⚠ THE BOTTOM CLEARANCE IS NO LONGER A MARGIN ON THIS ELEMENT — it is the HERO's
     `padding-bottom` (§36.10), so the card gets it too. It was `margin-bottom:
     var(--sk-corner-lift)` here for one round, which lifted the clock and left the card resting
     on the fold. Do not put it back. */
  .hero--threshold > .hero-count--corner {
    grid-column: 2; grid-row: 1;
    margin-right: clamp(1.2rem, 2.6vw, 3rem);
  }
}

/* ── 36.10 · the desktop hero gets a FLOOR, because the card rests on it ───────────────────
   Found by the verifier at 924 × 540: the glass card's bottom edge measured **exactly**
   `innerHeight` — its rounded corners bisected by the screen edge — while the clock floated 150px
   clear. The two were visibly out of step and the card looked cut off.

   The cause is §36.1, not a pixel: `.hero--threshold` is a grid with `align-content: end` (§27.1),
   so the card is an item resting on the hero's FLOOR. While the hero was `min(94svh, 52rem)` it
   was SHORTER than the screen, and the card's ~32px of air at 924 (and ~68px at 1440 × 900) was
   incidental — a by-product of the gap between the hero's floor and the fold. §36.1 closed that
   gap by design, so the floor became the fold and the card's clearance went to zero on every
   viewport under 960px tall. The ≥48em register had no `padding-bottom` at all: §30.1's is
   phone-scoped, which is why the phone card was never affected.

   ⚠ THE TWO NUMBERS ARE DERIVED TOGETHER, and that is why the clock's margin moved rather than
   the hero simply gaining padding. `margin-bottom` on the cluster STACKS on the hero's padding, so
   adding one without removing the other lifts the clock by the same amount and re-breaks the
   36px of dock air §36.2 was measured to hit. So:
     · the hero's `padding-bottom` IS `--sk-corner-lift` — one value, and the clock now earns its
       clearance by standing on the floor like everything else;
     · the CARD is offset ABOVE that floor, which is what keeps the pair out of step (the clock
       low-right, the card higher-left — the note's whole point, and §36.2's).
   ⚠ Border-box (engine.css line 4), so the padding is inside `min-height` and the hero is still
   exactly 100svh. The content box shrinks; the section does not grow. */
@media (min-width: 48em) {
  [data-skin="hanakenji"] .hero--threshold { padding-bottom: var(--sk-corner-lift); }
  [data-skin="hanakenji"] .hero--threshold > .hero-paper { margin-bottom: clamp(2.6rem, 5vw, 4.4rem); }
}
.hero-count--corner .hero-count-top { display: flex; justify-content: flex-end; }

/* ── 36.3 · the type ──────────────────────────────────────────── */
.hero-count--corner .hero-count-cap {
  margin: 0; color: #fff; text-align: right;
  font: 600 .78rem/1.3 var(--ui); letter-spacing: .17em; text-transform: uppercase;
  -webkit-text-stroke: 1.6px var(--sk-cut-ink); paint-order: stroke fill;
  text-shadow: 0 1px 8px rgba(18, 15, 22, .55);
}
.hero-count--corner .hero-count-cells {
  display: flex; align-items: flex-start; justify-content: flex-end;
  gap: clamp(.8rem, 1.6vw, 1.3rem);
}
[data-skin="hanakenji"] .hero-count--corner .count-cell {
  margin: 0; min-width: 0; padding: 0; border: 0;
  display: grid; justify-items: center; gap: .26rem;
  font: 400 clamp(1.45rem, .95rem + 1vw, 1.85rem)/1 var(--display);
  font-variant-numeric: tabular-nums; letter-spacing: 0;
  color: #fff;
}
[data-skin="hanakenji"] .hero-count--corner .count-num,
[data-skin="hanakenji"] .hero-count--corner .a-odo,
[data-skin="hanakenji"] .hero-count--corner .a-odo-col > span {
  font: inherit; font-variant-numeric: tabular-nums;
  color: #fff; -webkit-text-fill-color: #fff;
  background-image: none; background-clip: border-box; -webkit-background-clip: border-box;
  -webkit-text-stroke: 0;
  text-shadow: 0 1px 3px rgba(18, 15, 22, .70), 0 2px 12px rgba(18, 15, 22, .55);
  filter: none;
}
[data-skin="hanakenji"] .hero-count--corner .count-num { display: block; }
[data-skin="hanakenji"] .hero-count--corner .a-odo-win { min-width: .68ch; }
.hero-count--corner .count-label {
  display: block; color: #fff;
  font: 700 .72rem/1 var(--ui); letter-spacing: .14em; text-transform: uppercase;
  -webkit-text-stroke: 1.6px var(--sk-cut-ink); paint-order: stroke fill;
  text-shadow: 0 1px 8px rgba(18, 15, 22, .55);
}

/* ── 36.2b · the phone: the same cluster, and the scrim that shrinks ──────────────
   ⚠ The `!important`s are §29.4's. The bare-`.hero-count` phone rules they were written to beat
   (§24.6's and §25.6's) were deleted by §48, so they now win uncontested — kept, not stripped,
   because stripping them is a paint change on a parked page and buys nothing. */
@media (max-width: 47.99em) {
  [data-skin="hanakenji"] .hero--threshold { min-height: min(100svh, 54rem); }
  /* ⚠⚠ 36.2c · THE PHONE PHOTOGRAPH COVERS THE HERO AGAIN, AND THIS IS A BUG FIX, NOT A CHOICE.
     §30.1's header says "the phone hero: the photograph IS the section" and sets
     `.hero-bleed { position: absolute; inset: 0; height: 100% }` to do it. It has not been true
     since 2026-09-14. §35's rail block gained a nested `@media (max-width: 47.99em)
     { .hero--threshold .hero-bleed { height: 54svh; min-height: 19rem } }` whose own comment
     calls it "redundant where it worked" — because §29.4 sets the same pair. It is NOT redundant:
     it sits ~1290 lines AFTER §30.1 at the same (0,2,0), so it WINS, and the phone picture has
     been a 54svh band inside a 92svh hero ever since. Both rows then stood on ivory paper below
     it, and §34.1's 66% scrim at .72 was painting that empty paper near-black — which is why the
     hero read as "a photo and then a big grey area", and why shrinking the scrim in §36.2 made it
     look WORSE rather than better. The scrim was covering for this.
     ⚠ (0,3,0) THROUGH `[data-skin]` IS LOAD-BEARING. Both stale rules are (0,2,0), and one of
     them is later in the file than this section could be relied on to be; specificity settles it
     rather than source order, so this cannot be undone by appending a §37.
     ⚠ `min-height: 0` is half the fix: 19rem would floor the picture at 304px on a short window.
     ⚠ Do not "clean up" §29.4's or §35's declaration in place — §29.4's is the record of the
     round that shipped the band, and §35's nested block carries the tablet lesson in its comment.
     This is the register that wins; that is how every round in this file supersedes. */
  [data-skin="hanakenji"] .hero--threshold .hero-bleed {
    position: absolute; inset: 0; order: 0; z-index: 0;
    width: 100%; height: 100%; min-height: 0;
    object-fit: cover;
  }
  .hero-count--corner {
    grid-row: 2; order: 0;
    justify-self: end; justify-items: end;
    width: max-content; max-width: calc(100% - var(--gutter) * 2);
    margin: 0 var(--gutter) 0 0 !important;
    padding: 0 !important; border: 0 !important;
  }
  .hero-count--corner .hero-count-cells { gap: .95rem; }
  /* ⚠ A LOWER PLATEAU HERE, and it is arithmetic: on the phone the cluster sits INSIDE §36.2b's
     34%/.46 scrim, so the two washes composite. .52 over .44 is ~.73 of near-black under #fff,
     which is stronger than the desktop's .70 alone — at the desktop's value the pair read as a
     hard dark ellipse stamped on the photograph. Keep the two numbers in step. */
  .hero-count--corner::before {
    inset: -1.4rem -2.2rem -1.1rem -2.6rem;
    background: radial-gradient(closest-side ellipse at 52% 50%,
      rgba(var(--sk-corner-ink), .52) 0%,
      rgba(var(--sk-corner-ink), .48) 70%,
      rgba(var(--sk-corner-ink), .24) 88%,
      rgba(var(--sk-corner-ink), 0) 100%);
  }
  /* the phone's clearance is the HERO's padding-bottom (§30.1), not a margin on the cluster —
     both rows have to clear the dock stack. 16svh was 135px at 844 against a ~114px stack, so
     the cluster cleared the study tab by 21px once it moved into the corner above it. 18svh is
     152px there, and the clamp's floor rises with it. */
  [data-skin="hanakenji"] .hero--threshold { padding: 0 0 clamp(8.4rem, 18svh, 10rem); }
  /* §34.1's 66% band at .72 becomes a 34% band at .46. The clock no longer needs it — it has the
     wash — and the card is smoked glass carrying its own contrast floor (§25.3), so what is left
     here only seats the card's foot against the sand. */
  .hero--threshold::after {
    height: 34%;
    background: linear-gradient(to top,
      rgba(18, 15, 22, .46) 0%,
      rgba(18, 15, 22, .40) 38%,
      rgba(18, 15, 22, .18) 72%,
      rgba(18, 15, 22, 0) 100%);
  }
}

/* ── 36.7 · it arrives with the page ──────────────────────────────────
   §29.5's stagger, re-pointed and one beat later: the pill left this block for the card, so the
   cluster's own last cue is the caption rather than an action. */
[data-atelier~="motion"] .hero-count--corner .count-cell { animation: hkCount 1s var(--ease) both; }
[data-atelier~="motion"] .hero-count--corner .hero-count-cap { animation: hkCount 1s var(--ease) 1.2s both; }
.hero-count--corner .count-cell:nth-child(1) { animation-delay: .8s; }
.hero-count--corner .count-cell:nth-child(2) { animation-delay: .92s; }
.hero-count--corner .count-cell:nth-child(3) { animation-delay: 1.04s; }
.hero-count--corner .count-cell:nth-child(4) { animation-delay: 1.16s; }
@media (prefers-reduced-motion: reduce) {
  .hero-count--corner .count-cell,
  .hero-count--corner .hero-count-cap { animation: none !important; }
}
@media print {
  .hero-count--corner { display: none !important; }
}

/* ── 36.9 · the phone card and clock come DOWN, off the couple (2026-09-16, note 3) ────────
   The note: reduce the card and the countdown and move them down so they do not interfere with
   the couple. With §36.2c's fix the photograph finally covers the hero — and the couple are
   centred in `hero-tall.jpg`, so at a card top of 247/732 (34%) the glass sat squarely on them.
   Glass or not, §30.1's accepted trade ("they read THROUGH it") was written when the card crossed
   the couple's FEET at ~76% of a 736px box. It is not a licence to cover their faces.

   Two levers, and both are needed — shrinking alone leaves the top edge where it is, because
   §30.1 pins the rows to the FLOOR with `align-content: end`:
     · the card and the clock get smaller, so the stack is shorter and its top edge drops;
     · the hero's own padding-bottom comes in, which slides the whole stack down.
   ⚠ THE PADDING IS DOCK CLEARANCE and may not simply be spent. 18svh left 70px of measured air
   over the dock stack; **12svh leaves 36px**, which is the figure §36.2's desktop lift was derived
   to hit, and it is the floor. 🚫 Do not reduce it further without re-measuring — the docks cannot
   move (`DECISIONS.md`).
   ⚠⚠ AND THIS IS AS FAR AS LAYOUT GOES, so nobody spends another round on it. The stack is
   card(200) + gap(14) + clock(64) + clearance(88) = 366px of furniture in a 732px hero, and the
   couple run from roughly 35% to 70% of the frame. Clearing them ENTIRELY needs a card top below
   512px, which with that stack needs an 800px hero. **The crop cannot help either**: `hero-tall`
   is 4:5 in a 402 × 732 box, so `cover` scales by HEIGHT and crops the WIDTH only — the vertical
   half of `object-position` does nothing at this width (§34.1 says the same thing about the
   landscape frame). The couple are centred in the frame, so they are centred in the window, full
   stop. The card top went 34% → **49%**, which clears their faces and upper bodies and still
   crosses their legs. ⚠ **If that is not enough, the answer is a different PHOTOGRAPH** — and
   `hero-01.jpg` is still the folder's one unplaced portrait frame (`NEXT.md` → Still held).
   ⚠ The caption and the labels do NOT shrink: .78rem / .72rem are round four's measured floor
   (§27.4, §28.2). The FIGURES came down again, 1.45rem → 1.15rem, and the cells' gap with them.
   ⚠ The pill keeps `min-height: 44px` — §12's hit-target floor, and the gate fails a frame for it.
   Its horizontal padding is what gives way.
   ⚠ (0,4,0) on the names: §17's phone register for `.hero-names` is (0,3,0) through `[data-skin]`,
   so a (0,3,0) rule here would tie and depend on source order. */
@media (max-width: 47.99em) {
  [data-skin="hanakenji"] .hero--threshold { padding: 0 0 clamp(5.4rem, 12svh, 6.4rem); }
  [data-skin="hanakenji"] .hero--threshold .hero-paper {
    gap: .6rem;
    padding: 1.05rem 1.2rem;
  }
  [data-skin="hanakenji"] .hero--threshold .hero-say { gap: .4rem; }
  [data-skin="hanakenji"] .hero--threshold .hero-paper .hero-names { font-size: var(--step-3); }
  [data-skin="hanakenji"] .hero--threshold .hero-paper .hero-kana { font-size: var(--step-0); }
  [data-skin="hanakenji"] .hero--threshold .hero-paper .hero-line { font-size: var(--step-0); }
  [data-skin="hanakenji"] .hero-say .hero-do { margin-top: .25rem; }
  [data-skin="hanakenji"] .hero-say .hero-reply { padding: .55em 1.15em; }
  [data-skin="hanakenji"] .hero-count--corner .count-cell { font-size: 1.15rem; }
  [data-skin="hanakenji"] .hero-count--corner .hero-count-cells { gap: .7rem; }
  [data-skin="hanakenji"] .hero-count--corner::before { inset: -1.2rem -1.9rem -1rem -2.2rem; }
}

/* ═══ §37 · ROUND NINETEEN — THE CANVAS FEATURES STOP BEING DRAWN (2026-09-16) ═════════════
   The pass-2 images landed (`IMAGE-QA-pass2-hanakenji.md`). Most of that pass replaces CSS
   geometry and is handled where the geometry lives; this block is only the part that could not
   be, because a <canvas> takes its artwork through custom properties rather than through rules.

   `WSD.sprites` is the route, new in `engine/wsd.js` this round, and its contract is the whole
   reason this is safe to ship: a module reads a property holding url() values, keeps drawing its
   own geometry, and switches over when the files decode.
   ⚠ **THE PATHS IN THIS BLOCK ARE ROOT-RELATIVE — NO `../` — UNLIKE EVERY OTHER `url()` IN THIS
   FILE.** A custom property's value is literal text: the browser does NOT resolve it against the
   stylesheet, so `WSD.sprites` resolves it against `WSD.paths.root` instead. The first wiring of
   this shipped stylesheet-relative `../assets/…` and **every file 404'd silently** — the drawn
   fallback covered for it so completely that the pond still looked finished. 🚫 Do not "correct"
   these paths to match the rest of the file.
   ⚠ **NOTHING HERE WAITS ON A NETWORK.** A 404, a slow connection or a site that declares no
   sprites at all still gets the drawn render. That is why every drawn fallback below stays in
   the file and why deleting one is a regression rather than a cleanup.
   ⚠ **A photograph carries its own colour** (§19.7's lesson), so the colour tokens these features
   used to be driven by go INERT the moment the sprites arrive. They are kept as the fallback's
   palette. 🚫 Do not "clean up" `--petal-near`, `--pond-koi-a` and their siblings.
   ⚠ Both features are <canvas>, so **the gate cannot see either of these renders** — a canvas
   reads back blank (§20.2). Judge them by eye in `Device Preview`, never from a capture.

   ── 37.1 · the petals are photographs ──────────────────────────────────────────────────────
   `petals.js` drew each petal as a bezier lozenge with an ellipse bitten out of the tip. At the
   8–22px they actually fall at, that is a pink blob — and they are on ALL FOUR PAGES, ALWAYS,
   which is what made three sprites the highest-leverage spend in the pass:
     petal-01  flat and open, face-on   ·   petal-02  curled, one edge rolling over
     petal-03  near edge-on, a narrow sliver

   ⚠ **The three SILHOUETTES are the point, not three colours.** The engine now tumbles each
   sprite through its own plane as it falls, so a petal passes edge-on and comes back; three
   genuinely different outlines at independent tumble phases is what stops the fall reading as
   one shape repeated fifty-two times. Three near-identical ovals would have given the blobs back.
   ⚠ **The three files share ONE 352px long axis**, normalised in the lab on import. The engine
   draws the sprite BOX and `--petal-size` is a RADIUS, so the long axis is twice it — the
   delivered files were ×1.40 / ×1.68 / ×1.64 apart and would have fallen at three different
   sizes on top of the depth ladder that is meant to be the only thing varying them.
   ⚠ **`--petal-size` is unchanged at 6.5.** A photographed petal reads smaller than a solid fill
   of the same box because its edges go translucent to bone, so the temptation is to raise it.
   Resisted: §19's 112px floor is about CUT-OUTS PLACED AS MARKS, and these are ambient at ~13px.
   Judge it on a real page before touching it. */
[data-skin="hanakenji"] .a-petals {
  --petal-sprites:
    url("assets/hanakenji/petal-01.webp")
    url("assets/hanakenji/petal-02.webp")
    url("assets/hanakenji/petal-03.webp");
}

/* ── 37.2 · the pond is photographs ─────────────────────────────────────────────────────────
   TRAVEL ONLY, and the weakest thing on the site until now: `pond.js` drew each koi as an
   ellipse with a triangle for a tail, on a two-stop gradient.

   Three properties, three jobs:
     --pond-water-img     `pond-water.jpg`, drawn `cover` in place of the gradient. Briefed with
                          NO strong feature anywhere, because the canvas covers it at any aspect
                          and a bright patch becomes a permanent blemish in the pool.
     --pond-koi-sprites   the three fish. ⚠ **Seven koi out of three files is the intended case**
                          — the engine draws `data-wsd-count` of them from any set, and the depth
                          ladder keeps the repeats from reading as clones. That is why the koi
                          budget went from four fish to three: the fourth bought nothing.
     --pond-blossom-img   `pond-blossoms.webp`, one slowly drifting layer ABOVE the fish, because
                          petals float and koi are under the water.

   ⚠ **A straight fish is a hard requirement of the file, not a style note** — the engine steers
   by rotating the sprite about its centre, so a koi photographed mid-turn swims permanently
   sideways. All three were checked straight, level and head-up before import.
   ⚠ The sprites are photographed **head to the top of the frame** and the engine's heading is
   canvas-standard (0 = +x, pointing right), so the module applies a **quarter turn**. That
   correction lives in `pond.js`; it is noted here because a future sprite delivered nose-LEFT
   would need the engine changed, not this block.
   ⚠ `.pond` keeps its `inset` box-shadow: it is the pool's lip, and the photograph does not
   carry one (no baked shadows anywhere in the pass). */
[data-skin="hanakenji"] .pond {
  --pond-water-img: url("assets/hanakenji/pond-water.jpg");
  --pond-koi-sprites:
    url("assets/hanakenji/koi-kohaku.webp")
    url("assets/hanakenji/koi-yamabuki.webp")
    url("assets/hanakenji/koi-platinum.webp");
  --pond-blossom-img: url("assets/hanakenji/pond-blossoms.webp");
}

/* ── 37.3 · the crown and the post, RE-CUT off the pass-2 gate ───────────────────────────
   §19.4's standing rule is that A NEW CROWN MEANS A NEW POST, and the pass-2 gate made both
   overdue: `torii-crown-red.png` and `torii-post-red.png` were cut from the pass-1 file, so
   §26's four numbers were percentages of derivatives of a source that had been replaced.

   ── the crown: a SEVEN-slice, not §26's three ────────────────────────────────────────
   Source: gate rows 199–660, uniform scale 0.7706 (the cut row's 109px upright → 84px), out at
   1600 × 356. Natural scale for everything with a silhouette to protect — **both uprights and
   the gakuzuka** — and the horizontal beams either side of them resampled along their own grain:
     the kasagi's overhang  ×0.51  (67% of natural) — COMPRESSED
     the two beam spans     ×2.04  (265% of natural) — STRETCHED
   ⚠ **§26's 3-slice would have doubled the gakuzuka.** Pass-1's gate had no centre strut (it was
   the loose-lumber file), so a single stretched middle cost nothing; the new gate has one, dead
   centre, and a ×2 middle stretch makes it visibly fat. Protecting it is why there are seven
   slices and not three. 🚫 Do not simplify this back to a 3-slice.

   ── what it cost, honestly ──────────────────────────────────────────────────────
   The pass-2 gate's kasagi overhangs its uprights by 292px of a 425px half-width — 69% of the
   distance from centre to tip is OUTSIDE the upright, where pass-1's plank was simply cut off at
   the frame. That geometry cannot give §26's −1.337% (uprights straddling the picture's edge) at
   any k the rail clearance allows: it needs k ≈ 2.06 against §19.4's measured ceiling of 1.25.
   So the overhang is compressed, and how hard is a judgement:
     · ×0.39 → keeps §26's −1.337% exactly, and squashes the upturned tip to half width
     · ×0.57 → lands on §19.4's sumi geometry (+2.38%), the gate standing inside the picture
     · ×0.51 → **taken.** +1.25%: still reading as the picture's border, tip at 67% of natural.
   ⚠ The kasagi's tips now reach x=0 and x=1599 of the crown file — **touching the edge, not cut**
   (measured: 5px of tip at row 0, from 9px in the source). They land outside the plate anyway,
   the crown being 20% wider than it, and §19.4 forbids an `overflow` clip that would hide them.

   ── the post: cut from the crown, as the rule requires ───────────────────────────────
   The crown's free upright below the nuki is 84 rows (FOUND by walking up from the last row while
   it still reads as two narrow runs — not assumed). Per-row resampled to a constant 85px, rows
   REVERSED so the tile's top row is the joint, then scaled into a 938 × 307 tile, flush left.
   ⚠ **Row 0 is written in as raw bytes, not blitted.** `drawImage` interpolates a 1px source band
   even at 1:1 and left a **58/255 alpha step** at the seam on the first attempt — exactly the
   1px seam this cut exists to avoid. Verified after the fix: **max Δrgb 0, max Δalpha 0.**
   ⚠ The tile's aspect is held at the old file's 300/916 so the vertical GRAIN SCALE is unchanged;
   only the width ratio moved, which is why `background-size` is now 1103.5% (938/85). */


/* ═══ §38 · ROUND TWENTY — THE PETALS, THE CHAPTER PLATE AND THE PHONE HERO (2026-09-17) ═══
   Five notes from the user, read on a real phone. Three of them are here; the torii gates and
   the page backdrops wait on files. Nothing below deletes an earlier rule — every one of these
   supersedes by being later and at least as specific, which is how this file has always worked.

   ── 38.1 · the petals get BIGGER and FEWER ────────────────────────────────────────────────
   §37.1 held `--petal-size` at 6.5 and said "judge it on a real page before touching it". Judged:
   at ~13px the photographs read as specks, and fifty-two of them read as static. So the pass is
   four numbers, and they only work together:
     · size 6.5 → 11 (a ~22px long axis), the sprite finally showing its silhouette;
     · count 46 → 30 on all four pages — the SAME ink on the screen, spent on fewer, larger
       petals. Raising the size alone would have crowded the photograph.
     · `petals.js`: the alpha ladder 0.32 + depth·0.5 → **0.52 + depth·0.4** (the far petals were
       ghosts at 0.32; the near ones are unchanged at ~0.9);
     · `petals.js`: the tumble's foreshortening floor 0.14 → **0.46**. THIS WAS THE REAL FAULT —
       a petal passing edge-on went to 14% of its width, which at 13px is a 2px sliver, and a
       third of the fall was sliver at any moment. It still tumbles; it never disappears.
   ⚠ `--petal-size` is declared HERE on `.a-petals` (0,2,0), not edited at §37.1's token — the
   engine reads the property off the canvas, so this is the declaration that reaches it.
   ⚠ A canvas READS BACK BLANK at the gate (§20.2/§37). Judge this in `Device Preview`, by eye. */
[data-skin="hanakenji"] .a-petals { --petal-size: 11; }

/* ── 38.2 · the chapter plate stops being a white slab (2026-09-17, note 3) ────────────────
   The note: the Story headings sit on an opaque block that hides the photograph under them.
   §31.2's header is right that 90% paper is near-opaque and that "10% is the whole budget" —
   FOR A PLAIN ALPHA CHANGE. The budget is only 10% because the plate must hold --ink at 4.5:1
   over whatever the photograph does, and the worst case is pure black.

   `backdrop-filter` is what buys the other 40%, and the load-bearing function is `contrast(.55)`,
   not the blur and not `brightness`: **brightness MULTIPLIES**, so it cannot lift a near-black
   ground at all (0 × 1.06 = 0). Contrast below 1 pulls every channel toward mid-grey — black
   arrives at ~57/255 — and 50% paper over THAT measures **~4.6:1** against --ink. Legal, and the
   picture reads straight through the plate.
   ⚠ The fallback is not the old 90%: a browser without `backdrop-filter` gets **72% flat paper**
   (measured floor over pure black, 5.9:1), which is visibly lighter than before and still legal
   with no filter at all. §31.2's rule stays exactly as written for ≥64em.
   🚫 Do NOT tune the veil per chapter with `:nth-of-type()`. The chapters are seeded data and the
   page swaps the whole set after the wedding (`site.js` → `story` vs `forever`), so which ordinal
   is the dark one changes with the phase. The fix has to be phase-blind, and this one is.

   ⚠⚠ **THE PLATE CARRIES FOUR INKS, NOT ONE, AND THREE OF THEM ARE NOT `--ink`.** The first cut
   of this block measured `--ink` only and shipped two illegible elements — caught at the gate on
   chapter 02 (the ramen bar), whose left two-thirds is near-black. Both grounds re-measured, the
   worst case being a pure-black photograph:
     filtered branch  black → contrast(.55)/brightness(1.06) → 61/255 → under 50% paper the plate
                      renders **rgb(155, 153, 150)**
     fallback branch  72% flat paper over black → **rgb(180, 177, 172)**
   Measured, every element, both grounds (filtered / fallback):
     `.chapter h2`      `--ink` rgb(43,39,51)     **5.13 / 6.82** ✅ unchanged
     `.chapter-no`      was `--accent-ink` rgb(168,59,85)  2.16 / 2.87 ❌ — fails even 3:1
     `.chapter-date`    was `--accent-ink`                 2.16 / 2.87 ❌ at 13.4px
     `.chapter-blurb`   was `--muted` rgb(98,94,102)       2.23 / 2.97 ❌ at 20.6px
   So the two non-ink inks are re-pointed FOR THIS REGISTER ONLY (≥64em keeps the cherry and the
   muted grey on their 90% plate, where they were always legal):
     `.chapter-no`      `--accent-ink` 50% / `--ink` → rgb(104,52,68)  **3.42 / 4.54** ✅
     `.chapter-date`    `--ink`                                        **5.13 / 6.82** ✅
     `.chapter-blurb`   `--ink`                                        **5.13 / 6.82** ✅
   ⚠ **The number rides the 3:1 headline allowance and nothing else does.** It computes to
   39.99px — over WCAG's 24px large-text line — so 3.42 is legal there and would NOT be on the
   13.4px date beside it. That is the whole reason the two are split rather than given one colour.
   ⚠ **The cherry cannot survive at any mix.** Even `--accent-ink` 20% / `--ink` measures 4.41 on
   the filtered ground — short of 4.5 — so the date and the blurb go to flat `--ink` rather than
   to a tinted near-ink that only looks like it was solved. Hierarchy on this plate is carried by
   SIZE below 64em, not by three greys.
   ⚠ Re-measure against **rgb(155,153,150)** and **rgb(180,177,172)** if the veil, the filter or
   the palette ever moves. Those two numbers are the whole contract. */
@media (max-width: 63.99em) {
  [data-skin="hanakenji"] .chapter--frame > .chapter-plate {
    background: color-mix(in srgb, var(--paper) 72%, transparent);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
  @supports (backdrop-filter: blur(2px)) {
    [data-skin="hanakenji"] .chapter--frame > .chapter-plate {
      background: color-mix(in srgb, var(--paper) 50%, transparent);
      backdrop-filter: blur(8px) contrast(.55) brightness(1.06) saturate(.8);
      -webkit-backdrop-filter: blur(8px) contrast(.55) brightness(1.06) saturate(.8);
    }
  }
  [data-skin="hanakenji"] .chapter--frame > .chapter-plate .chapter-no {
    color: color-mix(in oklab, var(--accent-ink) 50%, var(--ink));
  }
  [data-skin="hanakenji"] .chapter--frame > .chapter-plate .chapter-date,
  [data-skin="hanakenji"] .chapter--frame > .chapter-plate .chapter-blurb {
    color: var(--ink);
  }
}

/* ── 38.3 · the phone hero swaps its rows (2026-09-17, note 4) ─────────────────────────────
   The note: on the phone the card belongs at the BOTTOM, inset with gutters and lifted off the
   floor; the clock belongs TOP-RIGHT under the noren, smaller, slightly transparent.

   §30.1 and §36.9 placed both by resting them on the hero's floor (`align-content: end`, two auto
   rows). That cannot separate them — the two are always adjacent. So the rows become explicit,
   **`auto minmax(0, 1fr)`**, and `align-content` stops being the thing that places anything: the
   clock is row 1 pinned `start`, the card is row 2 pinned `end`, and the 1fr row between their
   edges is the photograph's clear air.
   ⚠ **The clock clears the noren by a PERCENTAGE of the hero's width, not a px value.**
   `.noren--hung` is `left: 18%; right: 3%` at `aspect-ratio: 1600/500`, so its foot hangs at
   **24.7% of the hero's width** — 96px at 390, 99px at 402. `margin-top: 29%` leaves ~17px of air
   under it and STAYS in step at every phone width, because it is the same kind of number. A px
   margin would drift against the banner on every device.
   ⚠ The margin is written as the full shorthand with `!important`: §36.7's rule is
   `margin: 0 var(--gutter) 0 0 !important`, and a plain `margin-top` loses to it.
   ⚠ The figures take the reduction AGAIN (1.15rem → .98rem). The cap at .78rem and the labels at
   .72rem are round four's measured floor (§27.4, §28.2) and are NOT cut.
   ⚠ `opacity: .92` is the asked-for "tiny bit" of transparency, and it is on the cluster so the
   §36.7 scrim behind it fades with the digits — put it on the digits alone and the dark ellipse
   stays at full strength with paler type on it, which is worse than either.
   ⚠ **The hero's padding-bottom goes back UP**, and this is arithmetic, not taste: §36.9's
   `clamp(5.4rem, 12svh, 6.4rem)` (88px at 732) was derived for a card FLOATING at 47–76% of the
   hero, where the clock below it carried the dock clearance. With the card standing on the floor
   the card itself has to clear the ~114px dock stack, so **`clamp(7.6rem, 17svh, 9.6rem)`**
   (~124px at 732). 🚫 Re-measure before changing it; the docks cannot move (`DECISIONS.md`).

   Tablet and desktop keep their pairing across the frame — the card just stops floating: it drops
   to §36.10's floor in column 1, opposite the clock in column 2, which is untouched. */
@media (max-width: 47.99em) {
  [data-skin="hanakenji"] .hero--threshold {
    grid-template-rows: auto minmax(0, 1fr);
    padding: 0 0 clamp(7.6rem, 17svh, 9.6rem);
  }
  [data-skin="hanakenji"] .hero--threshold > .hero-count--corner {
    grid-row: 1; align-self: start;
    margin: 29% var(--gutter) 0 0 !important;
    opacity: .92;
  }
  [data-skin="hanakenji"] .hero--threshold > .hero-paper {
    grid-row: 2; align-self: end;
  }
  [data-skin="hanakenji"] .hero-count--corner .count-cell { font-size: .98rem; }
}
@media (min-width: 48em) {
  [data-skin="hanakenji"] .hero--threshold > .hero-paper { align-self: end; }
}

/* ═══ §39 · ROUND TWENTY, PART TWO — THE PAGE BACKDROPS (2026-09-17) ═════════════════
   One ink-wash motif per page, held at ~8% over the asanoha tooth: Fuji on home, a blossom bough
   on Story, the coast on Travel, one pressed blossom on Reply. Four pages that used to share one
   paper ground now each have a quiet horizon of their own, and it costs one div and one rule.

   ⚠ **THE LAYER LIVES OUTSIDE `<main>`, AND THAT IS NOT A PREFERENCE.** `a-page-in` holds an
   identity transform for the life of the page, and a transform makes its element the containing
   block for fixed descendants — a `.pagebg` inside main would pin to main and scroll with it.
   It is the first child of `<body>` on all four pages. (`NEXT.md` → Traps, first entry.)
   ⚠ **`z-index` is deliberately NOT set.** A fixed, untransformed `z-index: auto` box paints in
   DOM order against `main`, which is transformed and therefore also paints at the 0 level — so
   "first in the body" IS the stacking rule here. 🚫 A negative z-index would drop it behind the
   body's own background and the tooth would hide it completely.
   ⚠ **8% is not a starting point to tune by eye at full strength.** An ink wash at .08 over cream
   is a few values of grey; judged in a viewer at 100% every one of these looks far too strong.
   Judge them on the page.
   ⚠ **No paper ground is baked into any of the four files** — measured on import, all four corners
   alpha 0 and zero matte pixels across all ten deliveries. A future re-roll must hold that: a file
   with its own cream ground paints an opaque rectangle here and kills the tooth underneath.

   ── the vertical correction, and why it is a transform ──────────────────────────────
   Fuji and the coast are bottom-anchored, and **both arrived with empty frame below the ink** —
   the mountain's last row is 996 of 1200 and the coast's is 1002 of 1200. Anchored `bottom`, the
   motif would float **16.9%** / **16.4%** of the layer's height above the fold. The files are NOT
   re-cropped for it: they are WebP on alpha and this lab's encoder would have to re-compress them
   to fix a number CSS can express exactly. `translateY` of the measured fraction is free and
   reversible. ⚠ Re-measure it if either file is re-rolled.

   ── what each one is ───────────────────────────────────────────────────
     fuji     2000 × 1200, full width, bottom-anchored. The horizon of the whole site.
     coast    2000 × 1200, the same treatment on Travel — deliberately the same SHAPE as Fuji so
              the two pages rhyme rather than compete.
     bough    1400 × 1800, hanging from the TOP-RIGHT corner, capped in px so it stays an
              ornament on a wide screen instead of growing into a mural.
     blossom  900 × 900, one flower, bottom-right, the smallest and quietest of the four — Reply
              is the site's most functional surface and §19's allocation gives it the least.
   ⚠ The two corner motifs take **.07**, a hair under the pair of washes: they are small and dense,
   and at .08 the bough read as a smudge in the corner rather than as ink. */
[data-skin="hanakenji"] .pagebg {
  position: fixed;
  pointer-events: none;
  opacity: .08;
  background-repeat: no-repeat;
}
[data-skin="hanakenji"] .pagebg--fuji,
[data-skin="hanakenji"] .pagebg--coast {
  inset: auto 0 0 0;
  width: 100%; aspect-ratio: 2000 / 1200;
  background-size: 100% auto;
  background-position: center bottom;
}
[data-skin="hanakenji"] .pagebg--fuji {
  background-image: url("../assets/hanakenji/bg-fuji.webp");
  transform: translateY(16.9%);
}
[data-skin="hanakenji"] .pagebg--coast {
  background-image: url("../assets/hanakenji/bg-coast.webp");
  transform: translateY(16.4%);
}
[data-skin="hanakenji"] .pagebg--bough {
  inset: 0 0 auto auto;
  width: min(46vw, 30rem); aspect-ratio: 1400 / 1800;
  background-image: url("../assets/hanakenji/bg-bough.webp");
  background-size: 100% auto;
  background-position: right top;
  opacity: .07;
}
[data-skin="hanakenji"] .pagebg--blossom {
  inset: auto 0 0 auto;
  width: min(34vw, 15rem); aspect-ratio: 1 / 1;
  margin: 0 clamp(1rem, 4vw, 4rem) clamp(4rem, 10vh, 8rem) 0;
  background-image: url("../assets/hanakenji/bg-blossom.webp");
  background-size: 100% auto;
  background-position: center;
  opacity: .07;
}
/* 🚫 The backdrop does not print. It is a page's atmosphere, and a printed page has the paper's. */
@media print { [data-skin="hanakenji"] .pagebg { display: none !important; } }

/* ── 39.1 · ⚠ THE BACKDROP MOVED THE GROUND, SO `--faint` IS RE-POINTED ────────────────
   §39 shipped without this and the gate caught it — the same fault as §38.2's, one step later in
   the same round, and the standing decision written for §38.2 (*A veil measured against `--ink`
   has not been measured*) is exactly what covers it. **A new ground is a re-measurement of every
   ink standing on it.**

   Measured by compositing each of the four WebPs at its own layer opacity over `--paper` and
   taking the DARKEST resulting ground, then reading the resolved oklab inks against it:

     ground                       --ink    --muted   --faint (62%)
     bare paper  rgb(250,246,239) 13.53     5.89      4.64
     bg-fuji @.08 rgb(234,231,224) 11.80     5.13      **4.05** ❌
     bg-coast @.08 rgb(230,227,220) 11.37    4.95      **3.90** ❌
     bg-bough @.07 rgb(233,229,223) 11.62    5.05      **3.98** ❌
     bg-blossom @.07 rgb(233,229,223) 11.62  5.05      **3.98** ❌

   `--ink` and `--muted` keep a wide margin. **`--faint` had 0.14 of margin on bare paper and the
   wash spends 0.59 of it**, and it is live under real small type: `.sched-note` and the `.marg`
   lines sit directly over where the layer paints.

   ⚠⚠ **LOWERING THE OPACITY CANNOT FIX THIS, and that is the useful part.** The coast's darkest
   ink is effectively alpha-1 near-black, so at `opacity: .03` the ground is still rgb(243,239,232)
   and 62% `--faint` is still **4.36** — by which point the motif is invisible. **The ink is the
   lever, not the veil.** 🚫 Do not "fix" a backdrop contrast finding by fading the backdrop.

   So `--faint` goes **62% → 68% sumi**, measured as the floor that clears the worst of the four:
   coast **4.65** · bough/blossom 4.75 · fuji **4.83** · bare paper **5.54**. (66% was tried first
   and leaves the coast at 4.38.)

   ── 39.1b · ⚠ AND `--muted` MOVES WITH IT, BECAUSE THE RAMP IS THE POINT ──────────────
   The first cut of 39.1 asserted "§A2's ramp survives: 70/68 is still a visible step" and **did
   not measure it**. It is not a step at all — the gate caught that too, and it is the THIRD
   unmeasured assertion in this round, which is why the standing decision is worth re-reading
   rather than re-learning.

     muted 70%  rgb(98, 94,102)  oklab L 0.4884
     faint 68%  rgb(102,98,106)  oklab L 0.5026   → ΔL **0.0142**, **1.06:1**, 4/255 a channel.

   The same grey. §A2's own header names the step as load-bearing — "the ramp survives because
   70 / 62 is still a visible step" — and it is live: the ledger's three-tier read (uppercase
   label `--faint` / value `--ink` / sub-line `--muted`) and eight other adjacent pairs on home
   alone had collapsed to two tiers.

   So `--muted` goes **70% → 76% sumi**, solved to restore the ORIGINAL step rather than picked:

                        oklab L    ΔL to faint   ratio     on coast   vs --ink
     was  70 / 62        .4884       .0558        1.269       4.95       2.299
     now  **76 / 68**    .4492       **.0533**    **1.259**   **5.86**   1.941

   ΔL lands within **4.5%** of what it was, and every other number goes UP: `--muted` on the
   worst ground 4.95 → 5.86. The one number that falls is `--muted` against `--ink` (2.30 → 1.94),
   which is not a contrast requirement — they are never adjacent as small type on each other, and
   ΔL between them is 0.167, an enormous step.
   🚫 **Do not move one of these two without the other.** They are a ramp, not two values.
   ⚠ Declared on `[data-skin]`, which is `<html>` — so `.band--ink`'s own re-point (§23.1) and
   `.band--sink`/`.reply-ask`'s 66% (the audit fix) are untouched: those set the property on
   THEMSELVES and their subtrees inherit it. Different elements, no cascade conflict.
   ⚠ A band with an OPAQUE background hides the backdrop completely, which is why those two
   registers did not need re-measuring against it. */
[data-skin="hanakenji"] {
  --muted: color-mix(in oklab, var(--sumi) 76%, var(--paper));
  --faint: color-mix(in oklab, var(--sumi) 68%, var(--paper));
}

/* ═══ §40 · ROUND TWENTY, PART THREE — THE WHOLE GATE (2026-09-17) ════════════════════
   The user's note: one gate image per picture aspect, **the photograph sitting fully INSIDE the
   gate**. That retires the whole crown+post pipeline on these two plates — §19.4's 3-slice,
   §26's four constants and §37.3's 7-slice re-cut — by no longer matching. Nothing is deleted:
   `torii-crown-red.png` / `torii-post-red.png` stay because §24.4's rail mask still reads
   `torii-gate-red.png`, and §37.3 remains the record of how they were cut.

   ── what arrived, and what the lab had to do ─────────────────────────────────
   The brief's contract was on the OPENING — the clear area between the posts had to be the
   picture's aspect at top .24 / bottom .06 / sides .11, so file = opening × (1.22 w, 1.30 h).
   **Round one of the gates missed it on two of three** (`IMAGE-QA-round20.md` has the numbers:
   openings of 0.98 and 0.45 against 1.333 and 0.800, and gates **1.87×** and **2.64×** their own
   opening — an 878px gate around a 390px plate). The corrected pair fixed the disqualifying half:

     file                 opening aspect   needed   gate ÷ opening
     torii-gate-43         1.093           1.333    **1.535**
     torii-gate-169        1.756           1.778    **1.532**   (accepted first time)
     torii-gate-45         0.588           0.800    **1.589**

   ⚠ **The opening aspect is still not the picture's, and it is ACCEPTED rather than unnoticed.**
   The consequence is only that the posts stand a little wider of the picture than briefed — the
   gate is placed off its opening's WIDTH and its nuki's UNDERSIDE, so a taller opening simply
   means more post below, and the lab cut that to a uniform hairline (below).
   ⚠ **`gate ÷ opening` is the number that decides a gate**, and ~1.53 is the shipped value on all
   three — which is not a coincidence, it is §19.4's own geometry: the retired crown drew at
   **150%** of the plate and the band has clipped its tips ever since. So this route inherits a
   clearance problem the page already solved. 🚫 A future gate over ~1.6 does not fit.
   ⚠ **All three were cropped to content with their legs cut to 1.0% of the picture's height.**
   As delivered the posts ran to the file's bottom edge, which is 22–36% of the picture's height of
   free-standing leg — and unequal between the three, which on Travel would have been two different
   gates on one page at two widths. One uniform cut is what makes them a family.

   ── the geometry, measured off the cropped files ──────────────────────────────
   `.torii` is `inset: 0` on `.torii-frame` (§19.4), so with the frame's padding dropped its box IS
   the picture. The gate is then three numbers, all fractions of the PICTURE:
     --sk-gate-w   the gate's width, as % of the picture's width
     --sk-gate-x   the opening's left edge, negated — % of the picture's WIDTH
     --sk-gate-y   the nuki's underside, negated — % of the picture's HEIGHT (`top` resolves
                   against height, so this one is NOT the same denominator as the other two)
     --sk-gate-lift  the figure's own `margin-top`, to clear the lintel. ⚠ A percentage margin
                   resolves against **WIDTH**, so it is `--sk-gate-y × (picture h / picture w)`:
                   43 → 40.24 × .75 = **30.18%** · 169 → 51.12 × .5625 = **28.76%** ·
                   45 → 27.55 × 1.25 = **34.44%**. Re-derive it, never copy it between plates.
   ⚠ **`--sk-gate-y` differs wildly between the three (27–51%) and that is correct**: it is the
   kasagi-to-nuki gap measured against three different picture heights, not a style choice.

   ── the traps this route walks into ──────────────────────────────────────
   ⚠ **`max-width: none` is load-bearing** — `engine.css` caps every img at 100% of its container,
   which would draw the gate at the picture's width and put the posts inside the photograph.
   §19.4 paid for this once already.
   ⚠ **The selector reaches through `<picture>`.** Travel's gate is a `<picture>` (two files, one
   query) and `<picture>` is an inline box that boxes nothing — so the rule targets
   `.torii--whole img`, never `> img`. §35.2's identical trap on the photograph is the precedent.
   ⚠ **`.torii-frame`'s padding goes to 0.** §19.4's `11%` and §35.2's `3%` existed to hold the old
   crown's overhang INSIDE the frame; a whole gate overflows the frame instead and the figure's
   `margin-top` buys the room. Both old declarations are left in place as records — this one is
   later and more specific.
   ⚠ **`.torii` MUST keep its `z-index`** (§19.4). `[data-atelier~="toned"]` filters every plate
   image, a non-none filter makes a stacking context, and the picture then outranks a preceding
   absolutely-positioned sibling at `auto`.
   ⚠⚠ **`.a-mask` ON THE FIGURE WAS CLIPPING 56.6% OF THE GATE AWAY, AND §19.4 PREDICTED IT.**
   The first cut of §40 shipped with both figures still carrying `a-mask`, and
   `atelier.css` §L has `[data-atelier~="motion"] .a-mask { overflow: clip }` — whose box IS the
   picture. So the lintel, the nuki, both uprights and both kasagi tips did not paint at all: the
   gate rendered only where it overlapped the photograph's own rectangle, the exact inverse of
   this round's goal. Measured at 924: 249.4px cut above, 230.2 left, 230.9 right, gate visible
   fraction **0.434**. §19.4's own comment names the failure mode — "clip the frame and the cut
   lands on the plate's edges, exactly where the uprights are" — it just did not know `a-mask`
   was a second clipper on the same box.
   **Fixed in the MARKUP, not here**: `a-mask` moved off the figure and onto the photograph, using
   the engine's own wrapper-less variant (`img.a-mask`, atelier.css §L and §reveal both carry it).
   The reveal wipe is unchanged; the figure is no longer a clipping box.
   ⚠ The one trade: `img.a-mask`'s 1.07 over-scale is no longer contained by a wrapper, so during
   the reveal the photograph spills ~3.5% each side over transparent gate area. Accepted — it is
   brief, it is over alpha, and containing it means re-introducing the clip that broke the gate.
   🚫 **Do not put `a-mask`, `overflow`, or `clip-path` back on `.plate--torii` or `.torii-frame`.**
   ⚠ **§23.4's `.band--gate` is NOT in this chain** — an earlier draft of this header claimed it
   held the side overhang and that was wrong: Travel's band is a plain `.band` at
   `overflow-x: visible`. What actually absorbs the 26.7–29.5%-per-side overhang is **`BODY`'s own
   `overflow-x: clip`**. ⚠ Measure `documentElement.scrollWidth` at 390 / 834 / 1024 / 1440 after
   any change here; the acceptance gate fails a frame for horizontal overflow. */
[data-skin="hanakenji"] .plate--torii .torii-frame { padding-top: 0; }
[data-skin="hanakenji"] .plate--torii { margin-top: var(--sk-gate-lift); }
[data-skin="hanakenji"] .torii--whole img {
  position: absolute;
  width: var(--sk-gate-w);
  max-width: none;
  height: auto;
  left: var(--sk-gate-x);
  top: var(--sk-gate-y);
}
[data-skin="hanakenji"] [data-sk-gate="43"] {
  --sk-gate-w: 153.45%; --sk-gate-x: -26.73%; --sk-gate-y: -40.24%; --sk-gate-lift: 30.18%;
}
[data-skin="hanakenji"] [data-sk-gate="169"] {
  --sk-gate-w: 153.18%; --sk-gate-x: -26.55%; --sk-gate-y: -51.12%; --sk-gate-lift: 28.76%;
}
/* ⚠ The SAME query as the photograph's `<source>` on that plate. If one moves, both move. */
@media (max-width: 47.99em) {
  [data-skin="hanakenji"] [data-sk-gate="169"] {
    --sk-gate-w: 158.94%; --sk-gate-x: -29.47%; --sk-gate-y: -27.55%; --sk-gate-lift: 34.44%;
  }
}

/* ═══ §41 · ROUND TWENTY-ONE (2026-09-17) — THE GATE GOES BACK TO CODE ═══════════════
   Two notes this round land here. Both are gated the way every round since §24 has been: by a
   class or a media query, so nothing above is edited and nothing is deleted.

   ── 41.1 · the torii is DRAWN again, and it is a cylinder ──────────────────────────────────
   The note: *"Torii looks bad still. Revert to the one made by code. Make it have a cylindrical
   post with shine. Red is the color."* So §40's whole-gate PNGs stop being drawn on both plates
   (home's invitation, Travel's approach) — `.torii--whole` → **`.torii--drawn`**, which retires
   §40's `.torii--whole img` rules by no longer matching. 🚫 Nothing is deleted: the three
   `torii-gate-*.png` files stay in `assets/`, §24.4's rail mask still reads
   `torii-gate-red.png`, and §19.4 / §26 / §37.3's crown-and-post pipeline stays on record.

   ⚠ **This is NOT §19.4's retired drawn gate coming back.** That one was five FLAT tinted bars,
   and the fault that retired it (2026-09-06) was that its uprights read as two pink stripes.
   The fix for that fault is the note's own instruction, and it is optical, not geometric: every
   member here is a CYLINDER, lit from the left, with a specular highlight and a dark terminator.
     · an upright is a vertical cylinder → the gradient runs ACROSS its width (90deg)
     · a beam is a horizontal cylinder → the gradient runs DOWN its height (180deg)
   🚫 Do not "simplify" either gradient to two stops. The three-value read — shadow edge, body,
   highlight — IS the roundness; a two-stop ramp is a flat bar with a sheen painted on it.

   ── the geometry, in `cqw`, so it holds at every plate width and every picture aspect ────
   `.torii-frame` is `container-type: inline-size` (§19.4) and §40 dropped its padding, so its box
   IS the picture and **1cqw = 1% of the picture's width**. Everything below is therefore one set
   of numbers for all three aspects — which is the whole reason a drawn gate does not need
   §40's three per-file registers (`--sk-gate-w/x/y` go unused; they stay for the PNG route).

   ⚠⚠ **THE GATE IS DRAWN INSIDE THE PICTURE'S WIDTH, AND THAT IS MEASURED, NOT PREFERRED.**
   The first cut of §41 stood the posts entirely OUTSIDE the photograph, at -7.6cqw — §40's
   contract, "the picture fully inside the gate". Measured on the real page at 924: the plate is
   **867px wide inside a 909px body**, so there are **21px — 2.4cqw — of room each side**, and a
   61px post ran 75% off the screen with `BODY`'s `overflow-x: clip` eating it. That is very
   probably what "the torii looks bad" has been describing all along: on a wide plate an
   overhanging gate is a gate with no uprights. So the uprights stand ON the photograph's two
   edges (5.6cqw each, ~6% of the picture a side) and only the kasagi's tips overhang, by 2cqw —
   inside the 2.4 that actually exists.
     kasagi   top -13cqw · height 4.6cqw · -2cqw → 102cqw   (the only member that overhangs)
     shimaki  top -8.4   · height 2.2    · -0.6 → 100.6
     gakuzuka top -6.2   · height 5.0    · centred, 2.2 wide
     nuki     top -4.9   · height 3.6    · 1.5  → 98.5
     posts    top -11 (tucked BEHIND the kasagi) · bottom -1% of the picture's height
              width 5.6cqw, flush to the picture's left and right edges.
   ⚠ **Nothing crosses the middle of the photograph** — the nuki's underside stops 1.3cqw above
   the picture's top edge, so the only overlap is the two uprights on the outer 6% a side.
   ⚠ `--sk-gate-lift: 15%` (§40's `.plate--torii { margin-top }`, reused): the gate stands 13cqw
   above the picture and a percentage margin resolves against WIDTH, so 15% is 13 + 2 of air.
   🚫 Do not copy §40's 30.18/28.76/34.44 here — those measure three PNG frames, not this gate.
   ⚠ `a-mask` stays on the photograph and off the figure. On the figure it is `overflow: clip`
   whose box is the picture, and it cut 56.6% of §40's gate away.
   ⚠ The kasagi's upturn is a `clip-path`, so a `box-shadow` on it would be clipped away too:
   the shadow under each beam lives in the NEXT member's own top stop instead. */
[data-skin="hanakenji"] {
  --sk-gate-lacquer: #b5271a;   /* the body of the vermilion */
  --sk-gate-lac-mid: #cf3b25;   /* the turn toward the light */
  --sk-gate-lac-lit: #e3705a;   /* the specular band */
  --sk-gate-lac-hot: #f2a48f;   /* its core, one stop wide — ⚠ NOT white, or it reads as plastic */
  --sk-gate-lac-dim: #8e1c13;   /* the turn away */
  --sk-gate-lac-dep: #4e0f0a;   /* the terminator */
  --sk-gate-lac-edg: #300806;   /* the far edge, in its own shadow */
}
[data-skin="hanakenji"] [data-sk-gate="drawn"] { --sk-gate-lift: 16.5%; }
[data-skin="hanakenji"] .torii--drawn { filter: var(--a-tone, none); }
[data-skin="hanakenji"] .torii--drawn i { position: absolute; display: block; }

/* an upright: a vertical cylinder, lit from the left. ⚠ BOTH posts are lit from the left — the
   right one is NOT mirrored, because one light source is what makes the pair read as one object. */
[data-skin="hanakenji"] .torii--drawn .t-post {
  top: -12.2cqw; bottom: -1%; width: 5.6cqw; z-index: 0;
  /* torii uprights taper UPWARD — 2.2% a side, which is the difference between a post and a pipe */
  clip-path: polygon(2.2% 0, 97.8% 0, 100% 100%, 0 100%);
  background:
    linear-gradient(180deg, rgba(255, 255, 255, .10), rgba(255, 255, 255, 0) 24%,
      rgba(58, 9, 7, .18) 100%),
    linear-gradient(90deg,
      var(--sk-gate-lac-edg) 0%, var(--sk-gate-lac-dep) 8%, #8e1c13 20%,
      var(--sk-gate-lacquer) 34%, var(--sk-gate-lac-lit) 43%, var(--sk-gate-lac-hot) 46.5%,
      var(--sk-gate-lac-lit) 50%, var(--sk-gate-lac-mid) 61%,
      var(--sk-gate-lac-dim) 78%, var(--sk-gate-lac-dep) 92%, var(--sk-gate-lac-edg) 100%);
}
[data-skin="hanakenji"] .torii--drawn .t-post--l { left: 0; }
[data-skin="hanakenji"] .torii--drawn .t-post--r { right: 0; }

/* the kasagi: the top beam. ⚠⚠ IT IS SOLID, AND ITS MIDDLE IS NEVER CUT (the user's note: "the
   crowns look cut out and incomplete"). The first cut carried
   `polygon(0 0, 8% 26%, 50% 38%, 92% 26%, 100% 0, …)` to fake the traditional upturn, and at real
   scale a 38%-deep dip in the top edge does not read as a curve — it reads as a beam with a bite
   taken out of it. The upturn is now in the two ENDS only, chamfered on the UNDERSIDE (3% in,
   from 62% down), which is how the timber is actually cut. 🚫 No mid-span notch, ever. */
[data-skin="hanakenji"] .torii--drawn .t-kasagi {
  top: -14.2cqw; left: -2cqw; width: 104cqw; height: 5.4cqw; z-index: 3;
  clip-path: polygon(0 0, 100% 0, 100% 62%, 97% 100%, 3% 100%, 0 62%);
  background: linear-gradient(180deg,
    var(--sk-gate-lac-edg) 0%, var(--sk-gate-lac-dep) 10%, var(--sk-gate-lacquer) 34%,
    var(--sk-gate-lac-lit) 46%, var(--sk-gate-lac-mid) 58%,
    var(--sk-gate-lac-dim) 78%, var(--sk-gate-lac-edg) 100%);
}
/* the shimaki: the board flush under the kasagi, 1cqw narrower a side. ⚠ Its top stop IS the
   kasagi's shadow, and the two must stay CONTIGUOUS — any paper between them and the crown reads
   as two unrelated bars, which is the other half of the "incomplete" note. */
[data-skin="hanakenji"] .torii--drawn .t-shimaki {
  top: -8.8cqw; left: -1cqw; width: 102cqw; height: 2.8cqw; z-index: 2;
  background: linear-gradient(180deg,
    var(--sk-gate-lac-edg) 0%, #8e1c13 26%, var(--sk-gate-lacquer) 52%,
    var(--sk-gate-lac-lit) 68%, var(--sk-gate-lac-dim) 88%, var(--sk-gate-lac-edg) 100%);
}
/* the nuki: the tie beam, passing IN FRONT of the uprights (z-index 2 over their 0).
   ⚠ Its underside lands EXACTLY on the picture's top edge — the 0.7cqw of paper that used to show
   between the beam and the photograph read as a gap in the frame. */
[data-skin="hanakenji"] .torii--drawn .t-nuki {
  top: -4.2cqw; left: 1.5cqw; width: 97cqw; height: 4.2cqw; z-index: 2;
  background: linear-gradient(180deg,
    var(--sk-gate-lac-edg) 0%, var(--sk-gate-lac-dep) 12%, var(--sk-gate-lacquer) 36%,
    var(--sk-gate-lac-lit) 48%, var(--sk-gate-lac-mid) 60%,
    var(--sk-gate-lac-dim) 80%, var(--sk-gate-lac-edg) 100%);
}
/* the gakuzuka: the short strut between shimaki and nuki. A cylinder too, hence 90deg.
   ⚠ It spans EXACTLY that opening (-6 → -4.2, the shimaki's underside to the nuki's top): longer
   and a red nub pokes out below the beam onto the photograph, shorter and it floats. */
[data-skin="hanakenji"] .torii--drawn .t-gaku {
  top: -6cqw; left: calc(50% - 1.1cqw); width: 2.2cqw; height: 1.9cqw; z-index: 1;
  background: linear-gradient(90deg,
    var(--sk-gate-lac-edg) 0%, #8e1c13 26%, var(--sk-gate-lac-lit) 46%,
    var(--sk-gate-lac-dim) 74%, var(--sk-gate-lac-edg) 100%);
}
/* 🚫 The gate does not print — §19.4's print block already hides `.torii`, and it matches this
   markup unchanged. The lift does not print either, and that rule is there too. */

/* ── 41.2 · the Story chapter plate STEPS OFF the photograph on the phone ───────────────────
   The note: *"The story headers' opacity still obstructs the photo on phone view. Make a fix for
   this. Decide for me on how it will be done."*

   The decision, and why it is this one: §31.2 (90% + blur) and §38.2 (50% + `contrast(.55)`) are
   two attempts at the same trade, and the trade cannot be won on a phone. A veil that is light
   enough to hold `--ink` at 4.5:1 over a near-black photograph is, by arithmetic, heavy enough to
   hide the photograph — at 390 the plate is the full width of the picture and covers its lower
   ~40%, so "a corner of paper on a frame" (the desktop reading) is not what a phone shows at all.
   Every remaining lever is a smaller number on one side of the same equation.

   So below 48em the plate stops being a veil and becomes a CAPTION BAND: the picture keeps its
   whole height, unobstructed, and the plate sits under it on the page's own opaque paper, full
   bleed, in its own grid row. ⚠ This also retires the contrast contract §38.2 measured — the
   ground is `--paper`, not rgb(155,153,150) — so the three inks §38.2 re-pointed go BACK to the
   values they have at ≥64em, where they have always been legal on paper: the folio and the date
   in cherry `--accent-ink`, the blurb in `--muted`. Hierarchy returns to three tiers.
   ⚠ §38.2's block stays exactly as written and still governs **48–63.99em**, where the plate is a
   corner of a wide frame and the overlay reads correctly. 🚫 Do not delete it, and do not widen
   this block into its range without re-measuring the tablet by eye.
   ⚠ The rows must be explicit. §31's base rule puts the plate and the picture in the SAME grid
   area (`1 / 1`) with `align-self: end` — that is the overlay — so moving the plate means giving
   the chapter three rows and re-pointing all three children, not just nudging the plate.
   ⚠ The plate keeps `width: 100%` from §31's own phone block and loses its radius and its inner
   shadow: both were the language of paper lying ON a picture, and a band under one that still
   had a 14px top corner and an upward shadow would read as a mistake.
   ⚠ `--sk-chapter-h` is NOT reduced. The picture is now the only thing in its row, so the frame
   the crop was chosen against is finally the frame that is shown. */
@media (max-width: 47.99em) {
  [data-skin="hanakenji"] .chapter--frame {
    grid-template-rows: auto auto auto;
  }
  [data-skin="hanakenji"] .chapter--frame > .chapter-shots { grid-area: 1 / 1; }
  [data-skin="hanakenji"] .chapter--frame > .chapter-plate {
    grid-area: 2 / 1; align-self: start; justify-self: stretch;
    width: 100%;
    background: var(--paper);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    border-radius: 0;
    box-shadow: none;
    padding: 1.15rem 1.2rem 1.25rem;
  }
  [data-skin="hanakenji"] .chapter--frame:nth-of-type(even) > .chapter-plate {
    border-radius: 0;
  }
  [data-skin="hanakenji"] .chapter--frame > .chapter-body { grid-area: 3 / 1; }
  /* the inks go back to the paper register — §38.2's re-points were for its filtered ground */
  [data-skin="hanakenji"] .chapter--frame > .chapter-plate .chapter-no,
  [data-skin="hanakenji"] .chapter--frame > .chapter-plate .chapter-date {
    color: var(--accent-ink);
  }
  [data-skin="hanakenji"] .chapter--frame > .chapter-plate .chapter-blurb {
    color: var(--muted);
  }
}

/* ═══ §42 · ROUND TWENTY-ONE, PART TWO — THE CARD TAKES THE FLOOR, BOTTOM-LEFT ═════════
   The answered form: the card sits **bottom-left, inset from the edges, in every view**, and the
   clock stays where each register already has it — **top-right on the phone (§38.3), bottom-right
   on tablet and desktop (§36.2/§36.10)**. So this block is ≥48em only; the phone is already right.

   What was actually wrong at ≥48em, measured: the card was `align-self: end` (§38.3) inside a hero
   whose `padding-bottom` is `--sk-corner-lift` (9.4–10.6rem of DOCK clearance) **plus** its own
   `margin-bottom: clamp(2.6rem, 5vw, 4.4rem)` (§36.10) — so its "floor" was 190–240px above the
   fold and it read as floating in the middle-left, not as resting bottom-left. It was also FLUSH
   to the hero's left edge, with its rounded corners cut by it.

   ⚠⚠ **THE DOCK CLEARANCE MOVES BACK ONTO THE CLOCK, AND §36.10's WARNING STILL HOLDS.** That
   warning is about having the hero's padding AND a clock margin of the same size — they stack.
   Here the hero's padding drops to a plain floor gutter (`--sk-card-floor`) and the clock's margin
   is the DIFFERENCE, `--sk-corner-lift − --sk-card-floor`, so the clock lands exactly where §36.2
   measured it (36px over the dock stack) and only the card moves. 🚫 Do not write the full lift
   as the clock's margin — that double-counts the floor and re-breaks the 36px.
   ⚠ The clock's margin must be the **full shorthand with `!important`**: §36.7's arrival rule is
   `margin: 0 var(--gutter) 0 0 !important` and a plain `margin-bottom` loses to it (§38.3's trap).
   ⚠ The card's inset is written as a shorthand too, for the same reason — and it is `--gutter`,
   the page's own inset, so the card lines up with the gutters the phone register already uses.
   ⚠ The bottom-left corner is free of furniture at these widths: both demo docks and the music
   dock sit bottom-RIGHT (`demo-dock--corner`), and the rail is a top bar below 64em. The card's
   only clearance need is the fold itself. */
@media (min-width: 48em) {
  [data-skin="hanakenji"] .hero--threshold {
    --sk-card-floor: clamp(1.4rem, 2.4vh, 2.2rem);
    padding-bottom: var(--sk-card-floor);
  }
  [data-skin="hanakenji"] .hero--threshold > .hero-paper {
    align-self: end;
    margin: 0 0 0 var(--gutter);
  }
  [data-skin="hanakenji"] .hero--threshold > .hero-count--corner {
    margin: 0 var(--gutter) calc(var(--sk-corner-lift) - var(--sk-card-floor)) 0 !important;
  }
}

/* ═══ §43 · ROUND TWENTY-ONE, PART THREE — THE CARD TOUCHES THE PICTURE'S EDGES ═════
   The user, on §42 and §38.3: *"the card for desktop and tablet view must not have space between
   the edges of the picture"* and *"nothing was done to fix the phone view card"*. Both are the
   same correction, and §42 got it half right: it moved the card to the bottom-left and then held
   it off the edges with §42's own `--sk-card-floor` gutter and a `var(--gutter)` left margin.
   **Flush means flush.** The card now sits IN the picture's bottom-left corner at every width,
   with no paper, gutter or floor between it and the two edges it touches.

   ⚠ **§42's floor gutter is spent, so the clock takes the WHOLE lift back.** With the hero's
   `padding-bottom` at 0 the difference §42 subtracted no longer exists — the clock's margin is
   `--sk-corner-lift` again, which is exactly §36.10's arithmetic and the 36px of dock air §36.2
   was measured to hit. 🚫 Do not leave §42's `calc(… - …)` in place against a zero floor: that
   subtracts a gutter that is not there and walks the clock down into the dock.
   ⚠ **The radius follows the corner, as it does on Story's chapter plate (§31):** the only corner
   that rounds is the one standing in open picture — top-right on tablet/desktop, the two top
   corners on the phone, where the card is full width. A card flush to an edge with a rounded
   corner on it reads as a mistake (§36.10's "corners bisected by the screen edge").
   ⚠ **The phone's dock clearance is NOT a hero padding any more** — it is the card's own
   `padding-bottom` (2.6rem). The demo dock and the music pill are fixed, bottom-RIGHT, and the
   card's content is left-aligned (the RSVP pill ends around 150px at 390, the dock pill starts
   around 250), so they pass each other. 🚫 The docks cannot move (`DECISIONS.md`).
   ⚠ Every margin here is a full shorthand with `!important`: §36.7's clock rule and §25.6/§36.9's
   card rules are shorthands too, and a one-sided margin loses to them (§38.3's trap, third time). */
@media (min-width: 48em) {
  [data-skin="hanakenji"] .hero--threshold { padding-bottom: 0; }
  [data-skin="hanakenji"] .hero--threshold > .hero-paper {
    align-self: end;
    margin: 0 !important;
    border-radius: 0 calc(var(--radius) + 6px) 0 0;
  }
  [data-skin="hanakenji"] .hero--threshold > .hero-count--corner {
    margin: 0 var(--gutter) var(--sk-corner-lift) 0 !important;
  }
}
@media (max-width: 47.99em) {
  /* ⚠ `row-gap: 0` is part of the flush, not tidying: §30.1's `.85rem` row gap sat BELOW the
     card's row (the photograph's `<picture>` wrapper is a third, in-flow row), leaving a
     measured 14px strip of picture under a card that was supposed to be touching the edge. */
  [data-skin="hanakenji"] .hero--threshold { padding: 0; row-gap: 0; }
  [data-skin="hanakenji"] .hero--threshold > .hero-paper {
    grid-row: 2; align-self: end;
    width: auto;
    margin: 0 !important;
    padding: 1.05rem 1.2rem 2.6rem;
    border-radius: calc(var(--radius) + 6px) calc(var(--radius) + 6px) 0 0;
  }
}

/* ═══ §44 · THE CLOTH STOPS BEING STRETCHED — BOTH REGISTERS OF `.noren--plain` ═══════
   The user, round twenty-two: *"the noren does not look right on other pages (both mobile and
   desktop view)… it looked stretched, which kills the aesthetics of it."* Two different faults
   wearing one complaint, and neither is the image file — `noren-plain.webp` is 1600 × 500 of
   clean indigo weave with an uneven hem and nothing printed on it.

   ── 44.1 · the phone: an ASPECT, not a height ────────────────────────────────────────────
   §34.9 swaps the interior pages to `noren-crest.png` below 48em and draws it `500% 100%` — the
   whole 3.2:1 cloth squeezed into the strip's `clamp(96px, 9vw, 132px)`. **Measured at 390: the
   panels are 67 × 87, and 3.2:1 at that width wants 105.** The cloth is drawn at 83% of its own
   height, which is exactly what the crest showed — a round kamon going elliptical is the most
   legible possible symptom of a squashed background, and it is why this one read as "stretched"
   when the desktop one did not.
   The height comes OFF the strip and out of the panel's aspect instead: five panels, each
   `320 / 500` (the cloth's fifth), so the drawn image is `5w × 1.5625w` = 3.2:1 at every width,
   on every handset, with no value to re-derive. The strip grows ~11px at 390 and that is the
   whole cost. 🚫 Never give this cloth a height and a `500% 100%` in the same register.
   ⚠ `align-items: start` is load-bearing: flex items stretch to the container by default, which
   would hand the panel its height back and re-break the aspect the `aspect-ratio` just set.

   ── 44.2 · tablet and desktop: ONE cloth, not five copies ────────────────────────────────
   §19.2's `background-size: auto 100%` scales the file to the STRIP's height and lets its width
   fall where it likes — but each panel then paints its own copy of that whole 313–431px cloth,
   so the file's left edge, right edge and hem repeat five times across the row. That is what
   makes the strip read as five flat boards with lit edges rather than one curtain: the tell in
   the screenshot is the pale vertical band at the left of every panel, which is the cloth's own
   selvedge, five times.
   `500% auto` instead: the image is sized to the WHOLE strip (5 × panel) and each panel keeps
   §19.2's `nth-of-type` x-offset, so the five panels are five slices of ONE continuous cloth at
   its true aspect, cropped top-anchored to the strip's height. At 1440 that is 1360px of weave
   against the file's 1600 — near native, and no panel repeats another.
   ⚠ Top-anchored on purpose. Bottom-anchored shows the file's hem, and the 11% of transparent
   pad under it would hang the cloth ~47px above the strip's floor with the drop-shadow under it.
   A valance is cut off at the bottom; that is what a valance is.
   ⚠ The gap, the sway and §34.9's edge insets are untouched — the fault was the draw, not them. */
@media (min-width: 48em) {
  [data-skin="hanakenji"] .noren--plain i { background-size: 500% auto; }
}
@media (max-width: 47.99em) {
  [data-skin="hanakenji"] .noren--plain { height: auto; align-items: start; }
  [data-skin="hanakenji"] .noren--plain i { aspect-ratio: 320 / 500; background-size: 500% 100%; }
}

/* ═══ §45 · THE BLEED'S PAPER SHEET STEPS OFF THE PHOTOGRAPH ON THE PHONE ═══════════
   The user: *"some images are obscured by the headers… make sure the pictures are not cropped
   incorrectly and showing the non-highlighted portion."* Measured at 390 × 844, and the numbers
   are worse than the note suggests — `.frame-say` is flush into the bleed's bottom corner at
   §23.2 and at phone width it is the frame's FULL width, so it does not sit in a corner of the
   picture, it sits on top of it:
     · home, bleed 1 — frame 375 × 512, sheet **397px tall: 78% of the picture**, 115px showing
     · home, bleed 2 — sheet 271px: **53%**
     · Story's close — sheet 331px: **65%**
   And the crop cannot answer it. Every one of these sources is wider than the phone's frame, so
   `cover` scales BY HEIGHT and the whole height is already visible — the vertical half of
   `object-position` is inert (the same arithmetic as §36.9 and §34.1). There is no value of
   `--sk-frame-crop-mob` that lifts the lanterns, the lit wall or the near island out from under
   the sheet, because they are not cropped out: they are covered.

   So the panel moves, not the number — **§41.2's decision, applied to the second plate that has
   it** (`DECISIONS.md` → *When a translucent panel's alpha is pinned at both ends, move the panel
   instead of the number*; here the panel is opaque, which makes it a stronger case, not a weaker
   one). Below 48em the frame is two rows: the photograph in row 1 at its own height, the paper
   sheet in row 2 underneath it. Nothing overlaps, at any page, at any handset width.
   ⚠ The picture height is the knob: `--sk-frame-pic-h`, default `clamp(240px, 38svh, 320px)`.
   At 390 × 844 that is 320, and a 2400 × 1350 source covering 375 × 320 shows its **full height**
   and the middle 66% of its width — the lantern string, the lit wall and the water all inside
   the frame. 🚫 Do not raise it past ~42svh: the sheet is 271–397px and the pair has to leave
   the next band's opening on screen.
   ⚠ `position: relative` is the fix, not `grid-row` — §23.2's img is `position: absolute;
   inset: 0`, and an absolutely-positioned grid item takes its row as a containing block and goes
   right back to covering it. Both the direct-child and the `> picture > img` form are re-pointed
   (§35.4's rule: check every `> img` before touching one).
   ⚠ `justify-items: stretch` undoes §23.2's `--sk-frame-x`: in one column, `.frame--right`'s
   `end` would shrink-wrap the sheet against the right edge. The corner the sheet takes is a
   tablet-and-up idea; on the phone there is only one column. */
@media (max-width: 47.99em) {
  [data-skin="hanakenji"] .frame {
    grid-template-rows: auto auto;
    align-items: stretch; justify-items: stretch;
    min-height: 0;
  }
  [data-skin="hanakenji"] .frame > img,
  [data-skin="hanakenji"] .frame > picture > img {
    position: relative; inset: auto;
    grid-row: 1; grid-column: 1;
    width: 100%; height: var(--sk-frame-pic-h, clamp(240px, 38svh, 320px));
  }
  [data-skin="hanakenji"] .frame > .frame-say {
    grid-row: 2; grid-column: 1;
    width: 100%;
  }
}

/* ═══ §46 · THE GATE IS A PHOTOGRAPH, AND IT STANDS INSIDE ITS OWN FILE ════════════
   Round twenty-two's delivery. `torii--drawn` → **`torii-shot`**, which retires §41.1 by no
   longer matching (the §40 → §41 pattern, third time); every drawn member, every lacquer token
   and §40's `--sk-gate-w/x/y` stay in the file as records.

   ── 46.1 · why this route has no overhang at all ─────────────────────────────────────────
   Every gate before this one was sized so its OPENING covered the picture, which put the posts
   and the kasagi's tips outside the picture's box — 26.7% a side in §40, 2cqw in §41 — and
   `DECISIONS.md` → *An overhang is only as wide as the room you measured* is the record of what
   that costs. **This gate is drawn at the plate's full width and the photograph goes INSIDE it.**
   Nothing overhangs anything, so nothing can be clipped by `BODY`'s `overflow-x: clip`, and the
   acceptance gate's horizontal-overflow frame is unreachable by construction.

   ── 46.2 · the numbers are measured off the delivered alpha, not off the prompt sheet ────
   v2's three files passed everything irreversible — exact canvases, **zero matte**, **zero
   pixels inside the opening**, symmetry 0.11 / 0.20 / 0.93%, 109 / 130 / 168 KB — and came in
   narrower than the contract asked (opening ÷ file width **0.694 / 0.706 / 0.531** against
   0.82 / 0.82 / 0.76). The sheet says to derive off delivered pixels, so:
     · the photograph's left and right edges sit at the **centre line of each post**, so the posts
       stand ON the picture (the §41 pattern that survived) and the opening's shortfall is spent
       on overlap instead of on air;
     · its top edge is the **nuki's underside**, its bottom the **posts' feet** — the feet stop
       19 / 21 / 71px above each file's bottom edge, and a picture running past them would show
       below the gate's own ground line.
   Measured, as fractions of each file (top / left, then the picture's own width / height):
     43   1600 × 1280 · posts 104 wide at 141 and 1355 · feet y1260 →  22.50 / 12.03 · 75.88 × 76.02
     169  1760 × 1055 · posts 114 wide at 144 and 1501 · feet y1033 →  27.20 / 11.39 · 77.10 × 70.81
     45   1452 × 1790 · posts 127 wide at 211 and 1109 · feet y1718 →  26.26 / 18.87 · 61.92 × 69.77
   The picture's own aspect follows from those: **1.247 on home** (a 4:3 source cropped 6%),
   **1.817 on Travel** (16:9, near exact) and **0.72 on the phone** (a 4:5 source cropped 10%).
   🚫 Do not "correct" these to the sheet's .11 / .24 — the sheet is the brief, the file is the
   truth, and re-deriving off delivered pixels is the rule that this round exists to honour.

   ── 46.3 · the mechanics, and the two traps ──────────────────────────────────────────────
   ⚠ **`.torii-frame` takes the FILE's aspect-ratio**, because the photograph is no longer in
   flow — it is inset-positioned inside the gate, so nothing else gives the frame a height.
   ⚠ **Specificity: §35.4's phone rule is (0,4,2)** (`[data-skin] .plate--torii .torii-frame >
   picture > img`, aspect-ratio 4/5 + width 100%) and it would win against the obvious three-part
   selector — and now matches the GATE's `<picture>` on Travel as well as the photograph's. Both
   rules below therefore carry **three attributes plus two classes**. 🚫 Do not shorten them.
   ⚠ **The picture is sized, not inset — and this cost a round-trip.** An absolutely-positioned
   REPLACED element ignores an over-constrained `right`/`bottom`: `inset: t r b l` with
   `width: auto` gives an `<img>` its INTRINSIC width, so the photograph drew at 1400px and ran
   out past the right-hand post. The probe read it as `0 × 0` while the file was still unloaded,
   which is the same fault wearing its other face. 🚫 Never size a replaced element by insets:
   `top`/`left` place it, `width`/`height` size it.
   ⚠ Travel's gate is a two-file `<picture>` at **the same 47.99em query as the photograph's
   source**. If one moves, both move, and every `--sk-shot-*` moves with them.
   ⚠ `--sk-gate-lift: 0`: §40's `.plate--torii { margin-top: var(--sk-gate-lift) }` cleared a
   lintel that stood above the picture. There is no lintel above the picture any more. */
[data-skin="hanakenji"] [data-sk-gate="shot"] { --sk-gate-lift: 0; }
[data-skin="hanakenji"] [data-sk-gate="shot"] .torii-frame {
  padding-top: 0;
  aspect-ratio: var(--sk-shot-ar);
}
[data-skin="hanakenji"] [data-sk-gate="shot"][data-sk-shot] .torii-frame > img.torii-shot,
[data-skin="hanakenji"] [data-sk-gate="shot"][data-sk-shot] .torii-frame > picture > img.torii-shot {
  position: absolute; inset: 0; z-index: 1;
  width: 100%; height: 100%; max-width: none; margin: 0;
  object-fit: fill; aspect-ratio: auto;
  pointer-events: none;
  filter: var(--a-tone, none);
}
[data-skin="hanakenji"] [data-sk-gate="shot"][data-sk-shot] .torii-frame > img.a-mask,
[data-skin="hanakenji"] [data-sk-gate="shot"][data-sk-shot] .torii-frame > picture > img.a-mask {
  position: absolute; z-index: 0;
  top: var(--sk-shot-t); left: var(--sk-shot-l); right: auto; bottom: auto;
  width: var(--sk-shot-w); height: var(--sk-shot-h);
  max-width: none; margin: 0;
  object-fit: cover; aspect-ratio: auto;
}
[data-skin="hanakenji"] [data-sk-shot="43"] .torii-frame {
  --sk-shot-ar: 1600 / 1280;
  --sk-shot-t: 22.50%; --sk-shot-l: 12.03%; --sk-shot-w: 75.88%; --sk-shot-h: 76.02%;
}
[data-skin="hanakenji"] [data-sk-shot="169"] .torii-frame {
  --sk-shot-ar: 1760 / 1055;
  --sk-shot-t: 27.20%; --sk-shot-l: 11.39%; --sk-shot-w: 77.10%; --sk-shot-h: 70.81%;
}
/* ⚠ THE SAME QUERY AS THE TWO `<source media>` ON THAT PLATE — the gate's and the photograph's. */
@media (max-width: 47.99em) {
  [data-skin="hanakenji"] [data-sk-shot="169"] .torii-frame {
    --sk-shot-ar: 1452 / 1790;
    --sk-shot-t: 26.26%; --sk-shot-l: 18.87%; --sk-shot-w: 61.92%; --sk-shot-h: 69.77%;
  }
}
@media print {
  [data-skin="hanakenji"] .torii-shot { display: none !important; }
  [data-skin="hanakenji"] [data-sk-gate="shot"] .torii-frame { aspect-ratio: auto !important; }
  [data-skin="hanakenji"] [data-sk-gate="shot"] .torii-frame > img.a-mask,
  [data-skin="hanakenji"] [data-sk-gate="shot"] .torii-frame > picture > img.a-mask {
    position: static !important; width: 100% !important; height: auto !important;
  }}

@media print {
  /* the card flattens to white paper in §25.6/§28.7, so the pill cannot stay a solid cherry
     block with a white label on it — it prints as a ruled one. */
  .hero-say .hero-reply {
    background: none !important; border-color: var(--rule-strong) !important;
    color: var(--ink) !important; -webkit-text-fill-color: var(--ink) !important;
    box-shadow: none !important;
  }
}


/* ══════════════════════════════════════════════════════════════════════════════════════════
   §47 · THE MUSIC CONTROL REJOINS THE STUDY PILL (2026-09-19)

   The user: the music button had gone. It had not been deleted — §docks put `.music-dock` in
   its own fixed box at `bottom: 1rem` with the study tab stacked 4.4rem above it, and then
   hid the music box entirely below 40em. So on every phone the control genuinely did not
   exist, and on desktop it read as a second, unrelated pill.

   It is one row now: a 44px NOTE beside the design-study pill, inside `.demo-dock--corner`.
   · The markup moved — the standalone `<aside class="music-dock">` at the foot of all four
     pages is gone and the same mount (`data-wsd="music"`, `data-wsd-el="toggle"`/`label`) sits
     in the dock, BEFORE the tab, so the note reads left of the words.
   · ⚠ THE BUTTON'S WORDS ARE FOLDED BY TYPE SIZE, NOT REMOVED. `music.js` writes "Play music"
     / "Pause music" as the button's textContent — that IS the accessible name — so the glyph
     is a `::before` and the text is `font-size: 0`. 🚫 Never put the ♪ in the markup: the next
     toggle would overwrite it.
   · ⚠ `--corner` goes to `bottom: 1rem` at EVERY width. The 4.4rem was only ever the height
     of the music box beneath it (its own comment says so), and with the note in the row there
     is nothing under the tab to clear. The dock stack is ~44px tall now, not ~114 — every
     hero clearance derived from 114 gains air, and none of them loses any.
   · The credit line stays in the DOM (music.js writes it) but is not shown: the same sentence
     is already two inches away in the dock panel's tip note.
   · Playing state is the ground, not a wave: `[data-music="on"]` fills the note cherry, which
     is the same hover ink the tab and the demo buttons use. #fff on --cherry-ink is 4.52:1. */
.demo-dock--corner {
  display: flex; align-items: center; justify-content: flex-end; gap: .5rem;
  bottom: 1rem;
}
.demo-dock--corner .music-dock { position: static; display: flex; align-items: center; }
.demo-dock--corner .music-title { display: none; }
.music-note {
  flex: none;
  display: flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; min-height: 44px; padding: 0;
  font-size: 0;
  background: var(--ink); color: var(--paper);
  border: var(--rule-w) solid var(--ink); border-radius: 999px;
  cursor: pointer;
}
.music-note::before {
  content: "\266A";
  font: 400 1.2rem/1 var(--display);
}
.music-note:hover, .music-note:focus-visible,
[data-music="on"] .music-note { background: var(--cherry-ink); border-color: var(--cherry-ink); }
/* 🚫 The note does NOT shrink with the tab below 40em. The tab loses PADDING there and keeps
   its 44px box; 44 is the floor this project has spent twice and will not take back. */
@media print { .music-note { display: none; } }


/* ══════════════════════════════════════════════════════════════════════════════════════════
   §48 · THE COUNTDOWN'S EIGHT REGISTERS COLLAPSE TO ONE (2026-09-19)

   `NEXT.md`'s standing item, carried since round eleven. Home's clock had been re-designed
   eight times and every register was still in this file, because each round retired its
   predecessor THE SAME WAY — by changing the markup's modifier class so the old rules stopped
   matching. Nothing was ever deleted, so 151 rules were being parsed, none of them painting.

   WHAT WENT, and what each one was:
     §24.2  `.hero-count--card`     round two, the clock as a paper card pinned right
     §25.4  `.hero-paper .hero-count` round five, the clock bare INSIDE the names panel
     §26    `.hero-count--bare`     round four, bare ink type on the photograph
     §27    `.hero-count--glass`    round five, the second pane of the diptych
     §28    `.hero-count--type`     round six, glass letterforms on their own band
     §29    `.hero-count--cut`      round seven, white type stroked near-black, no ground
     §30.3  `.hero-count--chrome`   round eight, the metal clock — rejected by the user
     plus the bare-`.hero-count` phone and print remnants in §24.6 and §25.6, and the
     `.hero-count-act` family (§36.4 moved the reply into the card as `.hero-do`).

   WHAT STAYS, and why each one is not an orphan:
     · **§36 is the register.** `--corner`, with §36.2b/§38.3 (phone), §42/§43 (the card's
       floor) and §36.9/§36.10 on top of it. It is the only one the markup can reach.
     · **`@keyframes hkCount`** (§26.2) — §36.7's stagger runs on it.
     · **`--sk-cut-ink`** (§29.1) — §36.3 strokes the caption and the labels with it.
     · **§27.1's hero grid**, **§25.3/§27.3/§28.4's glass card** and **§29.4/§35's `.hero-bleed`
       records** — none of them are countdown rules and none were touched.
     · **§30.2's chrome CARD** — inert by markup, kept as the record of a rejected round.

   ⚠ THE COLLAPSE IS A DELETION, NOT A REWRITE. Not one live declaration changed: every removed
   rule was proven unreachable by the markup first (all four pages carry exactly one countdown
   mount, `.hero-count.hero-count--corner`, and only home has one at all). The gate was re-run
   after it, not before.
   📦 **The record is `_archive/skin-hanakenji-pre-collapse-2026-09-19.css`** — the whole
   stylesheet as it stood at 6,108 lines. 🚫 Do not re-import a register from it to "restore" a
   look; a round that wants an old clock back writes a new section, as every round here has.

   ⚠ AND THE RULE THAT CAUSED THE PILE-UP IS NOT REPEALED. Retiring a register by changing the
   modifier class is still right — it keeps a revert one markup edit away while a round is being
   judged. What was missing was the second step: once the user has accepted the new register and
   the page is parked, the dead ones are deleted and the file is the record. Sites 3–8 do this at
   the end of each page's review round, not eight rounds later.
   ══════════════════════════════════════════════════════════════════════════════════════════ */

/* ══════════════════════════════════════════════════════════════════════════════════════════
   §49 · STORY'S GALLERY DRIFTS — 2026-09-19 (round twenty-six)
   The first of the user's two long-standing Story picks. `engine/parallax.js` writes `--gp`
   (signed, -1..1, the figure's centre against the viewport's) and `--gpd` per figure and draws
   nothing; everything visible is here.

   ⚠⚠ THE DRIFT IS `translate`, NEVER `transform`. Every gallery figure is `.a-mask`, and
   `atelier.css` §L/§reveal animate the IMAGE's transform — a 1.07 over-scale resolving to
   `scale(1)` — then force `transform: none !important` under reduced motion. A parallax written
   as a transform fights the reveal on the way in and is silently cancelled for the readers who
   asked for less motion. `translate` is an independent property: it composes with that scale,
   and it is this skin's to zero.
   ⚠ It moves the FIGURE, not the picture. A masonry column has no fixed frame height, so
   translating the image inside `.a-mask`'s clip would show a transparent strip at one edge.
   ⚠ The amplitude alternates by column parity, because a whole grid drifting one way is a
   scroll-speed bug, not depth. ±8px against a .9rem column gap cannot make two figures touch.
   🚫 No amplitude above 10px: this is a gallery of faces, not a hero.                        */
[data-skin="hanakenji"] .gallery figure {
  --sk-gal-par: clamp(4px, .9vw, 8px);
  translate: 0 calc(var(--gp, 0) * var(--sk-gal-par));
  will-change: translate;
}
[data-skin="hanakenji"] .gallery figure:nth-child(2n) { --sk-gal-par: clamp(-8px, -.9vw, -4px); }
@media (prefers-reduced-motion: reduce) {
  [data-skin="hanakenji"] .gallery figure { translate: none; will-change: auto; }
}

/* ══════════════════════════════════════════════════════════════════════════════════════════
   §50 · STORY'S PARTY CARDS DISCLOSE THEIR LINE — 2026-09-19 (round twenty-six)
   The second pick. The card shows the person; the sentence about them arrives on hover, on tap,
   or on Tab. Hover is CSS and lives here; the tap and keyboard half is `engine/disclose.js`.

   ⚠⚠ EVERY RULE IS GATED ON `[data-disclose="ready"]`, WHICH THE ENGINE STAMPS. With JS off the
   attribute never appears and all six lines stay on the page. 🚫 Never key a hiding rule on
   `[data-wsd="disclose"]` — that attribute is in the markup and would hide the copy for good.
   ⚠ The row collapses with `grid-template-rows: 0fr → 1fr` rather than a height: the lines are
   one to three lines depending on width and no max-height is honest at every one of them.
   `min-height: 0` on the child is what makes 0fr actually collapse.
   ⚠ `:focus-within` covers the keyboard; the engine's `data-open` covers the thumb; `:hover`
   covers the mouse. All three, or the card is a trap on one of the three.                     */
[data-skin="hanakenji"] .party-grid[data-disclose="ready"] .party-say {
  display: grid; grid-template-rows: 0fr;
  transition: grid-template-rows var(--a-dur, .42s) var(--ease, ease), opacity var(--a-dur, .42s) linear;
  opacity: 0;
}
[data-skin="hanakenji"] .party-grid[data-disclose="ready"] .party-say > p { min-height: 0; margin: 0; overflow: hidden; }
[data-skin="hanakenji"] .party-grid[data-disclose="ready"] .party-card:hover .party-say,
[data-skin="hanakenji"] .party-grid[data-disclose="ready"] .party-card:focus-within .party-say,
[data-skin="hanakenji"] .party-grid[data-disclose="ready"] .party-card[data-open] .party-say {
  grid-template-rows: 1fr; opacity: 1;
}
/* the card is operable now, so it says so — a rule that arrives with the pointer, and a real
   focus ring for the Tab key (engine.css's outline is the skin's to place, not to remove). */
[data-skin="hanakenji"] .party-grid[data-disclose="ready"] .party-card { cursor: pointer; }
[data-skin="hanakenji"] .party-grid[data-disclose="ready"] .party-card:focus-visible {
  outline: 2px solid var(--accent-ink); outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
  [data-skin="hanakenji"] .party-grid[data-disclose="ready"] .party-say { transition: none; }
}
/* ⚠ §-phone: the card is a two-column list row below 48em and the old `.party-card p` placement
   rules point at the paragraph, which is no longer a grid item. The BOX takes the row now. */
@media (max-width: 47.99em) {
  [data-skin="hanakenji"] .party-card .party-say { grid-row: 3; grid-column: 2; margin-top: .15rem; }
}

/* ══════════════════════════════════════════════════════════════════════════════════════════
   §51 · THE AMPERSAND TAKES FULL INK — 2026-09-19 (round twenty-six)
   `tools/Site 2 Audit.html`, once its two paint-stack faults were fixed, left exactly one real
   contrast finding on site 2: `.hero-amp` — the "and" between the names — at `rgba(255,255,255,.72)`
   measured **3.44:1 at 390 and 3.22:1 at 834** over the glass card, against a 4.5 floor. It
   passes at 1024 and 1440, which is the tell: the ground is the same everywhere and only the
   type size changes, so this is the ALPHA, not the card.
   ⚠ The hierarchy was never carried by the alpha. The ampersand is 13px against names at 31px on
   a phone — a fifth of the area — and it keeps its own face and tracking. Full ink costs nothing
   an eye will read as loud, and it is the only value that clears the floor at every width.
   🚫 Do not answer this with a scrim, a shadow or a stroke: §29.2's stroked pair exists for type
   standing on a PHOTOGRAPH, and this word stands on the glass card. */
[data-skin="hanakenji"] .hero--threshold .hero-amp { color: #fff; }

/* ══════════════════════════════════════════════════════════════════════════════════════════
   §52 · A TALL PLATE IN THE MARGIN — 2026-09-19 (round twenty-six)
   `hero-01.jpg` was the folder's last unplaced frame and the only original PORTRAIT. It lands
   in Reply's ink band, beside the honesty passage, at the user's call.
   ⚠ `.plate` (§17) spans `1 / -1` and crosses the break — at 1400 × 1800 that is a 960 × 1234
   picture in a page whose point is a short form. This modifier takes it out of the full span and
   into the MARGIN column, capped at 20rem, which is also where `.marg` already lives: the page
   has a margin column, so a portrait belongs in it rather than inventing a third position.
   ⚠ The cap is on WIDTH and the height follows the file. Do not give it an aspect-ratio — the
   frame is 7:9 and every other plate on this site is landscape; a shared ratio would crop it.
   ⚠ Below 60em `.spread` is one column (§17), so the plate goes back to full width and keeps
   the same cap; `margin: 0` because the break margin belongs to a picture that crosses it.    */
[data-skin="hanakenji"] .spread > .plate--tall {
  grid-column: 1; margin: 0; width: min(100%, 20rem); justify-self: start;
}
/* ⚠ RECORD — why there is no margin-column register here. `.spread`'s second track is the `1fr`
   remainder and it does NOT grow with the viewport the way it looks like it should: the anchor
   rail indents the sheet as it appears, so the spread measures 855px at a 900 viewport but only
   **739px at 1024**, a 140px track — a 1400 × 1800 portrait in it is a 140 × 180 thumbnail beside
   17px body copy. Even at 1440, where the track reaches 348px, the figure ran 702px tall against
   a 254px header. The picture belongs in the flow, paired with its passage. */
[data-skin="hanakenji"] .plate--tall figcaption { display: grid; gap: .9rem; margin-top: .7rem; }
[data-skin="hanakenji"] .plate--tall .plate-tall-say { display: grid; gap: .45rem; }
[data-skin="hanakenji"] .plate--tall .plate-credit { text-align: left; }
/* ⚠⚠ THE SECOND TRACK NEEDED CONTENT, NOT A CLEVERER GRID — the review's finding, and the reason
   §52 is written the way it is. The paired register put an eyebrow and a credit line (38px of type
   all told) beside a 350px frame: a 595 × 350 track **11% filled**, a worse-reading tail than the
   one it replaced, because the emptiness now sat INSIDE the figure's own box. No column sizing
   fixes that — 38px cannot occupy 350px. So the honesty passage moved out of the header and into
   this caption, which is where this file's comment had claimed it was all along. 🚫 Do not answer
   an empty track by resizing the track. */
/* ⚠⚠ ONE PAIRED REGISTER FROM 48em UP — there is no 72em gate any more, and the margin-column
   placement is GONE. Three registers were two too many, and each extra one was a patch on the
   last: the 60em gate left a 547px empty tail at 924; re-gating to 72em fixed a 140px thumbnail
   at 1024 but left the caption 11% filling its track; and once the honesty passage moved into the
   caption the margin column became the worst of the three — at 1440 a **702px figure in the
   margin against a 254px header**, the same emptiness mirrored into the main column.
   With real content in the second track the paired row simply works at every width: picture left
   at 17rem, passage beside it, both growing with the sheet. 🚫 Do not reintroduce a margin-column
   register — a portrait in a 1fr remainder is a thumbnail at the widths that matter. */
@media (min-width: 48em) {
  [data-skin="hanakenji"] .spread > .plate--tall {
    width: 100%;
    display: grid; grid-template-columns: minmax(0, 17rem) minmax(0, 1fr);
    column-gap: var(--gutter); align-items: start;
  }
  [data-skin="hanakenji"] .spread > .plate--tall figcaption { margin: 0; align-content: space-between; min-height: 100%; }
}

/* ══════════════════════════════════════════════════════════════════════════════════════════
   §53 · THE LAST TWO REAL CONTRAST ROWS — 2026-09-19 (round twenty-six)
   With §51 in, the audit's remaining findings were two, and both are honest measurements rather
   than instrument pessimism: they are read off the PHOTOGRAPH the type actually sits on.

   · **The fortune's rank.** `.slip-rank` (中吉, 23px) is `--accent-ink` on `cut-omikuji-slip.webp`
     and measures **4.01 worst / 4.16 median** against a 4.5 floor — flat, not patchy, so no tile
     is carrying it. The paper in that file is warmer and lighter than `--paper`, which is why a
     token that clears the floor everywhere else does not clear it here. 22% sumi into the same
     cherry keeps the family and buys the step. 🚫 Not `--accent`: cherry is 3.2:1 on ink and
     this skin's standing rule keeps it for buttons.
   · **The swatch labels on a sumi PHOTOGRAPH.** §23.1 re-points `--faint` to 5.1:1 against flat
     `#2b2733`, and that is correct arithmetic against a flat ground — but `.band--sumi` tiles
     `ground-sumi-01.jpg`, whose lightest passages measure **3.52** under a 14px label (median
     7.39). ⚠ The lesson is the general one: **a token measured against a band's flat colour is
     not measured against the band's photograph.** The labels take `--muted` (8.5:1 flat) on a
     tiled ground only; `--faint` stays everywhere else, including flat ink bands.              */
[data-skin="hanakenji"] .slip-rank { color: color-mix(in oklab, var(--accent-ink) 78%, var(--sumi)); }
[data-skin="hanakenji"] .band--sumi .swatch span { color: var(--muted); }

/* ============================================================================
   FOREVER (2026-10-13, lab/docs/FOREVER-PLAN.md) — the site the morning after, in this skin.
   The engine hides the other phase ([data-when], engine.css). Below: the guestbook (guestbook.js,
   the plaques kept), the day hour by hour (home), the chapters' heads on the story page.
   ============================================================================ */
.gb { display: grid; gap: var(--gap); }
.gb-head { display: flex; flex-wrap: wrap; align-items: end; gap: var(--gap); }
.gb-head .filters { margin-left: 0; }
.gb-list { columns: 2 14rem; column-gap: .9rem; }
.gb--strip .gb-list { columns: 3 12rem; }
.gb-card {
  display: grid; gap: .5rem; break-inside: avoid; margin-bottom: .9rem;
  padding: .8rem; background: var(--card); color: var(--ink);
  border: var(--rule-w) solid var(--rule); border-radius: var(--radius);
}
.gb-card[data-mine] { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.gb-shot { margin: 0; line-height: 0; }
.gb-shot img { display: block; width: 100%; height: auto; border-radius: calc(var(--radius) - 6px); cursor: zoom-in; }
.gb-say { font: 400 var(--step-1)/1.35 var(--display); overflow-wrap: anywhere; }
.gb-card[data-has-photo="false"] .gb-say { font-size: var(--step-2); padding-block: .3rem; }
.gb-say:empty { display: none; }
.gb-by { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; font: 600 var(--step--1)/1.3 var(--body); color: var(--accent-ink); }
.gb-mine { padding: .15em .6em; border-radius: 999px; background: var(--accent); color: var(--on-accent); }
.gb-empty { color: var(--muted); }

/* the day, hour by hour */
.hours { display: grid; margin: 0; padding: 0; list-style: none; }
.hour { display: grid; gap: .6rem 1.4rem; padding-block: clamp(1.1rem, .8rem + 1.3vw, 2rem); border-top: var(--rule-w) solid var(--rule); }
.hour:first-child { border-top: 0; }
.hour-time { font: 600 var(--step--1)/1 var(--body); letter-spacing: .14em; text-transform: uppercase; color: var(--accent-ink); }
.hour-say { display: grid; gap: .4rem; align-content: start; }
.hour-say h3 { font: 400 var(--step-2)/1.15 var(--display); }
.hour-say p { color: var(--muted); max-width: 32em; }
.hour-shot { margin: 0; }
.hour-shot img { display: block; width: 100%; aspect-ratio: 3 / 2; object-fit: cover; border-radius: var(--radius); cursor: zoom-in; }
.hour-shot figcaption { margin-top: .4rem; font-size: var(--step--1); color: var(--faint); }
@media (min-width: 46em) {
  .hour { grid-template-columns: 7rem minmax(0, 1fr) minmax(0, 1.1fr); align-items: start; }
  .hour-time { padding-top: .45rem; }
}

/* the day's three chapters on the story page */
.fchapter .chapter-head { display: grid; gap: .5rem; align-content: start; }

/* ============================================================================
   FOREVER, ROUND 2 (2026-10-15, lab/docs/FOREVER-PLAN.md §6) — the morning after, and the years
   after it. The engine: clock.js (years later), anniversary.js, yours.js, predictions.js,
   journal.js, origins.js, capsule.js, postcard.js, print.js. This is the skin's dress for them:
   paper cards, cherry for what is yours, round corners, no hard object.
   ============================================================================ */

/* the dock: which morning (chrome.js wires [data-wsd-era]). A row of small pills, so the panel
   does not grow three buttons taller on a short phone. */
.dock-era { display: flex; flex-wrap: wrap; gap: .4rem; padding-top: .7rem; border-top: var(--rule-w) solid var(--rule); }
.dock-era-say { flex: 1 0 100%; font: 600 var(--step--1)/1.3 var(--ui); letter-spacing: .12em; text-transform: uppercase; color: var(--accent-ink); }
.dock-era-say span { letter-spacing: .02em; text-transform: none; font-weight: 400; color: var(--muted); }
.dock-era .demo-btn { padding: .5em .85em; letter-spacing: .06em; }
.demo-btn[aria-pressed="true"] { background: var(--cherry); border-color: var(--cherry); color: var(--on-accent); }

/* the banner and the thank-you, at the top of home */
.yours-band { padding-block: clamp(1.4rem, 1rem + 2vw, 2.6rem); }
.yours-band .sheet { display: grid; gap: clamp(1.1rem, .8rem + 1.3vw, 1.8rem); }
.anniv {
  display: grid; gap: .75rem; padding: clamp(.9rem, .7rem + .8vw, 1.2rem) clamp(1rem, .7rem + 1.2vw, 1.5rem);
  background: var(--sumi); color: var(--paper); border-radius: var(--radius);
}
.anniv-say { font: var(--step-0)/1.55 var(--body); max-width: 46em; }
.anniv-say b { font: 400 var(--step-1)/1.2 var(--display); margin-right: .25em; color: var(--blossom); }
.anniv-go { display: flex; flex-wrap: wrap; gap: .45rem; }
.anniv-btn {
  min-height: 44px; padding: .5em 1em; border-radius: 999px;
  border: var(--rule-w) solid color-mix(in oklab, var(--paper) 45%, transparent); background: transparent;
  color: var(--paper); font: 600 var(--step--1)/1.1 var(--ui); letter-spacing: .1em; text-transform: uppercase;
}
.anniv-btn:hover { background: color-mix(in oklab, var(--paper) 12%, transparent); }
.anniv-btn[aria-pressed="true"] { background: var(--blossom); border-color: var(--blossom); color: var(--sumi); }
.anniv-btn:focus-visible { outline-color: var(--blossom); }

.yours { display: grid; gap: .55rem; padding: clamp(1rem, .8rem + 1vw, 1.6rem); background: var(--card); border: var(--rule-w) solid var(--rule); border-radius: var(--radius); box-shadow: var(--a-lift); }
.yours .eyebrow { display: flex; align-items: center; gap: .5rem; }
.yours-head { font: 400 var(--step-4)/1.05 var(--display); }
.yours-lede { color: var(--muted); max-width: 36em; }
.yours-trail { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr)); gap: 0 var(--gap); margin: .4rem 0 0; }
.yours-trail > div { display: grid; gap: .2rem; padding-block: .75rem; border-top: var(--rule-w) solid var(--rule); }
.yours-trail dt { font: 600 var(--step--1)/1.3 var(--ui); letter-spacing: .14em; text-transform: uppercase; color: var(--faint); }
.yours-trail dd { margin: 0; font: 400 var(--step-1)/1.3 var(--display); color: var(--ink); overflow-wrap: anywhere; }

/* did it come true? */
.guess-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr)); gap: var(--gap); margin: 0; padding: 0; list-style: none; }
.guess-card { display: grid; gap: .75rem; align-content: start; padding: clamp(1rem, .8rem + .8vw, 1.4rem); background: var(--card); border: var(--rule-w) solid var(--rule); border-radius: var(--radius); }
.guess-q { font: 400 var(--step-2)/1.2 var(--display); }
.guess-opts { display: grid; gap: .45rem; margin: 0; padding: 0; list-style: none; }
.guess-pick {
  position: relative; display: flex; justify-content: space-between; align-items: center; gap: 1rem; width: 100%;
  min-height: 44px; padding: .55rem .9rem; text-align: left; overflow: hidden; isolation: isolate;
  background: var(--bg); color: var(--ink); border: var(--rule-w) solid var(--rule-strong); border-radius: 999px;
  font: var(--step-0)/1.3 var(--body);
}
.guess-pick::before {
  content: ""; position: absolute; inset: 0 auto 0 0; z-index: -1; width: calc(var(--share, 0) * 100%);
  background: var(--accent-soft); transition: width var(--motion-slow) var(--ease);
}
.guess-card:not([data-picked]):not([data-called]) .guess-pick::before,
.guess-card:not([data-picked]):not([data-called]) .guess-share { visibility: hidden; }
.guess-pick:hover:not(:disabled) { border-color: var(--accent); }
.guess-pick[aria-pressed="true"] { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.guess-pick:disabled { cursor: default; }
.guess-share { font: 600 var(--step--1)/1 var(--ui); color: var(--accent-ink); font-variant-numeric: tabular-nums; }
.guess-opt[data-answer] .guess-pick { border-color: var(--ink); }
.guess-opt[data-answer] .guess-label::after { content: "  \2713"; color: var(--accent-ink); font-weight: 700; }
.guess-said { color: var(--muted); }
.guess-said:empty, .guess-verdict:empty { display: none; }
.guess-verdict { font: 600 var(--step--1)/1.3 var(--ui); letter-spacing: .12em; text-transform: uppercase; color: var(--faint); }
.guess-card[data-called="yes"] { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.guess-card[data-called="yes"] .guess-verdict { color: var(--accent-ink); }

/* the first year (Story) */
.journal-list { display: grid; margin: 0; padding: 0; list-style: none; max-width: 46rem; }
.jentry { display: grid; gap: .45rem; padding-block: clamp(1.1rem, .8rem + 1.3vw, 1.8rem); border-top: var(--rule-w) solid var(--rule); }
.jentry-when { font: 600 var(--step--1)/1.3 var(--ui); letter-spacing: .14em; text-transform: uppercase; color: var(--accent-ink); }
.jentry-title { font: 400 var(--step-2)/1.2 var(--display); }
.jentry-body { display: grid; gap: .5rem; }
.jentry-body p { max-width: 34em; }
.jentry-shot { margin: .4rem 0 0; max-width: 34rem; line-height: 0; }
.jentry-shot img { display: block; width: 100%; height: auto; border-radius: var(--radius); cursor: zoom-in; }
.jentry[data-shape="tall"] .jentry-shot { max-width: 20rem; }
.jentry[data-latest] .jentry-when::after { content: " \00b7  newest"; color: var(--faint); }
.journal-next { padding-top: var(--gap); border-top: var(--rule-w) solid var(--rule); color: var(--muted); max-width: 40em; }

/* the day in numbers (home, after the hours) */
.numbers { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 var(--gap); margin: 0; border-top: 2px solid var(--ink); }
@media (min-width: 52em) { .numbers { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.numbers > div { display: flex; flex-direction: column-reverse; justify-content: flex-end; gap: .3rem; padding-block: clamp(.9rem, .7rem + 1vw, 1.5rem); border-bottom: var(--rule-w) solid var(--rule); }
.numbers dd { margin: 0; font: 400 var(--step-5)/.95 var(--display); color: var(--accent-ink); font-variant-numeric: lining-nums; }
.numbers dt { font: 600 var(--step--1)/1.35 var(--ui); letter-spacing: .1em; text-transform: uppercase; color: var(--faint); }

/* the recipe card (Travel) */
.recipe { display: grid; gap: var(--gap); padding: clamp(1rem, .7rem + 1.4vw, 1.8rem); background: var(--card); border: var(--rule-w) solid var(--rule); border-radius: var(--radius); box-shadow: var(--a-lift); }
@media (min-width: 60em) { .recipe { grid-template-columns: minmax(0, .9fr) minmax(0, 1.4fr); align-items: start; } }
.recipe-shot { margin: 0; line-height: 0; }
.recipe-shot img { display: block; width: 100%; height: auto; border-radius: calc(var(--radius) - 4px); cursor: zoom-in; }
.recipe-say { display: grid; gap: .7rem; }
.recipe-title { font: 400 var(--step-3)/1.1 var(--display); }
.recipe-meta { color: var(--muted); }
.recipe-cols { display: grid; gap: var(--gap); }
@media (min-width: 40em) { .recipe-cols { grid-template-columns: minmax(0, .9fr) minmax(0, 1.2fr); } }
.recipe-cols h4 { margin: 0 0 .5rem; font: 600 var(--step--1)/1.3 var(--ui); letter-spacing: .14em; text-transform: uppercase; color: var(--accent-ink); }
.recipe-list, .recipe-steps { display: grid; gap: .45rem; margin: 0; padding: 0; list-style: none; }
.recipe-list li { position: relative; padding-left: 1rem; }
.recipe-list li::before { content: ""; position: absolute; left: 0; top: .65em; width: .4rem; height: .4rem; border-radius: 50%; background: var(--blossom); }
.recipe-steps { counter-reset: step; }
.recipe-steps li { counter-increment: step; display: grid; grid-template-columns: 1.7rem minmax(0, 1fr); }
.recipe-steps li::before { content: counter(step); font: 400 var(--step-1)/1.1 var(--display); color: var(--accent-ink); }
.recipe-print { background: none; border: 0; padding: 0; cursor: pointer; min-height: 44px; }

/* where you all came from (Travel, by the map) */
.origins-body { display: grid; gap: var(--gap); }
@media (min-width: 56em) { .origins-body { grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); align-items: start; } }
.origins-chart { margin: 0; display: grid; gap: .6rem; }
.origins-chart svg { width: 100%; max-width: 32rem; height: auto; margin-inline: auto; overflow: visible; }
.origins-chart figcaption { font-size: var(--step--1); color: var(--faint); max-width: 32rem; }
.og-ring { fill: none; stroke: var(--rule-strong); stroke-width: 1; }
.og-ring--edge { stroke: var(--ink); stroke-width: 1.5; }
.og-ring-label { font: 600 9px var(--ui); letter-spacing: .06em; fill: var(--muted); }
.og-axis { stroke: var(--rule-strong); stroke-dasharray: 2 4; }
.og-compass { font: 600 11px var(--ui); letter-spacing: .12em; fill: var(--muted); }
.og-centre { fill: var(--sumi); }
.og-dot circle { fill: var(--cherry-ink); stroke: var(--bg); stroke-width: 2; }
.og-no { font: 700 9.5px var(--ui); fill: #fff; }
.origins-list { display: grid; margin: 0; padding: 0; list-style: none; }
@media (min-width: 34em) and (max-width: 55.99em) { .origins-list { display: block; columns: 2 14rem; column-gap: var(--gap); } }
.origin-row { display: grid; grid-template-columns: 1.8rem minmax(0, 1fr); gap: .1rem .5rem; padding-block: .5rem; border-top: var(--rule-w) solid var(--rule); break-inside: avoid; }
.origin-no { grid-row: span 2; font: 700 var(--step--1)/1.7 var(--ui); color: var(--accent-ink); }
.origin-place { font: 400 var(--step-1)/1.25 var(--display); }
.origin-meta { font-size: var(--step--1); color: var(--muted); }

/* your postcard (the reply page, after) */
.pcard-body { display: grid; gap: var(--gap); }
@media (min-width: 56em) { .pcard-body { grid-template-columns: minmax(0, .8fr) minmax(0, 1.2fr); align-items: start; } }
.pcard-side { display: grid; gap: .9rem; }
.pcard-say { font: 600 var(--step--1)/1.3 var(--ui); letter-spacing: .14em; text-transform: uppercase; color: var(--faint); }
.pcard-picks { display: grid; grid-template-columns: repeat(auto-fill, minmax(4.5rem, 1fr)); gap: .5rem; margin: 0; padding: 0; list-style: none; }
.pcard-pick-btn { display: block; width: 100%; padding: 0; aspect-ratio: 1; overflow: hidden; background: var(--bg-raised); border: 2px solid transparent; border-radius: calc(var(--radius) - 4px); }
.pcard-pick-btn img { width: 100%; height: 100%; object-fit: cover; }
.pcard-pick-btn[aria-pressed="true"] { border-color: var(--accent); }
.pcard-pick[data-mine] .pcard-pick-btn { box-shadow: 0 0 0 2px var(--blossom); }
.pcard-stage { display: grid; gap: .8rem; justify-items: start; }
.postcard {
  display: grid; width: 100%; max-width: 40rem; overflow: hidden; background: var(--card-raw); color: var(--sumi);
  border: var(--rule-w) solid var(--rule); border-radius: var(--radius); box-shadow: var(--a-shadow);
}
@media (min-width: 40em) { .postcard { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); } }
.postcard-shot { margin: 0; line-height: 0; background: var(--bg-raised); }
.postcard-shot img { width: 100%; height: 100%; max-height: 28rem; object-fit: cover; }
.postcard-back { display: grid; gap: .7rem; align-content: start; padding: clamp(1rem, .8rem + 1vw, 1.5rem); border-left: var(--rule-w) dashed var(--rule-strong); }
.postcard-to { font: 400 var(--step-2)/1.2 var(--display); overflow-wrap: anywhere; }
.postcard-from { font: 400 var(--step-1)/1.3 var(--display); color: var(--accent-ink); }
.postcard-where { font: 600 var(--step--1)/1.4 var(--ui); letter-spacing: .1em; text-transform: uppercase; color: var(--faint); }

/* the time capsule (the reply page, both phases) */
.capsule-form { display: grid; gap: .9rem; }
.capsule-years { display: flex; flex-wrap: wrap; gap: .3rem 1.2rem; margin: 0; padding: 0; border: 0; }
.capsule-years legend { width: 100%; margin-bottom: .3rem; padding: 0; font: 600 var(--step--1)/1.3 var(--ui); letter-spacing: .14em; text-transform: uppercase; color: var(--faint); }
.capsule-years label { display: inline-flex; align-items: center; gap: .45rem; min-height: 44px; }
.capsule-years input { accent-color: var(--accent); width: 1.1rem; height: 1.1rem; }
.capsule-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr)); gap: .8rem; margin: 0; padding: 0; list-style: none; }
.envelope {
  display: grid; gap: .35rem; align-content: start; padding: 1rem 1.1rem;
  background: var(--card); border: var(--rule-w) solid var(--rule); border-radius: var(--radius);
  border-top: 4px solid var(--rule-strong);
}
.envelope[data-open="true"] { border-top-color: var(--cherry); }
.envelope[data-mine] { box-shadow: 0 0 0 1px var(--accent); }
.envelope:not([data-mine]) .envelope-mine { display: none; }
.envelope-mine { margin-left: .5rem; padding: .1em .55em; border-radius: 999px; background: var(--accent); color: var(--on-accent); font: 600 var(--step--1)/1.3 var(--ui); }
.envelope-when { font: 600 var(--step--1)/1.3 var(--ui); letter-spacing: .14em; text-transform: uppercase; color: var(--faint); }
.envelope-from { font: 400 var(--step-1)/1.3 var(--display); }
.envelope-text:empty { display: none; }
.envelope-status { display: flex; align-items: center; gap: .5rem; font-size: var(--step--1); color: var(--muted); }
.envelope-seal { flex: none; width: .95rem; height: .95rem; border-radius: 50%; background: var(--cherry); box-shadow: inset 0 0 0 3px color-mix(in oklab, var(--paper) 30%, transparent); }

/* print: the recipe card and the postcard, alone on the page */
@media print {
  .wsd-print-sheet .recipe { box-shadow: none; border: 0; padding: 0; grid-template-columns: minmax(0, .8fr) minmax(0, 1.4fr); }
  .wsd-print-sheet .recipe-do { display: none; }
  .wsd-print-sheet .postcard { box-shadow: none; max-width: none; grid-template-columns: 1fr 1fr; break-inside: avoid; }
  .wsd-print-sheet .postcard-shot img { max-height: none; }
}

/* the first year, its own page (2026-10-15, the user: "a separate page for the couple's journey", the
   journal moved off Our story) and its highlight on home: the newest entry, then the way in */
.journal--page .journal-lede { font: 600 var(--step--1)/1.3 var(--ui); letter-spacing: .14em; text-transform: uppercase; color: var(--faint); }
.journal-when { display: grid; gap: .6rem; color: var(--muted); }
.journal-go .anniv-btn { border-color: var(--rule-strong); color: var(--ink); }
.journal-go .anniv-btn:hover { background: var(--accent-soft); border-color: var(--accent); }
.journal-go .anniv-btn[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.journal-go .anniv-btn:focus-visible { outline-color: var(--accent); }
.first-hi .jentry { border-top: 2px solid var(--accent); max-width: 46rem; }
