/* fracture.css — THE CRYSTALLISATION (dark → cream).
 *
 * The cooling that began at the hero completes here: the dark computational plane fractures into a
 * low-poly, crystalline facet field and resolves into Atheric's cream — the brand's paper home (v1
 * editorial). One scroll signal, --fracture (0→1, body.js), drives everything. It stays 0 across
 * the dark sections and 1 across the cream close; it only ramps across a TEXT-FREE band, so the
 * busy mixed-facet zone never fights type. This is the readability strategy, made structural.
 *
 * Layers (both fixed, z-index 0 — in front of the dark backdrop stack at negative z-index, behind
 * all content at z-index ≥ 1):
 *   #cream-layer    solid --paper, floods in during the latter transition → guarantees the cream
 *                   close (robust even if color-mix is unsupported) and backs the facets so the
 *                   facet shimmer is invisible once resolved.
 *   #fracture-layer the SVG facet field, on top of the cream layer; each facet resolves its own
 *                   near-black tone → cream as --fracture passes the facet's --t.
 */

/* ── The cream ground ─────────────────────────────────────────────────────────────────────── */
#cream-layer {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: var(--paper);
  /* floods in during the second half of the transition, then holds for the cream close */
  opacity: clamp(0, calc((var(--fracture, 0) - 0.25) / 0.6), 1);
}

/* ── The facet field ──────────────────────────────────────────────────────────────────────────
   Rendered on a <canvas> by body.js, NOT per-facet CSS: driving 192 polygon fills off a root
   custom property forces a full style recalc every scroll frame (~33ms → 30fps). Canvas keeps the
   same crafted geometry (read from the hidden #fracture-geo <svg>, which stays as the slot-in
   source) but repaints in one cheap pass → 60fps. The facet dark→cream resolve, the tonal spread,
   and the in-place shimmer all live in body.js's draw. */
#fracture-layer {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  /* Always opaque as a layer. The field's entry is spatial, not temporal: the canvas clears
     itself on the dark (curF ≤ 0.001 — the dark sections stay pristine) and the PRESENCE FADE
     in the draw (body.js §2b) sinks every facet to the cover's own opaque black at the
     .dark-run's bottom edge, gaining true tone over a long falloff below. This also lets the
     near-edge paint occlude the annealing pool, so the cover edge can't cut the glow as a
     straight line. (The old opacity ramp off --fracture left the canvas translucent through
     the band's entry, exposing exactly that cut.) */
}
#fracture-layer canvas {
  display: block;
  width: 100%;
  height: 100%;
}

/* ── The transition band ──────────────────────────────────────────────────────────────────────
   A tall, text-free section between the cabinet and the cream close. It supplies the scroll
   distance for the ramp; being taller than the viewport guarantees the mixed-facet midpoint never
   shares the screen with type. Its only contents are two quiet artifacts, each only ever on
   resolved ground: the entry note (dark side — shown early, faded well before the busy middle)
   and the set-seal (cream side — see below). */
.fracture {
  position: relative;
  height: 138vh;
  /* THE SEAL'S BERTH (2026-07-06). Border-box padding shortens the set-seal's STICKY RANGE
     only — the band's painted height and the --fracture ramp (body.js reads the border box)
     are untouched. Without it the sticky seal rode the viewport to the band's very edge and
     came to rest glued against the voice datum — the title block never landed on the sheet.
     Now it un-pins here and settles ON the cream with clear paper below it, like ink. */
  padding-bottom: clamp(90px, 16vh, 180px);
}
.fracture__note {
  position: sticky;
  top: 40vh;
  margin: 0 auto;
  width: max-content;
  max-width: 90vw;
  display: flex;
  align-items: center;
  gap: 11px;
  font-family: var(--font-label);
  font-size: 0.69rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-dim);
  text-align: center;
  /* visible while the facets are still dark, gone before they brighten */
  opacity: clamp(0, calc((0.32 - var(--fracture, 0)) / 0.16), 1);
}

/* ── The set-seal, fig. 06 — the casting's title block (the crystallisation's coda) ───────────
   What a machinist's drawing carries at the sheet's corner: the certification tab of the cast.
   It holds the cream emptiness the resolve leaves in the band's second half. Driven entirely by
   --fracture in CSS — no JS, no timers, fully scroll-reversible — staged like drafting, not
   like a reveal: the frame's four hairlines scribe themselves CLOCKWISE (the craft stage's own
   drawn-rule grammar), the divider sets, the ledger inks row by row, then the studio mark
   fig. 00 drew at the masthead is APPLIED — the stamp presses in, its orbit scribes, ticks
   extend, the punctum arrives last of all — FLAT accent (the wordmark's period): no glow on
   paper, per the materials law. The fig. caption inks beneath once certified. Ground law,
   measured (2026-07-06, canvas-sampled at 1440×900 / 390×844 / 1638×480 + RM): by 0.74 —
   the moment the ink reaches full opacity — the tab's station (box centred ≈48vh) is
   zero-defect cream; through the 0.66→0.74 fade the worst residue under the box is
   near-cream warm grey (R≥200) on ≤3% of the area while the ink is still translucent —
   sub-perceptual at every tested width. The band's padding-bottom ends the sticky ride
   early so the certified tab SETTLES on the sheet well clear of the voice datum. */
.fracture__seal {
  /* the score — each part's window on the one scroll signal */
  --fo: clamp(0, calc((var(--fracture, 0) - 0.66) / 0.08), 1); /* arrival (container) */
  --ft: clamp(0, calc((var(--fracture, 0) - 0.66) / 0.06), 1); /* frame: top, L→R */
  --fr: clamp(0, calc((var(--fracture, 0) - 0.7) / 0.06), 1); /* frame: right, T→B */
  --fb: clamp(0, calc((var(--fracture, 0) - 0.74) / 0.06), 1); /* frame: bottom, R→L */
  --fl: clamp(0, calc((var(--fracture, 0) - 0.78) / 0.06), 1); /* frame: left, B→T */
  --fd: clamp(0, calc((var(--fracture, 0) - 0.8) / 0.04), 1); /* the divider */
  --fm: clamp(0, calc((var(--fracture, 0) - 0.8) / 0.06), 1); /* the stamp: press + orbit */
  --fk: clamp(0, calc((var(--fracture, 0) - 0.85) / 0.03), 1); /* the ticks extend */
  --fp: clamp(0, calc((var(--fracture, 0) - 0.875) / 0.03), 1); /* the punctum, last */
  --fc: clamp(0, calc((var(--fracture, 0) - 0.9) / 0.03), 1); /* the fig. caption */
  position: sticky;
  /* Pin so the BOX reads optically centred (its centre ≈ 48vh; box ≈ 113px tall, caption
     rides below) — robust from short ultrawide (≈480px) to tall viewports. The old
     `top: 52vh` put the box's centre near 60–70% of the viewport and read low. */
  top: calc(48vh - 56px);
  margin: 0 auto;
  width: max-content;
  max-width: 92vw;
  display: grid;
  justify-items: center;
  gap: 12px;
  opacity: var(--fo);
}
/* the boxed tab itself — the caption sits in flow BELOW it (inside the sticky footprint, so
   the assembly can never overhang the band into the voice section's datum rule) */
.seal__box {
  position: relative;
  display: grid;
  grid-template-columns: auto auto;
  align-items: center;
  gap: 0 26px;
  padding: 19px 26px;
}
/* the frame — four hairlines that scribe clockwise (transform-only) */
.seal__edge {
  position: absolute;
  background: rgba(10, 10, 10, 0.32);
}
.seal__edge--t {
  top: 0; left: 0; width: 100%; height: 1px;
  transform: scaleX(var(--ft));
  transform-origin: left center;
}
.seal__edge--r {
  top: 0; right: 0; width: 1px; height: 100%;
  transform: scaleY(var(--fr));
  transform-origin: center top;
}
.seal__edge--b {
  bottom: 0; left: 0; width: 100%; height: 1px;
  transform: scaleX(var(--fb));
  transform-origin: right center;
}
.seal__edge--l {
  top: 0; left: 0; width: 1px; height: 100%;
  transform: scaleY(var(--fl));
  transform-origin: center bottom;
}
/* the stamp cell — pressed in while the sheet is still warm */
.seal__stamp {
  transform: scale(calc(1.06 - 0.06 * var(--fm)));
}
.seal__mark {
  display: block;
  width: 46px;
  height: 46px;
  overflow: visible;
}
.seal__orbit {
  fill: none;
  stroke: rgba(10, 10, 10, 0.55);
  stroke-width: 1;
  /* no vector-effect here: non-scaling-stroke computes dashes in screen space and breaks the
     pathLength=100 normalisation (an ~8% seam at the NE station even fully scribed). The mark
     renders at a fixed 46px, so the scaled hairline is correct anyway. */
  stroke-dasharray: 100;
  stroke-dashoffset: calc(100 - 100 * var(--fm));
}
.seal__tick {
  stroke: rgba(10, 10, 10, 0.38);
  stroke-width: 1;
  opacity: var(--fk);
}
.seal__punctum {
  fill: var(--accent); /* flat — no glow/blur on paper, ever */
  opacity: var(--fp);
}
/* the ledger — the cast's record, inked row by row */
.seal__ledger {
  position: relative;
  display: grid;
  gap: 7px;
}
.seal__ledger::before {
  /* the divider between stamp and record; the -19px reach spans the container padding so it
     meets the frame's top and bottom hairlines */
  content: '';
  position: absolute;
  left: -14px;
  top: -19px;
  bottom: -19px;
  width: 1px;
  background: rgba(10, 10, 10, 0.22);
  transform: scaleY(var(--fd));
  transform-origin: center top;
}
.seal__row {
  --w: 0;
  margin: 0;
  font-family: var(--font-label);
  font-size: 0.64rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-on-cream-dim);
  opacity: var(--w);
  transform: translateY(calc(6px * (1 - var(--w))));
}
.seal__row:nth-child(1) { --w: clamp(0, calc((var(--fracture, 0) - 0.76) / 0.05), 1); }
.seal__row:nth-child(2) { --w: clamp(0, calc((var(--fracture, 0) - 0.79) / 0.05), 1); }
.seal__row:nth-child(3) { --w: clamp(0, calc((var(--fracture, 0) - 0.82) / 0.05), 1); }
.seal__row:nth-child(4) { --w: clamp(0, calc((var(--fracture, 0) - 0.85) / 0.05), 1); }
.seal__row--set {
  color: rgba(10, 10, 10, 0.78); /* the certifying line carries a touch more ink */
}
/* small screens: tighten the tab's measure so every ledger row holds one line */
@media (max-width: 600px) {
  .seal__box {
    gap: 0 18px;
    padding: 16px 18px;
  }
  .seal__ledger::before {
    left: -9px;
    top: -16px;
    bottom: -16px;
  }
  .seal__row {
    font-size: 0.58rem;
    letter-spacing: 0.13em;
  }
  .seal__mark {
    width: 40px;
    height: 40px;
  }
}

/* the fig. caption — below the frame, inked once the cast is certified */
.seal__cap {
  margin: 0;
  text-align: center;
  font-family: var(--font-label);
  font-size: 0.62rem;
  letter-spacing: 0.2em; /* matched to fig. 00's caption tracking — one fig. voice */
  color: rgba(10, 10, 10, 0.5);
  opacity: var(--fc);
}

/* ── The loose facet — the band's one secret ──────────────────────────────────────────────────
   ONE crystal of the field never quite set — and it sets LAST (its own data-t 0.54 /
   data-w 0.10 in #fracture-geo hold it dark while the field crystallises around it, yet
   fully cream before the set-seal's station law at 0.66). While it is dark and unveiled it
   carries a visible warm breath and a faint candy whisper at its seam — light leaking from
   behind the loose crystal (canvas-drawn, body.js §2b; a static composed hint under reduced
   motion) — the only hint that exists anywhere. Under a fine pointer the seam ANSWERS the
   hand: brighter light, a quicker breath. This button IS the facet: its fixed position and
   clip-path are written by body.js from the same parsed polygon, so the hit area is exactly the
   triangle. It is live ONLY while the band covers it, the .dark-run's cover has cleared
   (presence), and the crystal is un-set — outside that window it is invisible, unfocusable and
   inert, so it can never sit over type or any other control. Activation (click / Enter) springs
   the seam: the one-shot candy-lit escape, drawn on the field's own canvas, resolving back to
   the exact resting band. No outline: the focus indicator is drawn diegetically on the canvas —
   a bone hairline around the true triangle (a CSS ring could only trace the bounding box). */
.facet-key {
  position: fixed;
  z-index: 2; /* above the field (z 0); the live window keeps it inside the text-free band */
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  appearance: none;
  cursor: pointer; /* the one permitted affordance: it is clickable, nothing more */
  -webkit-tap-highlight-color: transparent; /* no grey flash betraying the shape on touch */
  touch-action: manipulation;
  visibility: hidden;
  pointer-events: none;
}
.facet-key--live {
  visibility: visible;
  pointer-events: auto;
}
.facet-key:focus,
.facet-key:focus-visible {
  outline: none; /* replaced, not removed — body.js strokes the facet itself on :focus-visible */
}

/* ── The cream close (voice → begin → footer) ─────────────────────────────────────────────────
   Elegant flip: body.css reads semantic tokens everywhere, so redefining the ink/rule roles on
   .on-cream re-inks every heading, lede, list and hairline to near-black on cream automatically.
   The one hot orange accent (--accent) is untouched — it belongs on cream (v1). */
.on-cream {
  --ink: var(--ink-on-cream);
  --ink-dim: var(--ink-on-cream-dim);
  --ink-faint: rgba(10, 10, 10, 0.52); /* small mono on paper needs a touch more ink than bone-on-dark */
  --rule: var(--rule-on-cream);
  --rule-strong: rgba(10, 10, 10, 0.22);
  position: relative;
  z-index: 1; /* above the fixed cream/fracture layers (also via .broadsheet, but be explicit) */
  color: var(--ink);
}
/* the hairline between do/do-not items is a hardcoded bone rgba in body.css — re-ink on cream */
.on-cream .dd li {
  border-top-color: rgba(10, 10, 10, 0.08);
}
/* (the invitation CTA's dark-pill flip retired 2026-08-09 — the composer replaced the CTA;
   its send control bakes the v1 dark-pill grammar in body.css directly) */
/* the warm computational glow belongs to the dark plane, not the cream — the cream IS the resolve */
.on-cream .begin__glow {
  display: none;
}

/* ── The cast's edge — cream → dark, the loop closed (static) ─────────────────────────────────
   The reverse seam. The entry band crystallised the dark INTO cream in motion; here the cast
   sheet simply ENDS on the dark bench — the same triangular grammar, the same warm-biased tonal
   ramp (body.js §2b's curve, baked into the fills), and NO motion of any kind: a fixed inline-SVG
   composition, identical under reduced motion, zero runtime cost. The tones step --paper →
   --bone #efece4 → warm greys → #050505; facets laying an edge on the strip's top are exact
   paper and on its bottom exact ground-dark, so both meets are seamless and the fragmentation
   carries the whole transition. Two authored plates keep facet proportions in the entry field's
   family at both sheet sizes (preserveAspectRatio="none" stretch stays near-uniform within each
   plate's width range). No text ever enters the strip; the begin section's bottom padding and
   the footer's top padding hold the neighbours clear. */
.cast-edge {
  position: relative;
  z-index: 1; /* above the fixed cream/fracture layers, like all content */
  font-size: 0; /* no inline-svg baseline slack — the plates butt both grounds exactly */
}
.cast-edge__plate {
  display: block;
  width: 100%;
}
/* ≈ the authored 1600×230 proportion at 1440 (15vw ≈ 230/1600) */
.cast-edge__plate--wide {
  height: clamp(150px, 15vw, 300px);
}
.cast-edge__plate--narrow {
  display: none;
  height: clamp(120px, 26vw, 200px);
}
@media (max-width: 860px) {
  .cast-edge__plate--wide {
    display: none;
  }
  .cast-edge__plate--narrow {
    display: block;
  }
}

/* Reduced motion: the field's life is frozen in body.js (no rAF loop, no nucleation inertia, no
   ember cracks, no glint, no shimmer — the field is drawn once per scroll position, static); the
   black→cream resolve still tracks scroll, as the brief requires. The unveil gate (facets stay
   dark near the .dark-run's receding bottom edge) applies there too — it is spatial correctness,
   not motion. The set-seal title block presents composed (no scribing frame, no row stagger,
   no press, no staged stamp); like the facets, its appearance still tracks scroll. */
@media (prefers-reduced-motion: reduce) {
  .seal__edge,
  .seal__ledger::before,
  .seal__stamp,
  .seal__row {
    transform: none;
  }
  .seal__orbit {
    stroke-dashoffset: 0;
  }
  .seal__tick,
  .seal__punctum,
  .seal__row,
  .seal__cap {
    opacity: 1; /* the container's scroll-tracked opacity governs */
  }
}

/* ── THE HOLLOW — the room behind the loose facet ─────────────────────────────────────────────
   The band's secret is a PLACE — and v3 (2026-07-30) rebuilt its architecture. The vault-
   object is dead: no geode ring, no central void, nothing to look AT. The ENTIRE viewport is
   the room — a full-bleed triangulated field, edge to edge, that the visitor stands INSIDE.
   Mystical, almost sacred: dark crystal underfoot and overhead, the candy spectrum
   (#ff2e88 / #ff9e2e / #5f7bff — illumination, never paint) crossing THROUGH the field as
   three wandering presences, seams igniting as they pass, motes rising like incense.

   The field lives on ONE <canvas> (body.js §2b → the field): a welded shared-vertex mesh
   that breathes at the vertices — travelling drift, depth parallax under the pointer, the
   facets parting around the hand — so the living geometry can never open a gap. All candy
   is drawn additively (composite 'lighter'): seam halo + near-white core, the fig. 07
   seam-light grammar. The room answers: light gathers to the cursor; press-and-hold
   GATHERS the room and release rings a luminous wave outward through the seams (Space
   rings it from the room's heart). CSS here only dresses the surfaces: the canvas and the
   nave fade in on the estate cadence, the words keep their v2 arrival.

   Mechanics (body.js §2b → the hollow): a native <dialog> (top layer, modal focus trap, Esc).
   Entry is the IRIS FLIGHT — the facet's own screen triangle (clip-pathed, candy-lit) scales
   up from its exact band position until it swallows the viewport, crossfading candy → the
   room's black; the room composes beneath it on the estate cadence. Exit reverses the flight
   and the crystal reseats on the canvas. The host page is never touched: scroll is guarded
   while inside, no layout is displaced (top layer), and the band's canvas state rests
   untouched behind the dialog — the return IS the resting band.

   Inside, the room owns its physics (sustained life is lawful — this is the light's home,
   the estate's one exception to the rest-state law); outside, the calm is untouched. The
   field's loop runs only while the dialog is open. */
.hollow {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent; /* the room paints itself; the flight must see the band behind */
  overflow: hidden;
  color: var(--ink-on-dark);
}
.hollow::backdrop {
  background: transparent; /* the band remains visible under the iris flight */
}

/* — the room itself: darker than the plane — the only truly black ground on the estate — */
.hollow__room {
  position: absolute;
  inset: 0;
  background: #020202;
  opacity: 0;
  transition: opacity 0.5s var(--ease);
}
.hollow--in .hollow__room {
  opacity: 1;
}

/* — the field: the room's one canvas. The room paints itself here (body.js §2b — the
     field renderer); CSS only fades the surface in on the estate cadence. — */
.hollow__field {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity 1.1s var(--ease) 0.15s;
}
.hollow--in .hollow__field {
  opacity: 1;
}

/* — the nave: a feathered dark where the words hang — legibility as DEPTH (the field runs
     on beneath the type, dimmed, never absent), deliberately NOT a panel and NOT a vault:
     a wide soft falloff, no edge, no shape to look at. Above the canvas, below the type. — */
.hollow__nave {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(
    ellipse 52% 36% at 50% 73%,
    rgba(2, 2, 2, 0.7),
    rgba(2, 2, 2, 0.42) 55%,
    rgba(2, 2, 2, 0) 80%
  );
  opacity: 0;
  transition: opacity 1.2s var(--ease) 0.35s;
}
.hollow--in .hollow__nave {
  opacity: 1;
}

/* — the words at the void's centre. The column scrolls ITSELF on short viewports
     (grid + margin:auto centring — the classic safe-centre; body.js's guards stand
     aside when it truly overflows) — */
.hollow__center {
  position: absolute;
  inset: 0;
  display: grid;
  overflow-y: auto;
  overscroll-behavior: contain;
  /* the words keep the LOWER band — the core owns the upper heart (body.js seats it at
     ~29% of the window; this padding keeps the column clear of the body's grown reach) */
  padding: clamp(250px, 54vh, 620px) 24px 24px;
  text-align: center;
}
.hollow__col {
  margin: auto;
  max-width: min(680px, 88vw);
  display: grid;
  justify-items: center;
}
.hollow__kicker {
  margin: 0 0 26px;
  font-family: var(--font-label);
  font-size: 0.66rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.hollow__display {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 600;
  /* a register down from v3 — the display shares the stage with the core now */
  font-size: clamp(2.1rem, 4.8vw, 3.5rem);
  line-height: 1.04;
  letter-spacing: -0.045em;
  color: var(--ink-on-dark);
}
.hollow__display span {
  display: block; /* each authored line is its own reveal */
}
/* the display lines CONDENSE out of the dark — blur resolving to sharp, the room's own
   crystallisation at type scale (a finite entrance transition, not a held effect) */
.hollow__display span[data-hv] {
  filter: blur(9px);
  transition-property: opacity, transform, filter;
}
.hollow--in .hollow__display span[data-hv] {
  filter: blur(0);
}
.hollow__display em {
  font-family: var(--font-break);
  font-style: italic;
  font-weight: 400;
  letter-spacing: -0.01em;
  /* the ember — Clara's spectrum alive INSIDE the word (background-clip text; the shimmer
     repaints one word's box, nothing more), bloomed by its own drop-shadow */
  /* stops anchored pink at BOTH sweep rest points, so the ember only ever passes through
     tangerine/periwinkle and always comes home warm */
  background: linear-gradient(100deg, #ff2e88 4%, #ff9e2e 30%, #ff5ea4 50%, #7d94ff 72%, #ff2e88 96%);
  background-size: 300% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
  filter: drop-shadow(0 0 15px rgba(255, 46, 136, 0.45));
}
.hollow--in .hollow__display em {
  animation: hvEmber 11s ease-in-out 1.8s infinite;
}
@keyframes hvEmber {
  0%, 100% { background-position: 0% 0; }
  50% { background-position: 100% 0; }
}

/* — the inscriptions: the room's whole thesis in two lines, one breath each. Set larger
     than body serif (these are inscriptions now, not paragraphs), centred on the display's
     axis; each line's close carries its station of Clara's ramp — the display's ember is
     pink, i is tangerine, ii periwinkle — the palette structurally in the type. They
     CONDENSE out of the dark like the display lines (blur → sharp, a finite entrance). — */
.hollow__ins {
  margin: 42px auto 0;
  display: grid;
  gap: 19px;
}
.hollow__line {
  margin: 0;
  font-family: var(--font-break);
  font-size: clamp(1.12rem, 1.75vw, 1.36rem);
  line-height: 1.5;
  letter-spacing: 0.01em;
  color: var(--ink-dim);
}
.hollow__line[data-hv] {
  filter: blur(7px);
  transition-property: opacity, transform, filter;
}
.hollow--in .hollow__line[data-hv] {
  filter: blur(0);
}
.hollow__line em {
  font-style: italic;
  color: #ffab4d; /* the attention clause never cools — it is the burning part */
  filter: drop-shadow(0 0 14px rgba(255, 158, 46, 0.3));
}
/* — ONE line survives (the deep pass): it arrives CAST, the way a cell of the body is
     born — molten candy cooling to ink over a cell's own lifespan — while the em never
     cools. One-shot colour/text-shadow keyframes (paint-local, the ledeCool grammar);
     the base state is composed ink, so no-JS and reduced motion stand fully formed. — */
.hollow--in .hollow__line { animation: hvCast 2.4s var(--ease) 1.5s both; }
@keyframes hvCast {
  from { color: #ffab4d; text-shadow: 0 0 18px rgba(255, 158, 46, 0.5); }
  to { color: var(--ink-dim); text-shadow: 0 0 0 rgba(255, 158, 46, 0); }
}
/* — and the words LIVE with the body: when the lattice rings or chimes (body.js adds
     .hollow--chime for a breath), the burning clauses flare — the type answers the core — */
.hollow--chime .hollow__line em { animation: hvFlare 1.3s var(--ease); }
.hollow--chime .hollow__display em { animation: hvEmber 11s ease-in-out infinite, hvFlareD 1.3s var(--ease); }
@keyframes hvFlare {
  30% { color: #ffd29a; filter: drop-shadow(0 0 22px rgba(255, 158, 46, 0.66)); }
}
@keyframes hvFlareD {
  30% { filter: drop-shadow(0 0 26px rgba(255, 46, 136, 0.75)); }
}

.hollow__sigrow {
  margin-top: 36px;
  display: flex;
  align-items: center;
  gap: 16px;
}
.hollow__mark {
  width: 40px;
  height: 40px;
  overflow: visible;
  flex: none;
  filter: drop-shadow(0 0 7px rgba(255, 46, 136, 0.4)); /* static; the room owns its light */
}
.hollow__mark-orbit {
  fill: none;
  stroke: url(#hv-candy);
  stroke-width: 1.25;
}
/* the instrument runs while the room is open — a light circling the orbit (pathLength=100;
   a 48px repaint, nothing more) */
.hollow--in .hollow__mark-orbit {
  stroke-dasharray: 72 28;
  animation: hvOrbit 9s linear infinite;
}
@keyframes hvOrbit {
  to { stroke-dashoffset: -100; }
}
.hollow__mark-tick {
  stroke: rgba(244, 241, 236, 0.3);
  stroke-width: 1;
}
.hollow__mark-punctum {
  fill: #ff2e88;
}
.hollow--in .hollow__mark-punctum {
  animation: hvPunctum 4.5s ease-in-out infinite;
}
@keyframes hvPunctum {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.68; }
}
.hollow__sig {
  margin: 0;
  text-align: left;
  font-family: var(--font-label);
  font-size: 0.62rem;
  letter-spacing: 0.19em;
  text-transform: uppercase;
  color: var(--ink-faint);
  line-height: 2;
}
/* — the way out: seal the seam (or esc). The ring cycles Clara's stations on a pseudo
     (its own layer, so the hover transition on the button's real border never fights the
     ambient animation) — */
.hollow__seal {
  position: relative;
  margin-top: 42px;
  appearance: none;
  background: transparent;
  border: 1px solid rgba(244, 241, 236, 0.18);
  color: var(--ink-dim);
  font-family: var(--font-label);
  font-size: 0.68rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  padding: 13px 24px;
  cursor: pointer;
  transition: border-color 0.4s var(--ease), color 0.4s var(--ease), box-shadow 0.4s var(--ease);
}
.hollow__seal::before {
  content: '';
  position: absolute;
  inset: -1px;
  border: 1px solid rgba(255, 46, 136, 0);
  pointer-events: none;
  transition: opacity 0.4s var(--ease);
}
.hollow--in .hollow__seal::before {
  animation: hvSealHue 14s ease-in-out 2s infinite;
}
.hollow__seal:hover::before,
.hollow__seal:focus-visible::before {
  opacity: 0; /* the hand's own answer takes over */
}
@keyframes hvSealHue {
  0%, 100% { border-color: rgba(255, 46, 136, 0.34); }
  33% { border-color: rgba(255, 158, 46, 0.3); }
  66% { border-color: rgba(95, 123, 255, 0.34); }
}
.hollow__seal:hover {
  border-color: rgba(255, 46, 136, 0.55);
  color: var(--ink-on-dark);
  box-shadow: 0 0 22px rgba(255, 46, 136, 0.14), inset 0 0 14px rgba(255, 46, 136, 0.06);
}
.hollow__seal:focus-visible {
  outline: 1px solid rgba(255, 46, 136, 0.8);
  outline-offset: 3px;
}
.hollow__esc {
  margin-top: 14px;
  font-family: var(--font-label);
  font-size: 0.56rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: rgba(244, 241, 236, 0.42); /* a whisper, but a READABLE whisper (~4:1 on #020202) */
}

/* — arrival: the words settle on the estate cadence, after the shards assemble — */
.hollow [data-hv] {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 0.85s var(--ease), transform 0.85s var(--ease), filter 1.15s var(--ease);
}
.hollow--in [data-hv="1"] { transition-delay: 0.62s; }
.hollow--in [data-hv="2"] { transition-delay: 0.7s; }
.hollow--in [data-hv="3"] { transition-delay: 0.82s; }
.hollow--in [data-hv="4"] { transition-delay: 0.98s; }
.hollow--in [data-hv="5"] { transition-delay: 1.12s; }
.hollow--in [data-hv="6"] { transition-delay: 1.26s; }
.hollow--in [data-hv="7"] { transition-delay: 1.42s; }
.hollow--in [data-hv] {
  opacity: 1;
  transform: none;
}

/* — leaving: the room lets go quickly (exit faster than enter), the iris carries the rest — */
.hollow--out .hollow__room,
.hollow--out [data-hv] {
  opacity: 0;
  transition: opacity 0.3s var(--ease);
}

/* — the iris: the loose facet's own triangle, flown. Geometry (inset + clip-path +
   transform-origin) is written inline by body.js from the live facet position; only the
   transform animates (composited). Candy face fades to the room's black mid-flight. — */
.hollow__iris {
  position: absolute;
  will-change: transform;
  transform-origin: 50% 50%;
  pointer-events: none;
}
.hollow__iris-face {
  position: absolute;
  inset: 0;
  background: linear-gradient(135deg, #ff2e88, #ff9e2e 52%, #5f7bff);
}
.hollow__iris-dark {
  position: absolute;
  inset: 0;
  background: #020202;
  opacity: 0;
}
/* flight curves: the leap out is eager, the return settles */
.hollow--flight .hollow__iris {
  transition: transform 0.78s cubic-bezier(0.32, 0, 0.16, 1);
}
.hollow--flight .hollow__iris-dark {
  transition: opacity 0.55s ease-in 0.2s;
}
.hollow--return .hollow__iris {
  transition: transform 0.72s cubic-bezier(0.4, 0, 0.3, 1);
}
.hollow--return .hollow__iris-dark {
  /* stay black while the triangle is still large (the room collapsing), then cool to candy
     only in the last beat — a small bright shard landing on the facet, never a muddy wash */
  transition: opacity 0.28s ease-out 0.4s;
}

/* — short sheets (laptops): the column tightens so the whole inscription sits inside the
     fold — it may still scroll on truly small heights — */
@media (max-height: 980px) and (min-width: 601px) {
  .hollow__kicker { margin-bottom: 18px; }
  .hollow__display { font-size: clamp(2.1rem, 4.6vw, 3.3rem); }
  .hollow__ins { margin-top: 30px; gap: 15px; }
  .hollow__line { font-size: clamp(1.05rem, 1.5vw, 1.2rem); }
  .hollow__sigrow { margin-top: 26px; }
  .hollow__seal { margin-top: 30px; }
}

/* — small sheets: the words fill the sheet, so the nave widens with them (the column is
     ~88vw here — the desktop ellipse would leave the serif on ignited seams) — */
@media (max-width: 600px) {
  .hollow__nave {
    background: radial-gradient(
      ellipse 94% 42% at 50% 72%,
      rgba(2, 2, 2, 0.8),
      rgba(2, 2, 2, 0.5) 56%,
      rgba(2, 2, 2, 0) 82%
    );
  }
  .hollow__kicker { margin-bottom: 16px; font-size: 0.58rem; letter-spacing: 0.16em; }
  .hollow__display { font-size: clamp(1.95rem, 8.6vw, 2.35rem); }
  .hollow__ins { margin-top: 26px; gap: 13px; max-width: 88vw; }
  .hollow__line { font-size: 1.02rem; line-height: 1.5; }
  .hollow__sigrow { margin-top: 22px; gap: 13px; }
  .hollow__mark { width: 34px; height: 34px; }
  .hollow__sig { font-size: 0.53rem; letter-spacing: 0.13em; }
  .hollow__seal { margin-top: 24px; padding: 12px 20px; font-size: 0.62rem; }
  .hollow__esc { margin-top: 11px; }
}

/* — reduced motion: the room presents COMPOSED AND STILL — no flight, no drift, no
   travelling light, no stagger; entry/exit are quiet fades (body.js skips the iris entirely
   and paints the field ONCE, composed: presences at rest, motes settled). Fully
   experiencable: every word, the whole field, the seam-light at its resting stations — and
   the press still answers, as a state (single repaints), never a motion. — */
@media (prefers-reduced-motion: reduce) {
  .hollow__room,
  .hollow__field,
  .hollow__nave,
  .hollow [data-hv] {
    transition: opacity 0.25s linear;
    transform: none;
  }
  .hollow--in .hollow__display em,
  .hollow--in .hollow__line,
  .hollow--chime .hollow__line em,
  .hollow--chime .hollow__display em,
  .hollow--in .hollow__mark-orbit,
  .hollow--in .hollow__mark-punctum,
  .hollow--in .hollow__seal::before {
    animation: none;
  }
  .hollow--in [data-hv] {
    transition-delay: 0s;
  }
  .hollow__display span[data-hv],
  .hollow__line[data-hv] {
    filter: none;
  }
  .hollow__iris {
    display: none;
  }
}
