/* ============ DESIGN TOKENS (from preview-v13 tokens.css) ============ */
:root {
  --bg: #12090A; --bg2: #1A0F10; --bg3: #2D1A1C;
  --surface: rgba(255,255,255,0.03); --surface2: rgba(255,255,255,0.06);
  /* PART-D96 (2026-09-15) SYE-3/SYE-4: solid text tones (Q8), no alpha on text. --fg4 is for borders and rules only;
     --gold-text colours gold text and links. */
  --fg: #F0EBE3; --fg2: #CCC2B7; --fg3: #ADA296; --fg4: rgba(240,235,227,0.18);
  --gold: #F0C86E; --gold2: #D4A853; --gold3: #B8893A; --gold-text: #D4A853;
  --gold-dim: rgba(240,200,110,0.10); --gold-glow: rgba(240,200,110,0.20);
  --wine: #722F37; --wine2: #9A3D48; --wine3: #5A2029;
  --cream: #FAF6F1; --blush: #1A0F10; --maroon-deep: #5C1A1B;
  --maroon-light: rgba(114,47,55,0.12); --dark-border: #3D2426;
  --border: rgba(240,200,110,0.08); --border2: rgba(240,200,110,0.15); --border3: rgba(240,200,110,0.25);
  --card: #1A0F12; --shadow: rgba(0,0,0,0.5);
  --ease-luxury: cubic-bezier(0.25,0.46,0.45,0.94);
  --ease-overshoot: cubic-bezier(0.34,1.56,0.64,1);
  --ease-refined: cubic-bezier(0.4,0,0.2,1);
  --ease-cinematic: cubic-bezier(0.76,0,0.24,1);
  --ease-heavy: cubic-bezier(0.16,1,0.3,1);
  /* Generated-face frame (the two composite portraits on the results screen, TODO 0.3 / 7.1):
     a light pink-cream 5px mat, and the plate the portraits sit on. The mat is the same in both
     themes - it is the object's own colour - and the plate is what keeps the page in sync with it. */
  --face-frame: #F3DED7;
  /* PART-D72 (2026-09-09) W1 DEVIATION: this stays dark in the dark theme. Pointing it at
     var(--cream) here would put the failed/withheld veil copy (var(--fg3), a near-white at
     0.35 alpha in this theme) on a near-white field, which is illegible; inventing a new
     token is barred. So the plain field is cream in light theme and dark here, and the veil
     copy is legible in both. Stated as a deviation in the build report. */
  --face-frame-bg: #1C1114;
  --face-frame-shadow: rgba(0,0,0,0.45);
  /* PART-D96 (2026-09-15) SITE-1/SITE-2/SYE-2: the one C3 type scale (Q1); nothing below --fs-label outside .site-footer. */
  --fs-label: 1rem; --fs-body: 1.1875rem; --fs-lead: 1.5rem; --fs-title: 2rem;
  --fs-h2: clamp(2.25rem, 4vw, 3rem); --ls-label: 0.12em;
  --label: var(--fs-label);
  /* PART-D96 (2026-09-15) SYE-16/SITE-3: spacing steps and tiers (Q9); --gap-card and --gap-section step up at 900px below. */
  --space-1: 0.5rem; --space-2: 1rem; --space-3: 1.5rem; --space-4: 2rem; --space-5: 3rem; --space-6: 4.5rem;
  --gap-block: 1.5rem; --gap-card: 2rem; --gap-section: 3rem;
  /* PART-D96 (2026-09-15) readout box and between-cards gap (Q9/Q19). */
  --ro-measure: 100%; /* PART-D96 (2026-09-15) SYE-18: the widest block sets the box (main inner column), not a narrower 60rem measure */ --ro-gap: var(--gap-card);
  --nav-h: var(--site-nav-h, 73px);   /* the shared header's height, defined per breakpoint in assets/css/site-header.css */
}
[data-theme="light"] {
  --bg: #FAF6F1; --bg2: #F0EBE3; --bg3: #E8E0D4;
  --surface: rgba(0,0,0,0.03); --surface2: rgba(0,0,0,0.055);
  /* PART-D96 (2026-09-15) SYE-3/SYE-4: solid text tones (Q8); only colours are overridden here. */
  --fg: #120810; --fg2: #4D3F44; --fg3: #5E4F54; --fg4: rgba(18,8,16,0.18);
  --gold: #D4A853; --gold2: #B8893A; --gold3: #9A6A28; --gold-text: #7A5418;
  --gold-dim: rgba(212,168,83,0.10); --gold-glow: rgba(212,168,83,0.20);
  --border: rgba(114,47,55,0.09); --border2: rgba(114,47,55,0.16); --border3: rgba(114,47,55,0.28);
  --blush: #F5EDE6; --card: #FFFFFF; --shadow: rgba(92,26,27,0.10);
  /* PART-D72 (2026-09-09) W1: the plain field behind a not-yet-made face is cream in light theme */
  --face-frame-bg: var(--cream); --face-frame-shadow: rgba(92,26,27,0.12);
}
/* PART-D96 (2026-09-15) SYE-16/SITE-3: the card and section tiers step up from 900px (Q9). */
@media (min-width: 900px) {
  :root { --gap-card: 3rem; --gap-section: 4.5rem; }
}

/* ============ BASE ============ */
* { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
/* PART-D87 (2026-09-11) item 1a: the root's own background, made explicit.
   Until now only body carried one (:49 below) and the root carried none, so the canvas
   was painted by CSS's body-to-canvas propagation (CSS Backgrounds 3, 2.11.2) rather
   than by any rule a reader of this file could see. Measured at this pin BEFORE the
   change: getComputedStyle(document.documentElement).backgroundColor was rgba(0, 0, 0, 0)
   in BOTH themes while body resolved to var(--bg), which is item 1a's acceptance step 2
   failing. This is the ledger's own first candidate and the ONLY one the three-step
   diagnostic implicated.
   THE SECOND CANDIDATE IS DELIBERATELY NOT APPLIED, and the reason below is CORRECTED
   from this round's first statement of it. The first pass measured step 1 at one desktop
   viewport, 1280x800, read 0px of strip on every stage, and concluded that body's
   min-height never binds. That conclusion was wrong, and the error was the generalisation
   rather than the reading: the strip is a function of whether the window is TALLER than
   the stage's content, so one viewport height can only ever sample one side of it.
   Re-measured across heights and across all FIVE stages: at 1280x1200 the hero stage
   shows 346px of body background below the footer, the readout 27px and the loading
   stage 797px; at 375x730 the loading stage shows 195px. Identical at the base, so this
   round neither caused nor cured it. The svh/dvh mirror STILL cures nothing, because it
   only moves min-height between two values that both exceed the content: the 375x730
   loading strip measures 195px with the shipped 100vh and 195px with the dvh stack.
   WHAT THIS RULE DOES NOT CURE, AND IT IS THE DEFECT ITSELF, stated plainly rather than
   left to be found. The strip above is BODY'S OWN BOX painted with var(--bg), not the
   canvas, so an explicit root background cannot reach it; and the overscroll bounce
   region is the canvas, whose colour is var(--bg) before this rule (by propagation from
   body) and after it (from this rule), so that is unchanged too. In the LIGHT theme both
   read as cream beneath the footer's maroon plate, which is what the ruling of record
   describes as "a pale strip renders below the dark footer".
   THE CURE WAS FOUND AND TESTED AND IS DELIBERATELY NOT SHIPPED HERE: a sticky-footer
   layout, body as a flex column with main taking flex: 1 0 auto, measures 0px on hero,
   readout, loading and entry alike, with the entry cover's sticky behaviour and every
   document height unchanged. It needs properties this round's ledger does not name, and
   the round's own bar forbids going beyond them, so item 1a is filed as an HONEST STOP
   on substance with that recommendation attached, for a fresh ruling. */
html { background: var(--bg); }
html, body { overflow-x: clip; }
body {
  background: var(--bg); color: var(--fg);
  font-family: 'DM Sans', system-ui, sans-serif;
  font-size: var(--fs-body); line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  padding-top: var(--nav-h);
  min-height: 100vh;
  transition: background 0.35s var(--ease-refined), color 0.35s var(--ease-refined);
}
h1, h2, h3 { font-family: 'Cormorant Garamond', Georgia, serif; font-weight: 600; line-height: 1.15; }
button { font: inherit; cursor: pointer; }
input, select { font: inherit; }
.mono { font-family: 'DM Sans', system-ui, sans-serif; font-variant-numeric: tabular-nums; }   /* PART-D96 SITE-9: mono stays only on #recoveryCode and #recoverInput (Q2) */
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
::selection { background: var(--gold-glow); }

/* ============ NAV ============ */
/* The header is the shared site component: assets/js/site-header.js renders it and
   assets/css/site-header.css is its only stylesheet. This page used to carry a local
   copy (smaller bar, no dark-mode logo, a "Harmonia" wordmark, a LOCAL DEMO badge, a
   round icon theme button) and drifted from every other page - so nothing here may
   style nav, .nav-links, .hamburger or .theme-toggle. --nav-h tracks the shared bar. */

/* ============ LAYOUT ============ */
main { max-width: 1100px; margin: 0 auto; padding: 0 clamp(1rem, 4vw, 2.5rem); }
section.stage { display: none; }
section.stage.active { display: block; animation: stageIn 0.55s var(--ease-luxury); }
/* FREEZE-SAFE: transform-only, no opacity. An animation that never starts or is
   stalled by an extension (Dark Reader / uBlock / a userscript all do heavy work at
   document start) renders at its from-frame, and an opacity:0 from-frame hides the
   whole stage permanently — measured as "op=0 vis=visible disp=block" on a real
   profile. Content must never depend on an animation running to be visible. */
@keyframes stageIn { from { transform: translateY(14px); } to { transform: none; } }
.kicker {
  display: flex; align-items: center; gap: 0.8rem; justify-content: center;
}
/* PART-D96 (2026-09-15) SITE-2/SYE-15: the one short-label style (Q2). The Folio I archetype name is also a
   .kicker; it is excluded so it is never shrunk to 1rem (folio.css styles it). */
.kicker:not(.hero-arch .kicker) {
  font-family: 'DM Sans', system-ui, sans-serif; font-weight: 600; font-size: var(--fs-label);
  letter-spacing: var(--ls-label); text-transform: uppercase; color: var(--gold-text);
}
.kicker::before, .kicker::after { content: ""; height: 1px; width: 34px; background: var(--border3); }

/* ============ HERO ============ */
#stage-hero { text-align: center; padding: clamp(3rem, 9vh, 6rem) 0 4rem; position: relative; }
.hero-halo {
  position: absolute; inset: -20% -30% auto; height: 130%; pointer-events: none; z-index: -1;
  background: radial-gradient(ellipse 55% 45% at 50% 32%, var(--gold-dim), transparent 70%);
}
#stage-hero h1 { font-size: clamp(2.6rem, 7vw, 4.6rem); margin: 1.2rem 0 0.4rem; }
#stage-hero h1 em { font-style: italic; color: var(--gold-text); }
.hero-sub { max-width: 560px; margin: 1.1rem auto 0; color: var(--fg2); font-size: var(--fs-body); }
.hero-eye { margin: 2.4rem auto 0.5rem; width: 150px; height: 82px; display: block; overflow: visible;
  transform-origin: center; animation: heroBlink 7s ease-in-out 2.6s infinite; }
.hero-eye .he-lid, .hero-eye .he-iris { stroke-dasharray: 420; stroke-dashoffset: 420;
  animation: heroDraw 1.5s var(--ease-cinematic) 0.35s forwards; }
.hero-eye .he-iris { animation-delay: 0.6s; }
@keyframes heroDraw { to { stroke-dashoffset: 0; } }
.hero-eye .he-pupil { transform-box: fill-box; transform-origin: center;
  animation: heroPupilIn 0.6s ease 1.1s both, heroPupil 4.6s ease-in-out 2.2s infinite alternate; }
@keyframes heroPupilIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes heroPupil { from { transform: scale(1); } to { transform: scale(1.18); } }
.hero-eye .he-glint { transform-box: fill-box; transform-origin: center;
  animation: heroGlintIn 0.6s ease 1.3s both, heroGlint 4.6s ease-in-out 2.2s infinite alternate; }
@keyframes heroGlintIn { from { opacity: 0; } to { opacity: 0.8; } }
@keyframes heroGlint { from { transform: translateX(0); } to { transform: translateX(2.5px); } }
@keyframes heroBlink { 0%, 90%, 100% { transform: scaleY(1); } 94% { transform: scaleY(0.08); } 97% { transform: scaleY(1); } }
.hero-flow { font-family: 'DM Sans', system-ui, sans-serif; font-weight: 600; font-size: var(--fs-label); letter-spacing: var(--ls-label);
  text-transform: uppercase; color: var(--fg3); margin: 2rem auto 1.6rem; }
.hero-steps {
  display: flex; gap: clamp(0.8rem, 3vw, 2rem); justify-content: center; flex-wrap: wrap;
  margin: 2.4rem 0 2.6rem;
}
.hero-step {
  flex: 1 1 150px; max-width: 210px; padding: 1.1rem 1rem; text-align: center;
  background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
}
.hero-step .n {
  font-family: 'DM Sans', system-ui, sans-serif; font-size: var(--fs-lead); color: var(--gold-text); font-style: italic;   /* PART-D96 (2026-09-15) SYE-15 (Q2) */
}
.hero-step p { font-size: var(--fs-body); color: var(--fg2); margin-top: 0.15rem; }
.btn {
  position: relative; overflow: hidden;
  display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem;
  min-height: 46px; padding: 0.8rem 1.9rem; border-radius: 3px;
  border: none; background: var(--gold);
  color: #0A0406; font-weight: 500; font-size: var(--fs-label); letter-spacing: var(--ls-label); text-transform: uppercase;
  box-shadow: 0 4px 18px rgba(240,200,110,0.18);
  transition: transform 0.3s var(--ease-refined), box-shadow 0.3s, background 0.3s;
}
.btn::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(105deg, transparent 40%, rgba(255,255,255,0.14) 50%, transparent 60%);
  transform: translateX(-100%) skewX(-12deg);
  transition: transform 0.6s var(--ease-luxury);
}
.btn:hover::before { transform: translateX(200%) skewX(-12deg); }
.btn:hover { transform: translateY(-1px); background: var(--gold2); box-shadow: 0 8px 28px rgba(240,200,110,0.25); }
.btn:active { transform: translateY(0) scale(0.98); }
.btn-ghost {
  background: transparent; color: var(--fg); border: 1px solid var(--border2); box-shadow: none;
}
.btn-ghost::before { display: none; }
.btn-ghost:hover { background: var(--surface); border-color: var(--border3); color: var(--gold-text); transform: translateY(-1px); box-shadow: none; }
/* PART-D96 (2026-09-15) SYE-3 (Q8/Q9): disabled controls show their state in solid colour, never opacity on text */
.btn:disabled { opacity: 1; background: var(--surface2); color: var(--fg3); border-color: var(--fg4); cursor: not-allowed; transform: none; box-shadow: none; }
.btn:disabled::before { display: none; }
.resume-note {
  margin: 1.6rem auto 0; max-width: 480px; padding: 0.9rem 1.2rem; font-size: var(--fs-body);
  border: 1px solid var(--border2); border-radius: 12px; background: var(--surface); color: var(--fg2);
}
.resume-note .row { display: flex; gap: 0.7rem; justify-content: center; margin-top: 0.7rem; flex-wrap: wrap; }
.resume-note .btn { min-height: 40px; padding: 0.4rem 1.1rem; font-size: var(--fs-label); }

/* ============ ENTRY ============ */
#stage-entry { max-width: 620px; margin: 0 auto; padding: clamp(2rem, 6vh, 4rem) 0 4rem; position: relative; }
/* P3 · entry warp + progressive reveal + 18+ affirmation */
#entryCanvas { left: 50%; right: auto; width: 100vw; transform: translateX(-50%); }
/* FIX 2026-08-24: this was hardcoded to a dark rgba regardless of theme, so in light
   mode the entry card stayed a muddy dark brown against the cream page instead of
   picking up --card (#FFFFFF in light mode). color-mix keeps the same "70% card,
   blurred" glass effect in both themes instead of baking in one theme's color. */
#stage-entry .panel { background: color-mix(in srgb, var(--card) 70%, transparent); -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px); }
#stage-entry.active .panel > h2 { animation: rise 0.5s var(--ease-cinematic) 0.04s both; }
#stage-entry.active .panel > .entry-sub { animation: rise 0.5s var(--ease-cinematic) 0.09s both; }
#stage-entry.active #entryForm > * { animation: rise 0.5s var(--ease-cinematic) both; }
#stage-entry.active #entryForm > *:nth-child(1) { animation-delay: 0.14s; }
#stage-entry.active #entryForm > *:nth-child(2) { animation-delay: 0.21s; }
#stage-entry.active #entryForm > *:nth-child(3) { animation-delay: 0.28s; }
#stage-entry.active #entryForm > *:nth-child(4) { animation-delay: 0.35s; }
#stage-entry.active #entryForm > *:nth-child(5) { animation-delay: 0.42s; }
#stage-entry.active #entryForm > *:nth-child(6) { animation-delay: 0.49s; }
#stage-entry.active #entryForm > *:nth-child(7) { animation-delay: 0.56s; }
.age-ok { display: none; margin-top: 0.5rem; font-family: 'DM Sans', system-ui, sans-serif; font-weight: 600; font-size: var(--fs-label);
  letter-spacing: var(--ls-label); text-transform: uppercase; color: var(--gold-text); }
.age-ok.show { display: block; animation: rise 0.4s var(--ease-cinematic); }
/* PART-D78 (2026-09-10) CURES A DEFECT THAT NEVER ONCE RENDERED. This rule read
   `#ageInput.ok` from the day it was written, and `ageInput` is an id that has never
   existed anywhere in this repository: the class was always toggled on #dobInput. So the
   gold border-and-glow half of the "you're old enough" feedback has never appeared, and
   only the text affirmation above ever showed. Both declarations below are the original's,
   unchanged; only the selector is corrected, and it is corrected to the fieldset rather
   than to #dobInput because #dobInput is now hidden and can never draw a border. */
.dob-fieldset.ok { border-color: var(--gold2); box-shadow: 0 0 0 3px var(--gold-glow); }
.panel {
  background: var(--card); border: 1px solid var(--border); border-radius: 18px;
  padding: clamp(1.4rem, 4vw, 2.4rem); box-shadow: 0 10px 40px var(--shadow);
}
#stage-entry h2, #stage-rate h2, #stage-readout h2 { font-size: var(--fs-h2); text-align: center; }
.entry-sub { text-align: center; color: var(--fg2); font-size: var(--fs-body); margin: 0.4rem 0 1.6rem; }
.field { margin-bottom: 1.35rem; }
/* PART-D96 (2026-09-15) SITE-2/SYE-15: field labels and legends are sentence-case DM Sans 600 at --fs-body (Q2). */
.field > label {
  display: block; font-size: var(--fs-body); font-weight: 600; letter-spacing: normal;
  text-transform: none; color: var(--fg2); margin-bottom: 0.5rem;
}
.field .hint { font-size: var(--fs-label); color: var(--fg3); margin-top: 0.35rem; }
.choices { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.choice {
  min-height: 44px; padding: 0.5rem 1.05rem; border-radius: 999px;
  border: 1px solid var(--border2); background: var(--surface); color: var(--fg2);
  font-size: var(--fs-label); font-weight: 500;
  transition: all 0.22s var(--ease-refined);
}
.choice:hover { border-color: var(--border3); color: var(--fg); }
.choice.selected {
  background: var(--gold-dim); border-color: var(--gold2); color: var(--gold-text); font-weight: 600;
  box-shadow: inset 0 0 0 1px var(--gold2);
}
/* FIX 2026-08-24: input[type="date"] was never in this selector, so #dobInput rendered
   as a completely unstyled native control (no border-radius, no theme border/bg/color)
   -- the one visibly "wrong" element on an otherwise custom-styled form. color-scheme
   also tells the browser to draw the native calendar popup and picker icon in the
   matching theme, instead of defaulting to a light (often illegible-on-dark) picker. */
input[type="text"], input[type="number"], input[type="date"] {
  width: 100%; min-height: 48px; padding: 0.6rem 0.9rem; border-radius: 10px;
  border: 1px solid var(--border2); background: var(--surface); color: var(--fg);
  outline: none; transition: border-color 0.2s;
  color-scheme: light dark;
}
[data-theme="dark"] input[type="date"] { color-scheme: dark; }
[data-theme="light"] input[type="date"] { color-scheme: light; }
input:focus { border-color: var(--gold2); }

/* PART-D78 (2026-09-10) · THE DATE OF BIRTH, AS THREE ORDINARY FIELDS.
   Day and Year are input[type="text"], so their height, padding, radius, border and
   background all come from the combined rule directly above, which stays load-bearing
   for exactly that reason; only its type="date" branch has gone inert, and the two
   [data-theme] date rules with it. They are left in place as a separate, deferred tidy.
   The month <select> had NO rule anywhere in this file, which is the same fault the
   2026-08-24 note above describes for the old date input, so it is given the same
   treatment here, color-scheme included, and the native dropdown opens in the page's
   own theme instead of a light list on a dark page.
   The fieldset carries a TRANSPARENT border by default: it draws no box around the row
   in the ordinary case, but it gives the gold .ok ring above an edge to colour when
   science.js's own listener says the date reads as an adult. The equal negative margin
   cancels the padding, so the three parts line up with #locInput and the other fields
   rather than sitting a few pixels inside them. */
.dob-fieldset {
  border: 1px solid transparent; border-radius: 12px;
  padding: 0.4rem; margin: 0 calc(-0.4rem - 1px); min-width: 0;
  transition: border-color 0.22s var(--ease-refined), box-shadow 0.22s var(--ease-refined);
}
/* float rather than display:block: a legend is laid out by the fieldset, and floating it
   at full width is the portable way to give it a margin and let the row sit beneath. */
.dob-fieldset > legend {
  float: left; width: 100%; padding: 0;
  font-size: var(--fs-body); font-weight: 600; letter-spacing: normal;
  text-transform: none; color: var(--fg2); margin-bottom: 0.5rem;
}
.dob-row { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: flex-end; clear: both; }
.dob-part { display: flex; flex-direction: column; min-width: 0; }
/* --fg2, not the lighter --fg3 the hints use. These three words ARE the accessible names
   of three required fields, not de-emphasised commentary beside them, and at 0.7rem --fg3
   computes to about 2.7:1 against the card in both themes, well under the 4.5:1 floor.
   --fg2 is what .field > label above already uses, and it measures 6.29:1 in light and
   6.19:1 in dark. No new token either way. */
.dob-part > label {
  font-size: var(--fs-body); font-weight: 600; letter-spacing: normal;
  text-transform: none; color: var(--fg2); margin-bottom: 0.3rem;
}
/* Day and Year are fixed at roughly their own content plus the shared padding; the month
   is sized by its widest name rather than stretched across whatever is left, which is
   what keeps the three reading as one date and not as one field with two stubs beside
   it. It may still shrink on a narrow screen, and the row wraps before anything clips. */
.dob-part-day { flex: 0 0 4.6rem; }
.dob-part-year { flex: 0 0 6.2rem; }
.dob-part-month { flex: 0 1 auto; }
#dobMonth {
  max-width: 100%; min-height: 48px; padding: 0.6rem 0.9rem; border-radius: 10px;
  border: 1px solid var(--border2); background: var(--surface); color: var(--fg);
  outline: none; transition: border-color 0.2s;
  color-scheme: light dark;
}
/* Below the file's own narrow breakpoint the three parts stop fitting on one line, and
   A20 is a phone complaint, so the wrap is made deliberate rather than left to chance:
   the month fills the space beside the day instead of leaving a hole at the end of the
   first line, and the year takes the second on its own. Nothing is truncated and every
   part keeps its full 48px target. */
@media (max-width: 480px) {
  .dob-part-month { flex: 1 1 auto; }
}
#dobMonth:focus { border-color: var(--gold2); }
/* PART-D87 (2026-09-11) item 1b: the month popup, made legible in both themes.
   D78 shipped color-scheme on the control itself (the #dobMonth rule above and the two
   theme rules restated below), and on a Chromium engine that alone is what repaints the
   native popup. It is not a guarantee everywhere: color-scheme's reach into the popup's
   OWN surface is uneven across engines and historically weakest in WebKit, where this
   theme's pale --fg can land on a popup surface that never followed, which is the defect
   the critique reports.
   TWO LAYERS, NEITHER OF THEM A CUSTOM LISTBOX. That reversal would undo D78's own
   native-control, autofill-preserving rationale (local-network.html:225-234) and needs a
   fresh ruling, so the native control, its id and its autocomplete all survive untouched.
   (1) The scheme is RESTATED on the options. color-scheme is an inherited property and
   so reaches them in principle, but engines have long treated the popup as a separate
   widget, and one declaration removes the ambiguity at that boundary rather than
   trusting it.
   (2) The options carry an OPAQUE theme-following surface of their own, which is the
   layer never attempted before. var(--card), NOT the control's own var(--surface): that
   token is a 3 per cent white in this theme and reads as the field only because the card
   behind it is opaque, so handing the same value to a popup would leave the popup
   transparent over whatever the engine happened to draw, which is the very failure this
   exists to close. var(--card) is opaque in both themes and needs no new token, and
   against var(--fg) it measures about 15:1 in dark and about 19:1 in light. */
[data-theme="dark"] #dobMonth, [data-theme="dark"] #dobMonth option { color-scheme: dark; }
[data-theme="light"] #dobMonth, [data-theme="light"] #dobMonth option { color-scheme: light; }
#dobMonth option { background-color: var(--card); color: var(--fg); }
/* No display declaration: the element carries the `hidden` attribute when there is
   nothing to say, and the browser's own [hidden] rule is what should govern it. */
.dob-error { margin-top: 0.55rem; font-size: var(--fs-body); color: var(--wine2); font-weight: 600; }

.age-error {
  display: none; margin-top: 0.5rem; font-size: var(--fs-body); color: var(--wine2); font-weight: 600;
}
.entry-actions { text-align: center; margin-top: 1.8rem; }
.form-error { color: var(--wine2); font-size: var(--fs-body); text-align: center; margin-top: 0.8rem; min-height: 1.2em; }
.underage-block {
  text-align: center; padding: 2.2rem 1rem;
}
.underage-block h3 { font-size: var(--fs-title); color: var(--wine2); margin-bottom: 0.6rem; }
.underage-block p { color: var(--fg2); max-width: 420px; margin: 0 auto; font-size: var(--fs-body); }

/* ============ RATE ============ */
/* PART-D72 (2026-09-09) W4: the bottom padding is reclaimed (4rem was mostly dead space
   below the controls) so the 1-to-7 scale sits within reach without a scroll. The face is
   NOT shrunk: the lens keeps its min(420px, 84vw) cap. Paired with the tighter .rate-tally
   and .scale-wrap top margins below. */
#stage-rate { padding: 1.2rem 0 2.5rem; }
.rate-top {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  margin-bottom: 1rem; flex-wrap: wrap;
}
.rate-progress { flex: 1; min-width: 180px; }
.rate-progress .label {
  font-family: 'DM Sans', system-ui, sans-serif; font-weight: 600; font-size: var(--fs-label); letter-spacing: var(--ls-label);
  text-transform: uppercase; color: var(--fg3); margin-bottom: 0.35rem;
}
.bar-track { height: 3px; background: var(--surface2); border-radius: 2px; overflow: hidden; }
.bar-fill { height: 100%; width: 0%; background: linear-gradient(90deg, var(--gold3), var(--gold)); border-radius: 2px; transition: width 0.4s var(--ease-luxury); }
#endBtn { min-height: 44px; padding: 0.5rem 1.2rem; font-size: var(--fs-label); }
.face-frame {
  position: relative; max-width: 460px; margin: 0 auto;
  background: var(--card); border: 1px solid var(--border2); border-radius: 20px;
  padding: 14px; box-shadow: 0 14px 50px var(--shadow);
}
.face-frame::before, .face-frame::after,
.face-frame .corner-b::before, .face-frame .corner-b::after {
  content: ""; position: absolute; width: 22px; height: 22px; pointer-events: none;
  border-color: var(--border3); border-style: solid;
}
.face-frame::before { top: 6px; left: 6px; border-width: 1px 0 0 1px; border-top-left-radius: 12px; }
.face-frame::after { top: 6px; right: 6px; border-width: 1px 1px 0 0; border-top-right-radius: 12px; }
.face-frame .corner-b::before { bottom: 6px; left: 6px; border-width: 0 0 1px 1px; border-bottom-left-radius: 12px; }
.face-frame .corner-b::after { bottom: 6px; right: 6px; border-width: 0 1px 1px 0; border-bottom-right-radius: 12px; }
.face-img-wrap { border-radius: 12px; overflow: hidden; background: var(--bg3); aspect-ratio: 4/4.6; }
.face-img-wrap img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  animation: faceIn 0.4s var(--ease-luxury);
}
@keyframes faceIn { from { transform: scale(1.025); } to { transform: none; } }  /* transform-only: a frozen frame shows the face, never a blank */
.scale-wrap { max-width: 560px; margin: 0.9rem auto 0; text-align: center; }   /* PART-D72 W4: 1.6rem -> 0.9rem, reclaiming space above the controls without shrinking the face */
.scale-q { font-family: 'DM Sans', system-ui, sans-serif; font-size: var(--fs-lead); font-style: italic; color: var(--fg2); }   /* PART-D96 (2026-09-15) SYE-15 (Q2): DM Sans, not Cormorant */
.scale {
  display: flex; gap: clamp(4px, 1.4vw, 10px); justify-content: center; margin-top: 1rem;
}
.scale button {
  width: clamp(44px, 11vw, 56px); height: clamp(48px, 12vw, 60px);
  border-radius: 12px; border: 1px solid var(--border2);
  background: var(--surface); color: var(--fg2);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px;
  transition: all 0.18s var(--ease-refined);
  position: relative;
}
.scale button .num { font-family: 'DM Sans', system-ui, sans-serif; font-size: var(--fs-lead); line-height: 1; }   /* PART-D96 (2026-09-15) SYE-15 (Q2) */
.scale button .pip { width: 5px; height: 5px; transform: rotate(45deg); background: var(--fg4); transition: background 0.18s; }
.scale button:hover { border-color: var(--gold2); color: var(--gold-text); transform: translateY(-3px); }
.scale button:hover .pip { background: var(--gold2); }
.scale button.picked {
  background: linear-gradient(150deg, var(--gold), var(--gold2)); color: #2B1608;
  border-color: var(--gold2); transform: translateY(-3px) scale(1.06);
  box-shadow: 0 6px 20px var(--gold-glow);
}
.scale button.picked .pip { background: #2B1608; }
.scale-ends {
  display: flex; justify-content: space-between; max-width: 560px; margin: 0.55rem auto 0;
  font-family: 'DM Sans', system-ui, sans-serif; font-weight: 600; font-size: var(--fs-label); letter-spacing: var(--ls-label);
  text-transform: uppercase; color: var(--fg3);
}
.scale-hintkeys { margin-top: 0.9rem; font-size: var(--fs-label); color: var(--fg3); }
.batch-panel { max-width: 520px; margin: 2.5rem auto; text-align: center; }
.batch-panel h3 { font-size: var(--fs-title); margin-bottom: 0.5rem; }
.batch-panel p { color: var(--fg2); margin-bottom: 1.5rem; font-size: var(--fs-body); }
/* The provisional one-line observation shown at each batch boundary. Ported from the pre-merge
   build, where the id was dropped from the markup but the JS kept writing to it. Sits tighter to
   #batchText than a normal paragraph and reads quieter than it, because it is a read-only aside
   that must not compete with the batch headline. */
.batch-panel .batch-insight { max-width: 460px; margin: -0.8rem auto 1.5rem; color: var(--fg3); font-size: var(--fs-body); line-height: 1.6; }
.batch-panel .row { display: flex; gap: 0.8rem; justify-content: center; flex-wrap: wrap; }

/* ============ READOUT ============ */
#stage-readout { padding: 2.2rem 0 3rem; max-width: 780px; margin: 0 auto; }
/* text-wrap:balance so a centred two-line sentence splits evenly instead of leaving a
   short orphan line under a full one -- which is what made this read as oddly centred.
   Purely typographic: unsupported engines just fall back to normal wrapping. */
/* PART-D75 (2026-09-10) W1 / A11, the title lift: margin-bottom was 2.2rem. This margin and
   .eye-print-faces' margin-top are ONE COLLAPSED SIBLING PAIR at runtime (this element and
   the face block are adjacent siblings inside #stage-readout once injectEyePrintFaces has
   run), so the gap is the LARGER of the two, not their sum. Both were cut to the same 0.9rem
   together, and BOTH cuts were necessary: this one alone would have moved nothing at all
   (max(0.9, 2.8) is still 2.8rem), and the .eye-print-faces cut alone would have got only as
   far as 2.2rem (max(2.2, 0.9)), a quarter of the intended lift. Never tune one without the
   other, and never below 0.45rem each. */
/* PART-D96 (2026-09-15) SYE-18 (Q9): the readout's top blocks (label, heading, sub) sit on the one box, var(--ro-measure);
   the sub's own copy keeps a 66ch measure inside it, still centred (was 560px). */
.readout-sub { text-align: center; color: var(--fg2); font-size: var(--fs-body); margin: 0.5rem auto 0.9rem; max-width: var(--ro-measure); padding-inline: max(0px, calc((var(--ro-measure) - 66ch) / 2)); text-wrap: balance; }
#stage-readout > .kicker, #stage-readout > h2 { max-width: var(--ro-measure); margin-left: auto; margin-right: auto; }
.ro-card { margin-bottom: 1.6rem; position: relative; overflow: hidden; }
.ro-card .ro-head { display: flex; align-items: baseline; gap: 0.9rem; margin-bottom: 1rem; }
.ro-card .ro-num {
  font-family: 'DM Sans', system-ui, sans-serif; font-style: italic; font-size: var(--fs-title); color: var(--gold-text);   /* PART-D96 (2026-09-15) SYE-15 (Q2) */
}
.ro-card h3 { font-size: var(--fs-title); }
.ro-card .ro-tag {
  margin-left: auto; font-family: 'DM Sans', system-ui, sans-serif; font-weight: 600; font-size: var(--fs-label);
  letter-spacing: var(--ls-label); text-transform: uppercase; color: var(--fg3);
}
.ro-card p.lede { color: var(--fg); font-size: var(--fs-body); }
.ro-card p.soft { color: var(--fg2); font-size: var(--fs-body); margin-top: 0.6rem; }
.ro-hero { display: flex; gap: 1.6rem; align-items: stretch; flex-wrap: wrap; }
.ro-hero .hero-arch { flex: 1 1 300px; min-width: 240px; }
/* 2026-09-05: the .hero-fig / .fig / .figcap rules that styled the hero's gauge card are
   gone with it (js/overhaul.js enhance() lifts .hero-fig out of the cover post-render). */
.ro-locked { border-style: dashed; }   /* PART-D96 SYE-3: opacity 0.75 removed, no opacity on text (Q8) */
.lock-note { color: var(--fg2); font-size: var(--fs-body); }
/* PART-D96 (2026-09-15) SITE-1: the pip is a real text node (science.js lockedCard), so it computes to the floor and is drawn at 60% */
.lock-note .lock-pip { display: inline-block; color: var(--gold-text); font-size: var(--fs-label); margin-right: 0.4rem; transform: scale(0.6); }
.ro-svg { width: 100%; height: auto; margin-top: 1rem; display: block; }
.ro-radar-wrap { display: flex; justify-content: center; }
.ro-radar { max-width: 320px; margin: 0.4rem auto 0; }
.lean-list { list-style: none; display: grid; gap: 0.55rem; margin-top: 0.3rem; }
.lean-line { color: var(--fg); font-size: var(--fs-body); padding-left: 1.1rem; position: relative; line-height: 1.6; }
.lean-line::before { content: "◆"; position: absolute; left: 0; top: 0.35em; font-size: 0.5rem; color: var(--gold-text); }
.lean-neutral::before { color: var(--fg3); }
.lean-firmmark {
  display: inline-block; margin-left: 0.4rem; padding: 1px 7px; border-radius: 999px;
  font-size: var(--fs-label); letter-spacing: 0.04em; color: var(--gold-text);
  border: 1px solid var(--border2); background: var(--gold-dim); vertical-align: 1px;
}
.ro-meaning {
  margin-top: 0.9rem; font-size: var(--fs-body); color: var(--fg2); font-style: italic;
  border-left: 2px solid var(--border2); padding-left: 0.7rem;
}
.ro-dd { margin-top: var(--gap-block); }   /* PART-D72 W7: more separation from the copy above, now that the disclosure opens by default */
.ro-dd summary {
  cursor: pointer; list-style: none; display: inline-block;
  font-family: 'DM Sans', system-ui, sans-serif; font-weight: 600; font-size: var(--fs-label);
  letter-spacing: var(--ls-label); text-transform: uppercase; color: var(--fg);
  border: 1px solid var(--border2); border-radius: 999px; padding: 4px 12px;
}
.ro-dd summary::-webkit-details-marker { display: none; }
.ro-dd[open] summary { background: var(--gold-dim); margin-bottom: var(--space-1); }   /* PART-D72 W7: space the toggle from the detail it now reveals by default */
/* PART-D96 (2026-09-15) SYE-22 (C9): 'Show the detail' drawer text at the readout body size at every width. */
.ro-dd .dd-h {
  font-family: 'DM Sans', system-ui, sans-serif; font-weight: 600; font-size: var(--fs-body);
  letter-spacing: normal; text-transform: none; color: var(--fg);
  margin: var(--space-2) 0 var(--space-1);
}
.ro-dd .dd-p { font-size: var(--fs-body); color: var(--fg); line-height: 1.6; }
.dd-nums { list-style: none; display: grid; gap: 0.3rem; }
.dd-nums li {
  display: flex; justify-content: space-between; gap: 1rem; font-size: var(--fs-body); color: var(--fg);
  border-bottom: 1px dashed var(--border); padding-bottom: 0.25rem;
}
.dd-nums .dd-k { color: var(--fg); }   /* PART-D96 (2026-09-15) SYE-22 (C9): drawer number rows are var(--fg) (was --fg2) */
.dd-nums .dd-v { font-family: 'DM Sans', system-ui, sans-serif; font-variant-numeric: tabular-nums; font-size: var(--fs-body); color: var(--fg); text-align: right; }
.ro-compare { font-size: var(--fs-body); color: var(--fg); margin-top: 0.3rem; }
.ro-portrait { background: var(--gold-dim); }
.portrait-text {
  font-family: 'Cormorant Garamond', Georgia, serif; font-size: var(--fs-lead);
  line-height: 1.55; font-style: italic; color: var(--fg);
}
.finer-details summary {
  cursor: pointer; display: flex; align-items: baseline; gap: 0.9rem; list-style: none;
}
.finer-details summary::-webkit-details-marker { display: none; }
.finer-details summary::after {
  content: "expand"; margin-left: auto; font-family: 'DM Sans', system-ui, sans-serif; font-weight: 600;
  font-size: var(--fs-label); letter-spacing: var(--ls-label); text-transform: uppercase; color: var(--fg);
}
.finer-details[open] summary::after { content: "collapse"; }
.finer-list { margin-top: 1rem; }
@keyframes roCardIn { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: none; } }
.ro-reveal { animation: roCardIn 0.55s var(--ease-luxury) both; }
.guard-note {
  border: 1px dashed var(--border3); border-radius: 12px; background: var(--surface);
  padding: 0.9rem 1.1rem; color: var(--fg2); font-size: var(--fs-body); margin-top: 0.6rem;
}
/* PART-D96 (2026-09-15) SYE-18 (Q9): the actions row and the disclaimer sit on the readout box; the disclaimer's copy keeps 66ch (was 520px). */
.readout-actions { display: flex; gap: 0.8rem; justify-content: center; flex-wrap: wrap; margin: 2rem auto 0.6rem; max-width: var(--ro-measure); }
.readout-disclaimer { text-align: center; font-size: var(--fs-label); color: var(--fg2); margin-top: 1.6rem; max-width: var(--ro-measure); padding-inline: max(0px, calc((var(--ro-measure) - 66ch) / 2)); margin-left: auto; margin-right: auto; }
/* PART-D72 (2026-09-09) W5: at desktop widths the readout's smallest text is raised to a
   0.8rem floor for body and detail copy, and the two low-contrast offenders are lifted off
   --fg3 onto --fg2 (nothing grey on black). Base sizes were: detail heads 0.58rem/fg3,
   detail values 0.78rem, disclaimer 0.72rem/fg3. The folio eyebrow and figure caption are
   raised in styles/folio.css, where the folio owns them. Both themes get the same floor
   since the sizes are theme-independent and only the two colours differed. */
/* PART-D96 (2026-09-15) SYE-22: the 900px+ block this comment described is folded into the base rules above,
   which sit on the C3 tokens at every width (the disclaimer keeps the --fg2 lift). */

/* ============ SHARE ============ */
#share-section { text-align: center; padding: 1.5rem 0 2rem; max-width: var(--ro-measure); margin-left: auto; margin-right: auto; }   /* PART-D96 (2026-09-15) SYE-18: on the readout box */
#share-holder { display: flex; justify-content: center; margin: 1.4rem 0; }
#share-holder svg { width: min(320px, 78vw); height: auto; border-radius: 14px; box-shadow: 0 18px 60px var(--shadow); }

/* ============ SIGN-IN ============ */
#signin-section { max-width: var(--ro-measure); margin: 0 auto 4rem; }   /* PART-D96 (2026-09-15) SYE-18: on the readout box (was 620px) */
#signin-section .panel > p, #signin-section .promise-list { max-width: 66ch; margin-left: auto; margin-right: auto; }   /* its running text keeps 66ch */
.promise-list { list-style: none; margin-top: 1rem; display: grid; gap: 0.55rem; text-align: left; }
.promise-list li { display: flex; gap: 0.65rem; align-items: baseline; color: var(--fg2); font-size: var(--fs-body); }
.promise-list li::before { content: "◆"; color: var(--gold-text); font-size: 0.55rem; flex: none; }
.stub-label {
  display: inline-block; margin-top: 1rem; font-family: 'DM Sans', system-ui, sans-serif; font-weight: 600;
  font-size: var(--fs-label); letter-spacing: var(--ls-label); text-transform: uppercase;
  color: var(--fg3); border: 1px dashed var(--border3); border-radius: 6px; padding: 4px 9px;
}
.signedin-state {
  display: none; margin-top: 1.4rem; padding: 1rem 1.2rem; border-radius: 12px;
  border: 1px solid var(--border2); background: var(--gold-dim); font-size: var(--fs-body);
}
.signedin-state.show { display: block; animation: stageIn 0.4s var(--ease-luxury); }
.signedin-state .mono { font-size: var(--fs-label); color: var(--fg2); word-break: break-all; }

/* ============ LOADING / ERROR ============ */
.loading-veil { text-align: center; padding: 4rem 0; color: var(--fg2); }
.loading-veil .orn { font-size: var(--fs-title); color: var(--gold-text); animation: pulse 1.6s infinite; }
@keyframes pulse { 0%,100% { transform: scale(0.9); } 50% { transform: scale(1); } }   /* PART-D96 (2026-09-15) SYE-3: the ornament pulses by scale, not opacity */
.load-error {
  max-width: 560px; margin: 3rem auto; padding: 1.2rem 1.4rem; border-radius: 12px;
  border: 1px solid var(--wine2); background: var(--maroon-light); font-size: var(--fs-body); color: var(--fg);
}
.load-error code { font-family: 'DM Sans', system-ui, sans-serif; font-size: var(--fs-label); }

/* ============ FOOTER ============ */
footer { border-top: 1px solid var(--border); margin-top: 3rem; background: var(--bg2); }
.footer-inner {
  max-width: 1100px; margin: 0 auto; padding: 2.2rem clamp(1rem,4vw,2.5rem) calc(2.2rem + env(safe-area-inset-bottom));
  display: flex; flex-direction: column; gap: 1rem; align-items: center; text-align: center;
}
.logo-text { font-family: 'Cormorant Garamond', serif; font-size: var(--fs-lead); color: var(--gold-text); }
.footer-links { display: flex; gap: 1.4rem; flex-wrap: wrap; justify-content: center; }
.footer-links a { color: var(--fg2); text-decoration: none; font-size: var(--fs-label); }
.footer-links a:hover { color: var(--gold-text); }
.copyright { color: var(--fg3); font-size: var(--fs-label); }

/* ===================================================================
   HARMONIA V13 OVERHAUL — chrome, ambient life, the Lens, the reveal.
   Cascade-later overrides; the tested JS/markup is untouched.
   =================================================================== */
/* — Cinematic stage entrance + per-child stagger — */
@keyframes stageIn { from { transform: translateY(24px) scale(0.985); } to { transform: none; } }
section.stage.active { animation: stageIn 0.6s var(--ease-cinematic); }
/* readout hosts a position:fixed rail — a lingering transform on the stage would trap it,
   so this stage fades (opacity only, no transform) instead of the scale entrance */
#stage-readout.active { animation-name: fadeInStage; }
@keyframes fadeInStage { from { transform: none; } to { transform: none; } }   /* opacity fade removed: same stalled-animation trap as stageIn */
#stage-hero.active > .kicker { animation: rise .6s var(--ease-cinematic) .05s both; }
#stage-hero.active > h1 { animation: rise .7s var(--ease-cinematic) .14s both; }
#stage-hero.active > .hero-sub { animation: rise .7s var(--ease-cinematic) .24s both; }
#stage-hero.active > .hero-flow { animation: rise .8s var(--ease-cinematic) .44s both; }
#stage-hero.active > #beginBtn { animation: rise .8s var(--ease-cinematic) .54s both; }
/* BLANK-CONTENT SAFETY: `rise` is used with animation-fill-mode:both on the hero and entry
   content. When it animated opacity 0→1, a stalled/never-started animation (e.g. a browser with
   GPU compositing disabled, or a throttled first paint) left `both` holding the from-frame at
   opacity 0 — the whole stage rendered blank. It is now TRANSFORM-ONLY: the content is ALWAYS
   opaque and legible, and merely slides up as an enhancement when the animation runs. This is the
   same freeze-safe rule the readout/folio entrances already use. */
@keyframes rise { from { transform: translateY(18px); } to { transform: none; } }
:focus-visible { outline: 2px solid rgba(240,200,110,0.65); outline-offset: 3px; border-radius: 3px; }

/* — Ambient canvas layer behind the three key stages — */
#stage-hero, #stage-rate, #stage-readout { position: relative; }
.ambient-canvas { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 0;
  pointer-events: none; display: block; }
/* #stage-readout is very tall (~7500px); an absolute ambient canvas would size its
   backing store to the whole section (~8M px) though only a viewport is ever seen.
   Pin it to the viewport so the texture stays viewport-sized — the star field then
   holds steady while the readout scrolls over it. (Guarded in JS via getClientRects
   since fixed elements report offsetParent === null.) */
#readoutCanvas { position: fixed; }
#stage-hero > *:not(.ambient-canvas):not(.hero-halo),
#stage-entry > *:not(.ambient-canvas),
#stage-rate > *:not(.ambient-canvas),
#stage-readout > *:not(.ambient-canvas):not(.ro-rail) { position: relative; z-index: 1; }

/* — Hero polish — */
.hero-step { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; }
.kicker { color: var(--gold-text); }
/* 2026-09-05: light is now this page's no-preference default (local-network.html boot
   script), and --gold on the cream ground measures ~2:1 at this 10px size. The light
   theme takes the same wine the header's active link uses (~9:1). */
:root[data-theme="light"] .kicker { color: var(--wine); }

/* — THE LENS (rating) — an aperture, not a card. Distinct from the monorepo deck. — */
.lens { position: relative; width: min(420px, 84vw); aspect-ratio: 1 / 1; margin: 0.4rem auto 0;
  border-radius: 50%; padding: 15px;
  background:
    radial-gradient(circle at 50% 28%, rgba(240,200,110,0.12), transparent 62%),
    conic-gradient(from 212deg, var(--gold3), var(--gold), #f7dca0, var(--gold3), var(--wine2), var(--gold3));
  box-shadow: 0 26px 80px rgba(0,0,0,0.55), inset 0 0 0 1px rgba(240,200,110,0.18);
  transition: transform 0.5s var(--ease-heavy); }
.lens-iris { position: relative; width: 100%; height: 100%; border-radius: 50%; overflow: hidden;
  background: var(--bg3); box-shadow: inset 0 0 44px rgba(0,0,0,0.65), inset 0 0 0 7px rgba(8,4,6,0.55); }
.lens .face-img-wrap { position: absolute; inset: 0; border-radius: 50%; overflow: hidden;
  aspect-ratio: auto; background: var(--bg3); }
.lens .face-img-wrap img { width: 100%; height: 100%; object-fit: cover; display: block;
  animation: lensFocus 0.64s var(--ease-heavy); }
@keyframes lensFocus {
  0%   { transform: scale(1.16); filter: blur(16px); clip-path: circle(34% at 50% 50%); }
  55%  { filter: blur(2px); }
  100% { transform: none; filter: blur(0); clip-path: circle(78% at 50% 50%); } }
.lens-ring { position: absolute; inset: 7px; border-radius: 50%; border: 1px solid var(--border2); pointer-events: none; }
.lens-glint { position: absolute; inset: -2px; border-radius: 50%; pointer-events: none; mix-blend-mode: screen;
  background: conic-gradient(from 0deg, transparent 0 74%, rgba(240,200,110,0.55) 85%, transparent 91%);
  animation: lensGlint 9s linear infinite; opacity: 0.5; }
@keyframes lensGlint { to { transform: rotate(360deg); } }
.lens.focus-lock { animation: lensLock 0.5s var(--ease-overshoot); }
@keyframes lensLock { 0% { transform: scale(1); } 32% { transform: scale(0.96); } 100% { transform: scale(1); } }
.lens.focus-lock .lens-ring { animation: lensRingBurst 0.55s var(--ease-luxury); }
@keyframes lensRingBurst { 0% { box-shadow: 0 0 0 0 rgba(240,200,110,0.5); border-color: var(--gold); }
  100% { box-shadow: 0 0 0 24px transparent; border-color: var(--border2); } }
/* aperture stops (the 1–7 scale, restyled circular) */
.scale { gap: clamp(6px, 1.7vw, 13px); margin-top: 1.4rem; }
.scale button { width: clamp(42px, 10vw, 52px); height: clamp(42px, 10vw, 52px); border-radius: 50%; }
.scale button .num { font-size: var(--fs-body); }
.scale button:hover { transform: translateY(-3px) scale(1.05); }
.scale button.picked { background: var(--gold); color: #0A0406; border-color: var(--gold);
  transform: translateY(-3px) scale(1.14); box-shadow: 0 0 24px var(--gold-glow); }
.scale button.picked .pip { background: #0A0406; }
.scale-q { color: var(--fg); }
/* P4 · forming-ring, tally odometer, rating spark */
.lens-form { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; z-index: 6; overflow: visible; }
.lf-arc { transition: stroke-dashoffset 0.6s var(--ease-heavy); filter: drop-shadow(0 0 3px rgba(240,200,110,0.55)); }
.rate-tally { display: flex; align-items: baseline; justify-content: center; gap: 0.5rem; margin: 0.7rem auto 0; }   /* PART-D72 W4: 1.2rem -> 0.7rem, tightening the gap between the face and the tally */
.rt-odo { font-family: 'DM Sans', system-ui, sans-serif; font-weight: 300; font-size: var(--fs-title); color: var(--gold-text); line-height: 1; }
.rt-label { font-family: 'DM Sans', system-ui, sans-serif; font-weight: 600; font-size: var(--fs-label); letter-spacing: var(--ls-label); text-transform: uppercase; color: var(--fg3); }
.lens.spark::before { content: ""; position: absolute; inset: -3px; border-radius: 50%; pointer-events: none; z-index: 7;
  animation: lensSpark 0.55s var(--ease-luxury); }
@keyframes lensSpark { from { box-shadow: 0 0 0 0 rgba(240,200,110, calc(0.6 * var(--spark, 0.5))); }
  to { box-shadow: 0 0 0 calc(28px * var(--spark, 0.5)) rgba(240,200,110, 0); } }
/* P5 · batch chapter card */
.batch-eyebrow { font-family: 'DM Sans', system-ui, sans-serif; font-weight: 600; font-size: var(--fs-label); letter-spacing: var(--ls-label);
  text-transform: uppercase; color: var(--gold-text); margin-bottom: 0.9rem; }
.batch-accum { display: flex; gap: 0.5rem; justify-content: center; margin-bottom: 1.1rem; }
.batch-accum .ch { font-family: 'DM Sans', system-ui, sans-serif; font-style: italic; font-size: var(--fs-label); color: var(--fg3);
  width: 26px; height: 26px; display: grid; place-items: center; border: 1px solid var(--border); border-radius: 50%;
  transition: all 0.3s var(--ease-refined); }
.batch-accum .ch.done { color: #0A0406; background: var(--gold); border-color: var(--gold); }
.batch-tally { display: flex; align-items: baseline; justify-content: center; gap: 0.5rem; margin: 0.2rem auto 1.5rem; }
.batch-tally .rt-odo { font-size: var(--fs-title); }
/* P6 · Eye Crest: removed 2026-09-05 with the gauge card it decorated. */

/* P8 · share card assemble entrance */
#share-holder svg { animation: cardAssemble 0.9s var(--ease-cinematic) both; }
@keyframes cardAssemble { from { opacity: 0; transform: translateY(20px) scale(0.94); } to { opacity: 1; transform: none; } }

/* P9 · Funnel as the terminal card of the story — reveal, personalized bridge,
   shimmering terminal CTA, and a post-signin 3-step preview of what joining does.
   The funnel is ALWAYS visible; entering the viewport only adds a one-time rise —
   never a persistent hidden state, so the terminal CTA can't get stuck invisible if
   the IntersectionObserver is slow or unavailable. */
/* transform-only entrance (like dossierRise) — a frozen/throttled animation leaves the element
   fully opaque and merely offset, never invisible. Opacity is deliberately NOT animated here. */
#signin-section.funnel-entered { animation: funnelRise 0.75s var(--ease-cinematic); }
@keyframes funnelRise { from { transform: translateY(24px); } to { transform: none; } }
.funnel-eyebrow { font-family: 'DM Sans', system-ui, sans-serif; font-weight: 600; font-size: var(--fs-label); letter-spacing: var(--ls-label);
  text-transform: uppercase; color: var(--gold-text); margin-bottom: 0.75rem; }
.funnel-bridge { color: var(--fg2); font-size: var(--fs-body); line-height: 1.6; margin: 0.5rem auto 0.2rem; max-width: 44ch; }
.funnel-bridge em { font-style: italic; color: var(--gold-text); }
/* terminal CTA — a slow gold sheen crosses the button to close the story (paused for reduced-motion) */
#signInBtn.cta-glow { position: relative; overflow: hidden; }
#signInBtn.cta-glow::after { content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(115deg, transparent 34%, rgba(255,255,255,0.4) 50%, transparent 66%);
  transform: translateX(-130%); animation: ctaSheen 4.2s var(--ease-cinematic) 1.4s infinite; }
@keyframes ctaSheen { 0% { transform: translateX(-130%); } 26%, 100% { transform: translateX(130%); } }
/* 3-step "what happens next" preview, revealed after the simulated sign-in */
.next-steps { display: none; margin-top: 1.4rem; }
.next-steps.show { display: block; }
.next-steps-track { display: flex; align-items: flex-start; justify-content: center; margin-top: 0.5rem; }
.ns-step { flex: 1 1 0; max-width: 150px; text-align: center; position: relative;
  transform: translateY(8px); transition: transform 0.5s var(--ease-cinematic); }
.ns-step.lit { transform: none; } /* visible by default; only the offset settles, so a frozen transition can't hide it */
.ns-line { position: absolute; top: 6px; left: 50%; width: 100%; height: 1px; z-index: 0;
  background: linear-gradient(90deg, var(--gold), rgba(240,200,110,0.12)); }
.ns-step:last-child .ns-line { display: none; }
.ns-dot { display: block; position: relative; z-index: 1; width: 13px; height: 13px; border-radius: 50%; background: var(--gold);
  margin: 0 auto 0.55rem; box-shadow: 0 0 12px rgba(240,200,110,0.6); }
.ns-step p { font-size: var(--fs-label); color: var(--fg2); line-height: 1.4; margin: 0; padding: 0 0.3rem; }
/* reading-strength completion settle: a one-time, restrained landing the first time the meter
   reaches its complete state. Armed by .rs-settle, which overhaul.js adds only on the render that
   newly reaches completion, so it never replays on a re-render. Transform and opacity only, no
   layout property, so it cannot reflow the card; the resting state (folio.css .rs-done) is fully
   opaque, so with the class absent, or under reduced motion below, the line simply shows. Modelled
   on funnelRise above, with a gentle opacity fade in the manner of cardAssemble. */
.rs-done.rs-settle { animation: rsSettle 0.48s var(--ease-cinematic); }
@keyframes rsSettle { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  #signin-section.funnel-entered { animation: none !important; }
  #signInBtn.cta-glow::after { display: none; }
  .ns-step { transform: none !important; transition: none !important; }
  .rs-done.rs-settle { animation: none !important; }
}

/* — Readout: hero card gets the top-match-hero treatment; count-up; card reveal — */
.ro-hero { background:
    radial-gradient(ellipse 60% 50% at 22% 26%, rgba(240,200,110,0.10), transparent 62%), var(--card);
  border: 1px solid var(--border2); border-radius: 18px;
  box-shadow: 0 30px 80px rgba(0,0,0,0.45), 0 0 0 1px rgba(154,61,72,0.14); overflow: hidden; }
.ro-portrait { background: var(--gold-dim); border: 1px solid var(--border); border-radius: 12px; }
.ro-reveal { animation: roCardIn 0.6s var(--ease-cinematic) both; }
.ro-reveal[data-i] { animation-delay: calc(var(--i, 0) * 90ms); }
@keyframes roCardIn { from { opacity: 0; transform: translateY(22px) scale(0.99); } to { opacity: 1; transform: none; } }
.readout-actions .btn, #share-section .btn { min-height: 46px; }

/* — LENS materiality: vignette, camera shutter, glass reflection, f-stop rim — */
.lens-vignette { position: absolute; inset: 0; border-radius: 50%; pointer-events: none; z-index: 2;
  background: radial-gradient(circle at 50% 44%, transparent 50%, rgba(8,4,6,0.55) 80%, rgba(8,4,6,0.95) 100%);
  box-shadow: inset 0 0 55px 10px rgba(8,4,6,0.7); }
.lens-shutter { position: absolute; inset: 0; border-radius: 50%; pointer-events: none; z-index: 3;
  background: repeating-conic-gradient(from 0deg, #160d10 0deg 41deg, #0a0507 41deg 45deg);
  clip-path: circle(0% at 50% 50%); }
.lens.shutter-blink .lens-shutter { animation: shutterBlink 360ms var(--ease-heavy); }
@keyframes shutterBlink { 0% { clip-path: circle(0% at 50% 50%); } 46% { clip-path: circle(85% at 50% 50%); }
  100% { clip-path: circle(0% at 50% 50%); } }
.lens-reflection { position: absolute; inset: 5px; border-radius: 50%; pointer-events: none; z-index: 4;
  background: linear-gradient(135deg, rgba(255,255,255,0.16), transparent 32%, transparent 74%, rgba(255,255,255,0.05));
  mix-blend-mode: screen; }
.lens::after { content: ""; position: absolute; inset: 3px; border-radius: 50%; pointer-events: none; z-index: 1;
  background: repeating-conic-gradient(from 0deg, transparent 0deg 8deg, rgba(240,200,110,0.28) 8deg 8.5deg);
  -webkit-mask: radial-gradient(circle, transparent 45.5%, #000 46.5%, #000 48.5%, transparent 49.5%);
  mask: radial-gradient(circle, transparent 45.5%, #000 46.5%, #000 48.5%, transparent 49.5%);
  animation: lensGlint 44s linear infinite; opacity: 0.55; }
/* --- SCALE ALIGNMENT ------------------------------------------------------
   The end labels are laid out across .scale-wrap's 560px, but the seven buttons
   stop growing at their 52px cap -- so on desktop the buttons span ~435px inside
   a 552px box and the labels splay past the scale they describe: "Leaves you cold"
   sat 32px left of button 1 and "Stops you" 53px right of button 7. On mobile the
   buttons already fill the wrapper, which is why it only ever looked wrong on a
   wide screen.

   Both the row and the labels now derive their width from the SAME expression, so
   they cannot drift apart again when the button size or gap is retuned. min(100%)
   keeps the narrow case exactly as it is today.

   space-between is also wrong for three labels of different widths -- it centres
   the middle one in the LEFTOVER space, not on the middle button, which is where
   the 20px error on "Somewhere between" came from. Three equal grid columns put it
   on the true centre. */
.scale-wrap {
  --scale-btn: clamp(42px, 10vw, 52px);
  --scale-gap: clamp(6px, 1.7vw, 13px);
  --scale-w: min(100%, calc(7 * var(--scale-btn) + 6 * var(--scale-gap)));
}
/* The ROW is deliberately left alone. Constraining its width at all makes the
   buttons shrink -- they carry flex-shrink:1, so an explicit width that lands even
   a sub-pixel under their natural total is absorbed by the buttons rather than the
   container (measured: width resolved to 332.25px, used width 327.27px, buttons
   41.37px instead of 42px). Only the LABELS are constrained, below. */
/* DESKTOP ONLY, and that is the whole diagnosis. On a phone the buttons SHRINK to
   fit (computed 43.55px, actual 42.9px), so any width derived from the clamp
   over-predicts the row and the labels end up narrower than the scale -- the exact
   mirror of the desktop bug. They also already line up there, because the buttons
   fill the wrapper. Above 700px the buttons have stopped growing and no longer
   shrink, so the expression is exact and the mismatch is real: 435px of buttons
   inside a 552px label box. Fix it only where it is wrong. */
@media (min-width: 700px) {
  .scale-ends {
    display: grid; grid-template-columns: repeat(3, 1fr);
    width: var(--scale-w); max-width: none; margin-left: auto; margin-right: auto;
  }
  .scale-ends > :nth-child(1) { justify-self: start;  text-align: left; }
  .scale-ends > :nth-child(2) { justify-self: center; text-align: center; }
  .scale-ends > :nth-child(3) { justify-self: end;    text-align: right; }
}

.scale { position: relative; }
.scale::before { content: ""; position: absolute; left: 6%; right: 6%; top: 50%; height: 1px;
  background: var(--border2); z-index: 0; transform: translateY(-0.5px); }
.scale button { position: relative; z-index: 1; background: var(--bg2); }

/* — READOUT as a scroll-snap DOSSIER: acts, eyebrows, rail, radial score gauge — */
#stage-readout { max-width: 940px; }
/* readout cards must never be hidden by a stray reveal animation */
#readoutBody .ro-card { opacity: 1 !important; animation: none !important; }
.ro-act { padding: clamp(1.4rem, 5vh, 3rem) 0; scroll-margin-top: 80px; }
.ro-act-eyebrow { font-family: 'DM Sans', system-ui, sans-serif; font-weight: 600; font-size: var(--fs-label); letter-spacing: var(--ls-label);
  text-transform: uppercase; color: var(--gold-text); margin-bottom: 16px; display: flex; align-items: center; gap: 0.8rem; }
.ro-act-eyebrow::after { content: ""; flex: 1; height: 1px; background: var(--border); }
.ro-act.reveal-act.in-view { animation: dossierRise 0.6s var(--ease-cinematic); }
@keyframes dossierRise { from { transform: translateY(24px); } to { transform: none; } }

.ro-rail { position: fixed; right: clamp(0.75rem, 2.5vw, 2rem); top: 50%; transform: translateY(-50%);
  display: flex; flex-direction: column; align-items: flex-end; gap: 16px; z-index: 60; width: max-content; left: auto; }
.ro-rail-tick { position: relative; background: none; border: none; display: flex; align-items: center; justify-content: flex-end;
  cursor: pointer; padding: 4px 0; color: var(--fg3); font-family: 'DM Sans', sans-serif; font-size: var(--fs-label); letter-spacing: 0.06em; }
.ro-rail-dot { width: 8px; height: 8px; border-radius: 50%; background: rgba(240,200,110,0.18);
  border: 1px solid rgba(240,200,110,0.3); transition: all 0.3s var(--ease-refined); flex: none; }
.ro-rail-label { position: absolute; right: calc(100% + 12px); top: 50%; opacity: 0;
  transform: translateY(-50%) translateX(-6px); white-space: nowrap; transition: all 0.3s var(--ease-refined); }
.ro-rail-tick:hover .ro-rail-label { opacity: 1; transform: translateY(-50%); color: var(--fg); }
.ro-rail-tick.is-active { color: var(--gold-text); }
.ro-rail-tick.is-active .ro-rail-dot { background: var(--gold); transform: scale(1.4); box-shadow: 0 0 12px rgba(240,200,110,0.55); }
.ro-rail-tick.is-active .ro-rail-label { opacity: 1; transform: translateY(-50%); }
@media (max-width: 860px) { .ro-rail-label { display: none; } .ro-rail { right: 0.5rem; gap: 12px; } }

/* Act I hero: scroll hint (the radial gauge and its quality label went 2026-09-05) */
.ro-hero { align-items: center; }
.ro-scroll-hint { display: flex; flex-direction: column; align-items: center; gap: 3px; margin: 1.4rem auto 0;
  color: var(--fg3); font-family: 'DM Sans', sans-serif; font-size: var(--fs-label); letter-spacing: 0.1em;
  animation: hintBob 2.2s ease-in-out infinite; }
.ro-scroll-hint .arr { font-size: var(--fs-label); color: var(--gold-text); }
@keyframes hintBob { 0%,100% { transform: translateY(0); } 50% { transform: translateY(4px); } }   /* PART-D96 (2026-09-15) SYE-3: motion only, no opacity on the hint text */

/* ===== P1 · MOTION-FOUNDATION PRIMITIVES ===== */
.odo { display: inline-flex; align-items: baseline; font-variant-numeric: tabular-nums; line-height: 1; }
.odo-col { display: inline-block; height: 1em; overflow: hidden; line-height: 1; }
.odo-strip { display: flex; flex-direction: column; transition: transform 0.7s cubic-bezier(0.16,1,0.3,1); }
.odo-d { height: 1em; line-height: 1; display: block; }
.odo-sfx { margin-left: 0.04em; }
.segbar { display: flex; gap: 6px; width: 100%; }
.segbar .seg { flex: 1; height: 3px; background: var(--surface2); border-radius: 2px; overflow: hidden; }
.segbar .seg > i { display: block; height: 100%; width: 100%; background: linear-gradient(90deg, var(--gold3), var(--gold));
  border-radius: 2px; transform: scaleX(0); transform-origin: left; transition: transform 0.35s linear; }
.txt-shimmer { background: linear-gradient(100deg, var(--gold2) 0%, var(--gold) 28%, #FFF6DF 50%, var(--gold) 72%, var(--gold2) 100%);
  background-size: 220% 100%; -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; color: transparent;
  animation: txtShimmer 2.6s var(--ease-luxury) infinite; }
.txt-shimmer.once { animation-iteration-count: 1; }
@keyframes txtShimmer { from { background-position: 210% 0; } to { background-position: -70% 0; } }
/* PART-D96 (2026-09-15) SYE-3 (Q8/Q21): gradient text is transparent-filled and its light stops read about 1-2:1 on cream,
   so in the light theme the shimmer and the knot flare are solid --gold-text (the shimmer is dark-theme only). */
[data-theme="light"] .txt-shimmer, [data-theme="light"] .knot-flare { background: none; -webkit-text-fill-color: currentColor; color: var(--gold-text); }

/* — Reduced-motion parity for the new pieces — */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; }
  .lens-glint, .lens::after, .ro-scroll-hint { display: none; }
  .lens .face-img-wrap img { clip-path: circle(78% at 50% 50%) !important; filter: none !important; }
  #stage-hero.active > * { animation: none !important; opacity: 1 !important; transform: none !important; }
  .ro-act.reveal-act { opacity: 1 !important; transform: none !important; }
}

/* =====================================================================
   TESTING POOL SIGN-UP FORM
   Static markup in #signin-section (never runtime-injected: realSignIn reads
   #poolEmail first, so putting it behind a presentation script would let the
   whole join path die silently while rating still looked fine).
   ===================================================================== */
.pool-label {
  display: block; font-family: 'DM Sans', system-ui, sans-serif; font-weight: 600; font-size: var(--fs-body);
  letter-spacing: normal; text-transform: none; color: var(--fg2);
  margin-bottom: 0.4rem;
}
#poolEmail {
  width: 100%; padding: 0.75rem 0.9rem; background: var(--bg2);
  border: 1px solid var(--border2); border-radius: 3px;
  color: var(--fg); font-family: inherit; font-size: var(--fs-body);
}
#poolEmail:focus {
  outline: none; border-color: var(--gold2);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--gold2) 18%, transparent);
}
.pool-check {
  display: flex; gap: 0.6rem; align-items: flex-start;
  margin-top: 0.9rem; font-size: var(--fs-label); line-height: 1.5; color: var(--fg2);
  cursor: pointer;
}
.pool-check input {
  flex: 0 0 auto; margin-top: 0.2rem; width: 1rem; height: 1rem;
  accent-color: var(--gold2); cursor: pointer;
}
/* The consent tick must be reachable and obvious by keyboard - it gates a
   legal act, so it never relies on the surrounding label alone. */
.pool-check input:focus-visible {
  outline: 2px solid var(--gold2); outline-offset: 2px;
}
.pool-check a { color: var(--gold-text); }

/* The compact heads-up that used to sit directly under the readout sub-line.
   PART-D76 (2026-09-10) W7: its MARKUP is gone from local-network.html, so every rule
   in this block is currently unused. It is KEPT, not deleted, deliberately and for a
   stated term: A18's primary reading (remove this block, rehome the full panel) is
   built on Avery's own words but has not yet been checked against his screenshot, and
   the mirrored contingency would rebuild exactly this markup. DELETE THIS BLOCK once he
   confirms the primary reading; if he confirms the other, the markup comes back. */
.recovery-inline {
  display: none; align-items: center; justify-content: center; flex-wrap: wrap;
  /* margin-top was -0.9rem, tuned for its old spot tucked under the readout sub-line. It now
     sits below the eye-print signature, where a negative pull-up reads as a collision -- so it
     gets normal spacing. max-width + auto margins keep it centred in the (unconstrained,
     see #stage-readout.folio) stage. */
  gap: 0.5rem 0.7rem; max-width: 40rem; margin: 0.5rem auto 2.4rem;
  padding: 0.7rem 1rem; border: 1px dashed var(--border2); border-radius: 8px;
  background: var(--gold-dim);
}
.recovery-inline.show { display: flex; }
.ri-label {
  font-family: 'DM Sans', system-ui, sans-serif; font-weight: 600; font-size: var(--fs-label); letter-spacing: var(--ls-label);
  text-transform: uppercase; color: var(--fg3);
}
.ri-code {
  font-family: 'DM Sans', system-ui, sans-serif; font-variant-numeric: tabular-nums; font-size: var(--fs-label);
  letter-spacing: 0.14em; color: var(--gold-text); user-select: all; white-space: nowrap;
}
.ri-copy {
  background: none; border: 1px solid var(--border2); border-radius: 3px; cursor: pointer;
  padding: 0.22rem 0.6rem; color: var(--fg2);
  font-family: 'DM Sans', system-ui, sans-serif; font-weight: 600; font-size: var(--fs-label); letter-spacing: var(--ls-label);
  text-transform: uppercase;
}
.ri-copy:hover { color: var(--gold-text); border-color: var(--border3); }
.ri-copy:focus-visible { outline: 2px solid var(--gold2); outline-offset: 2px; }
.ri-note { font-size: var(--fs-label); color: var(--fg3); flex-basis: 100%; text-align: center; }
@media (max-width: 480px) { .ri-note { flex-basis: auto; } }

/* --- RETURN ACCESS (eye-print code) --------------------------------------
   Same bordered gold-dim panel as .signedin-state and .pool-verify: on this page
   that shape means "something happened, act on it", and saving this code is the
   single most consequential thing an anonymous rater can do -- it is the only way
   back to their reading and the only handle for an erasure request. */
/* PART-D76 (2026-09-10) W7: this panel no longer sits far down the page after the share
   card; overhaul.js now moves it to the slot directly under the generated-faces card,
   where #recoveryInline used to go. Its old margin (2rem top, 0 bottom) was tuned for a
   trailing position with nothing after it. It now takes .eye-print-faces' own vertical
   rhythm, 2.8rem above and 1.2rem below, so it reads as one clean continuation of the
   faces card rather than a second heavy interruption before the folio begins. */
/* PART-D96 (2026-09-15) SYE-17: the 2.8rem / 1.2rem seam is held until SYE-14's call-PDF check (tools/d76-controls.js i4 pins it);
   SYE-18 (Q9): the code box sits on the readout box (was 30rem). */
.recovery-holder {
  display: none; margin: 2.8rem auto 1.2rem; max-width: var(--ro-measure); padding: 1.2rem 1.3rem;
  border-radius: 12px; border: 1px solid var(--border2); background: var(--gold-dim);
  text-align: left;
}
.recovery-holder.show { display: block; animation: stageIn 0.4s var(--ease-luxury); }
.recovery-code {
  font-family: 'JetBrains Mono', ui-monospace, monospace; font-size: var(--fs-lead);
  letter-spacing: 0.16em; color: var(--gold-text); text-align: center;
  padding: 0.85rem 0.6rem; margin: 0.9rem 0; user-select: all;
  background: var(--bg2); border: 1px dashed var(--border3); border-radius: 4px;
  word-break: break-all;
}
.recovery-uses {
  list-style: none; margin: 0.9rem 0 0; padding: 0;
  font-size: var(--fs-body); line-height: 1.6; color: var(--fg2);
}
.recovery-uses li { position: relative; padding-left: 1.1rem; margin-bottom: 0.45rem; }
.recovery-uses li::before {
  content: 'C6'; position: absolute; left: 0; top: 0;
  color: var(--gold-text); font-size: 0.62rem; line-height: 1.9;
}
.recovery-uses strong { color: var(--fg); font-weight: 500; }
.recovery-warn { font-size: var(--fs-body); color: var(--fg3); line-height: 1.6; margin-top: 0.8rem; }
#copyRecoveryBtn { width: 100%; }

/* the returning-user entry on the hero */
.recover-entry { margin-top: 1.6rem; text-align: center; }
.recover-toggle {
  background: none; border: none; cursor: pointer; padding: 0.35rem;
  font-family: 'DM Sans', system-ui, sans-serif; font-weight: 600; font-size: var(--fs-label); letter-spacing: var(--ls-label);
  text-transform: uppercase; color: var(--fg3);
  text-decoration: underline; text-underline-offset: 3px;
}
.recover-toggle:hover { color: var(--gold-text); }
.recover-toggle:focus-visible { outline: 2px solid var(--gold2); outline-offset: 2px; }
.recover-form { display: none; max-width: 22rem; margin: 0.9rem auto 0; text-align: left; }
.recover-form.show { display: block; animation: stageIn 0.3s var(--ease-luxury); }

/* --- ERASURE, from the same code input -------------------------------------
   Understated on purpose. This must be FINDABLE by someone who came back
   specifically to exercise it -- the page promises them it exists -- without
   competing with 'bring my reading back', which is what almost everyone here
   actually wants. Hence a quiet link that opens a real confirm, rather than a
   second button of equal weight. */
.recover-delete {
  display: block; margin: 0.8rem auto 0; padding: 0.3rem 0.2rem;
  background: none; border: none; cursor: pointer;
  font-family: 'DM Sans', system-ui, sans-serif; font-weight: 600; font-size: var(--fs-label);
  letter-spacing: var(--ls-label); text-transform: uppercase; color: var(--fg3);
  text-decoration: underline; text-underline-offset: 3px;
}
.recover-delete:hover { color: var(--wine2); }
.recover-delete:focus-visible { outline: 2px solid var(--wine2); outline-offset: 2px; }
.recover-confirm {
  display: none; margin-top: 0.8rem; padding: 0.9rem 1rem; border-radius: 10px;
  border: 1px solid var(--wine2); background: rgba(154,61,72,0.08);
}
.recover-confirm.show { display: block; animation: stageIn 0.3s var(--ease-luxury); }
.rc-warn { font-size: var(--fs-body); line-height: 1.6; color: var(--fg2); margin-bottom: 0.8rem; }
.rc-row { display: flex; gap: 0.6rem; }
.rc-row .btn { flex: 1 1 0; min-width: 0; padding-inline: 0.8rem; font-size: var(--fs-label); }
.btn-danger {
  background: var(--wine2); color: #FFF3F4; border: 1px solid var(--wine2);
  box-shadow: none;
}
.btn-danger:hover { background: var(--wine); border-color: var(--wine); }
.recover-done {
  display: none; margin-top: 0.9rem; padding: 0.9rem 1rem; border-radius: 10px;
  border: 1px solid var(--border2); background: var(--gold-dim);
  font-size: var(--fs-body); line-height: 1.6; color: var(--fg2);
}
.recover-done.show { display: block; animation: stageIn 0.3s var(--ease-luxury); }
@media (prefers-reduced-motion: reduce) {
  .recover-confirm.show, .recover-done.show { animation: none; }
}
#recoverInput {
  width: 100%; padding: 0.75rem 0.9rem; background: var(--bg2);
  border: 1px solid var(--border2); border-radius: 3px; color: var(--fg);
  font-family: 'JetBrains Mono', ui-monospace, monospace; font-size: var(--fs-body);
  letter-spacing: 0.12em; text-align: center; text-transform: uppercase;
}
#recoverInput:focus {
  outline: none; border-color: var(--gold2);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--gold2) 18%, transparent);
}
@media (prefers-reduced-motion: reduce) {
  .recovery-holder.show, .recover-form.show { animation: none; }
}

/* --- EMAIL VERIFICATION -------------------------------------------------------
   Same visual idiom as .signedin-state on purpose: a bordered gold-dim panel is
   already this page's "something happened, act on it" shape, and that is exactly
   what this is -- the join has NOT completed until the mailbox is proven.
   Toggled by JS (.show), not by a scroll animation, so the freeze-safe rule in
   folio.css does not apply: there is no animation-timeline base state to strand. */
.pool-verify {
  display: none; margin-top: 1.4rem; padding: 1.1rem 1.2rem; border-radius: 12px;
  border: 1px solid var(--border2); background: var(--gold-dim); text-align: left;
}
.pool-verify.show { display: block; animation: stageIn 0.4s var(--ease-luxury); }
.verify-eyebrow {
  font-family: 'DM Sans', system-ui, sans-serif; font-weight: 600; font-size: var(--fs-label); letter-spacing: var(--ls-label);
  text-transform: uppercase; color: var(--gold-text); margin-bottom: 0.5rem;
}
.verify-lede { font-size: var(--fs-body); line-height: 1.6; color: var(--fg2); margin-bottom: 1rem; }
.verify-lede strong { color: var(--fg); font-weight: 500; word-break: break-all; }
/* Box copied from #poolEmail so the two fields read as one form, but widely
   tracked (tabular DM Sans since PART-D96, Q2): six digits should look like six digits, and a transposed pair should be
   visible without counting. */
#verifyCode {
  width: 100%; padding: 0.75rem 0.9rem; background: var(--bg2);
  border: 1px solid var(--border2); border-radius: 3px; color: var(--fg);
  font-family: 'DM Sans', system-ui, sans-serif; font-variant-numeric: tabular-nums; font-size: var(--fs-body);
  letter-spacing: 0.34em; text-align: center;
}
#verifyCode:focus {
  outline: none; border-color: var(--gold2);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--gold2) 18%, transparent);
}
.verify-resend {
  display: block; margin: 0.75rem auto 0; padding: 0.3rem 0.2rem;
  background: none; border: none; cursor: pointer;
  font-family: 'DM Sans', system-ui, sans-serif; font-weight: 600; font-size: var(--fs-label);
  letter-spacing: var(--ls-label); text-transform: uppercase; color: var(--fg3);
  text-decoration: underline; text-underline-offset: 3px;
}
.verify-resend:hover { color: var(--gold-text); }
.verify-resend:focus-visible { outline: 2px solid var(--gold2); outline-offset: 2px; }
.verify-resend:disabled { opacity: 1; color: var(--fg3); cursor: not-allowed; text-decoration: none; }   /* PART-D96 (2026-09-15) SYE-3: no opacity on text */

/* The banner for the ?verify= link. Sits above every stage because that link is
   normally opened on a second device, where no readout exists to host the panel. */
.verify-banner {
  display: none; max-width: 34rem; margin: 1rem auto 0; padding: 0.85rem 1.1rem;
  border-radius: 12px; border: 1px solid var(--border2); background: var(--gold-dim);
  font-size: var(--fs-body); line-height: 1.6; color: var(--fg2); text-align: center;
}
.verify-banner.show { display: block; animation: stageIn 0.4s var(--ease-luxury); }
.verify-banner strong { color: var(--fg); font-weight: 500; }
.verify-banner.bad { border-color: var(--wine2); }

@media (prefers-reduced-motion: reduce) {
  .pool-verify.show, .verify-banner.show { animation: none; }
}

/* =====================================================================
   THE ENTRY CURTAIN  (LN-ENTRY-CURTAIN-PLAN-v2.md)

   A short viewport-height cover is the sticky element. The info cards and
   the entry form sit BELOW it in normal flow and scroll over it.

   v1 made the FORM sticky and that was fatal: the entry panel is ~1400px
   tall on a phone, and a sticky element taller than the viewport with only
   top:0 never scrolls to its own bottom - so the consent tick and Start
   became unreachable. The cover holds a title and an eye and nothing else,
   so it cannot hide anything.

   SPECIFICITY, and it is the reason v1 produced no curtain at all:
   demo.css:511-514 sets `#stage-entry > *:not(.ambient-canvas)` to
   position:relative; z-index:1 at (1,1,0). A bare `.ln-cover{position:sticky}`
   at (0,1,0) loses silently. The rules below are written as
   `#stage-entry > .ln-cover` - also (1,1,0) - and live at the END of this
   file, so they win on source order. Do not "tidy" them into a bare class
   and do not move them above line 514.
   ===================================================================== */

/* Drop the 620px reading cap for the duration of the curtain; the measure
   moves inward to .ln-flow > * so the form is still a 620px column. */
#stage-entry.ln-curtain { max-width: none; padding: 0 0 4rem; }

/* Geometry copied from .folio-cover (styles/folio.css:80-85) - the one sticky
   curtain this repo has shipped and verified under html/body{overflow-x:clip}. */
#stage-entry > .ln-cover {
  position: -webkit-sticky; position: sticky; top: 0; z-index: 0;
  min-height: 100vh; min-height: 100svh; min-height: 100dvh;  /* svh/dvh so an iOS URL-bar collapse never jumps it */
  display: flex; flex-direction: column; justify-content: center; align-items: center;
  text-align: center;
  background: var(--bg);
  overflow: visible;                     /* MUST stay visible - curtain ancestor chain */
}
.ln-cover-inner { position: relative; z-index: 1; padding: 0 clamp(1rem, 5vw, 3rem); }
.ln-cover-title { margin-top: 0.7rem; }
.ln-cover-sub {
  color: var(--fg2); font-size: var(--fs-body); line-height: 1.6;
  max-width: 34ch; margin: 0.8rem auto 0;
}
.ln-cover-eye { width: min(150px, 38vw); height: auto; margin: 1.8rem auto 0; display: block; }
.ln-scrollcue {
  margin-top: 1.6rem; font-size: var(--fs-body); color: var(--gold-text);
  /* base state is fully visible: no hidden/zero/offset state outside the double
     gate, so an engine without animation support still shows the cue */
  opacity: 1;   /* PART-D96 SYE-3: was 0.75, no opacity on text (Q8) */
}
@media (prefers-reduced-motion: no-preference) {
  .ln-scrollcue { animation: lnCueDrift 2.1s var(--ease-refined) infinite; }
  /* starts and ends at rest, so the global reduce block's 0.01ms clamp lands on a sane frame */
  @keyframes lnCueDrift { 0%,100% { transform: none; } 50% { transform: translateY(7px); } }
}

/* Everything that scrolls over the cover. Opaque + above it: the same contract
   .folio-ch uses at folio.css:50-52. z-index/position come from demo.css:512. */
.ln-flow {
  background: var(--bg);
  padding: clamp(2.5rem, 8vh, 5rem) clamp(1rem, 4vw, 2.5rem) 0;
  overflow: visible;                     /* MUST stay visible - curtain ancestor chain */
}
.ln-flow > * { max-width: 620px; margin-left: auto; margin-right: auto; }
.ln-cards { margin-bottom: clamp(2rem, 5vh, 3rem); }

/* ---- the three cards: real <details>, so keyboard + AT come for free ---- */
.ln-card {
  border: 1px solid var(--border); border-radius: 14px;
  background: var(--card); margin-bottom: 0.9rem;
  overflow: hidden;                      /* safe: a card is not a curtain ancestor */
}
.ln-card > summary {
  list-style: none; cursor: pointer;
  display: flex; align-items: center; gap: 0.75rem;
  min-height: 56px; padding: 0.9rem clamp(1rem, 3vw, 1.5rem);
  font-size: var(--fs-label); font-weight: 600; color: var(--fg);
  transition: background 0.2s var(--ease-refined);
}
.ln-card > summary::-webkit-details-marker { display: none; }
.ln-card > summary::after {
  content: "+"; margin-left: auto; flex: 0 0 auto;
  font-family: 'DM Sans', system-ui, sans-serif; font-size: var(--fs-body); font-weight: 400;
  color: var(--gold-text); line-height: 1;
}
.ln-card[open] > summary::after { content: "\2212"; }   /* minus */
.ln-card > summary:hover { background: var(--surface); }
.ln-card > summary:focus-visible { outline: 2px solid var(--gold2); outline-offset: -2px; }
.ln-card-n {
  font-family: 'DM Sans', system-ui, sans-serif; font-weight: 600; font-size: var(--fs-label); letter-spacing: var(--ls-label);
  text-transform: uppercase; color: var(--gold-text); flex: 0 0 auto;
}
.ln-card-body {
  padding: 0 clamp(1rem, 3vw, 1.5rem) 1.25rem;
  color: var(--fg2); font-size: var(--fs-body); line-height: 1.6;
  border-top: 1px solid var(--border);
  padding-top: 1.1rem;
}
.ln-card-body p { margin: 0 0 0.8rem; }
.ln-card-body p:last-child { margin-bottom: 0; }
.ln-card-body ul { margin: 0; padding-left: 1.15rem; }
.ln-card-body li { margin-bottom: 0.55rem; }
.ln-card-body li:last-child { margin-bottom: 0; }
.ln-card-body strong { color: var(--fg); font-weight: 600; }
.ln-card-body a { color: var(--gold-text); }
.ln-card-neg-lead { margin-top: 1rem !important; color: var(--fg3); font-size: var(--fs-body); }
.ln-card-neg li::marker { color: var(--gold-text); }
.ln-card-note {
  font-size: var(--fs-body); color: var(--fg3);
  border-left: 2px solid var(--border2); padding-left: 0.8rem;
}

/* ---- Tick 1: consent in the entry form, directly above Start ----
   Reuses the .pool-check geometry (Tick 2) so the two ticks read as one system.
   Hit target is the whole label, and >= 24px, per the accessibility note. */
/* PART-D96 (2026-09-15) SITE-2: tick text is --fs-label (Q2). The second selector outranks .field > label, which
   had been forcing this label to display:block, uppercase and 0.78rem. */
.ln-consent, .field > label.ln-consent {
  display: flex; gap: 0.6rem; align-items: flex-start;
  font-size: var(--fs-label); font-weight: 400; letter-spacing: normal; text-transform: none; line-height: 1.55; color: var(--fg2);
  cursor: pointer; padding: 0.15rem 0;
}
.ln-consent input {
  flex: 0 0 auto; margin-top: 0.15rem; width: 1.1rem; height: 1.1rem;
  accent-color: var(--gold2); cursor: pointer;
}
.ln-consent input:focus-visible { outline: 2px solid var(--gold2); outline-offset: 2px; }
.ln-consent a { color: var(--gold-text); }

/* The consent field is #entryForm's 7th child, so .entry-actions is now the 8th.
   The stagger at demo.css:199-205 stops at 7, and without this the Start button
   rises ahead of the field above it. */
#stage-entry.active #entryForm > *:nth-child(8) { animation-delay: 0.63s; }

@media (prefers-reduced-motion: reduce) {
  /* the curtain is geometry, not animation, so it needs no reduce fallback -
     but the cover must not also try to recede. */
  .ln-scrollcue { animation: none !important; }
}

/* =====================================================================
   §4.3 · BACK ARROW ON THE RATING SCALE (undo a misclick)
   Sits by position 1. Desktop: in the left gutter, vertically centred on
   the row. Phone (no side room): tucked to the row's top-left, above
   position 1. Only reserves that top space when the arrow is actually
   shown (.has-back), so the first face of a run has no empty gap.
   ===================================================================== */
.scale-row-wrap { position: relative; }
.scale-back {
  position: absolute; left: 0; top: 50%; transform: translateY(-50%);
  width: 40px; height: 40px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--surface); border: 1px solid var(--border2); color: var(--gold-text);
  cursor: pointer;
  transition: border-color 0.2s var(--ease-refined), color 0.2s var(--ease-refined), transform 0.2s var(--ease-overshoot);
}
.scale-back svg { width: 18px; height: 18px; display: block; }
.scale-back:hover { border-color: var(--gold2); color: var(--gold-text); transform: translateY(-50%) scale(1.07); }
.scale-back:active { transform: translateY(-50%) scale(0.93); }
.scale-back:focus-visible { outline: 2px solid var(--gold2); outline-offset: 2px; }
/* NEW 2026-08-24: hover/focus hint, directly below the button. Same type treatment as
   .scale-ends ("NO DESIRE" / "IDEAL") -- JetBrains Mono, uppercase, letter-spaced -- and
   the same 0.2s fade speed already used for this button's own hover transition above. */
.scale-back::after {
  content: "Go Back?";
  position: absolute; top: 100%; left: 50%; transform: translateX(-50%);
  margin-top: 0.5rem; white-space: nowrap;
  font-family: 'DM Sans', system-ui, sans-serif; font-weight: 600; font-size: var(--fs-label); letter-spacing: var(--ls-label);
  text-transform: uppercase; color: var(--fg3);
  opacity: 0; pointer-events: none;
  transition: opacity 0.2s var(--ease-refined);
}
.scale-back:hover::after, .scale-back:focus-visible::after { opacity: 1; }
/* NEW 2026-08-24: on desktop the button sits inline with the rating row, directly above
   the "NO DESIRE / SOMEWHERE BETWEEN / IDEAL" labels -- putting the hint below it would
   crowd that row. Desktop gets it above instead; mobile (>640px override moves the
   button to top-left, away from the label row) keeps the default below placement. */
@media (min-width: 641px) {
  .scale-back::after { top: auto; bottom: 100%; margin-top: 0; margin-bottom: 0.5rem; }
}

/* the restored previous rating: outline + soft glow, no fill — reads as "your last choice,
   still editable", and deliberately does NOT use .picked so the pick-spark never re-fires */
.scale button.restored {
  border-color: var(--gold2); color: var(--gold-text);
  box-shadow: inset 0 0 0 1px var(--gold2), 0 0 16px var(--gold-glow);
}
.scale button.restored .pip { background: var(--gold2); }

@media (max-width: 640px) {
  .scale-row-wrap.has-back { padding-top: 3rem; }
  .scale-back { top: 0; left: 0; transform: none; }
  .scale-back:hover { transform: scale(1.07); }
  .scale-back:active { transform: scale(0.93); }
}
@media (prefers-reduced-motion: reduce) {
  .scale-back, .scale-back::after { transition: none; }
}

/* =====================================================================
   §7 · THE TWO GENERATED FACES (desire | disgust) + gender-flip toggle
   Composite portraits in the light pink-cream 5px frame (--face-frame),
   centred and equally sized, on the page's cream ground. A card flip
   swaps both to the opposite gender; reduced-motion gets a cross-fade.
   ===================================================================== */
/* MOVED 2026-08-24: this block now sits above Folio I as a section of its own rather than as a
   small pair inside #share-section, so it is sized to occupy that room. It is capped at 52rem
   and centred; #stage-readout.folio drops its own max-width to none, but it sits inside <main>,
   which carries the page gutter, so this block never runs edge to edge.
   PART-D72 (2026-09-09) W4: the competing side padding here is REMOVED so the one horizontal
   gutter is main's own clamp(1rem, 4vw, 2.5rem), not that value plus this
   block's own. On a narrow screen the two used to stack into a doubled gutter. */
/* PART-D75 (2026-09-10) W1 / A11, the title lift: margin-top was 2.8rem. It is the OTHER
   half of the collapsed sibling pair described on .readout-sub above; the two were cut to
   the same 0.9rem in one move, dropping the collapsed title-to-pair gap from 44.8px to
   14.4px. The margin-bottom (1.2rem) is a different, unrelated gap and is unchanged. */
/* PART-D96 (2026-09-15) SYE-18 (Q9): the faces card sits on the readout box (was 52rem) */
.eye-print-faces {
  max-width: var(--ro-measure); margin: 0.9rem auto 1.2rem; text-align: center;   /* tightened: the code block now follows directly */
  padding: 0;
}
.epf-pair {
  display: flex; gap: clamp(1rem, 3.5vw, 2rem);
  justify-content: center; align-items: stretch;
}
/* width AND flex-basis: the pair below is width:fit-content, and intrinsic sizing reads a flex
   item's width, not its flex-basis (a basis alone left the pair 32px wide, the bare gap). The
   basis still governs the flex layout itself; min-width:0 + shrink let phones squeeze it. */
.epf-card { flex: 0 1 min(22rem, 42vw); width: min(22rem, 42vw); min-width: 0; }   /* was 11rem: doubled to fill the section */
/* PART-D75 (2026-09-10) W1 / A11: the static pole label above each frame. THE SERIF IDIOM
   IS DELIBERATE and is this round's recommended default, not an accident of copying the
   nearest rule. The file's other idiom, the letterspaced mono, force-uppercases through
   text-transform (.kicker, .folio-eyebrow, .epf-toggle all do), which would silently flatten
   Avery's deliberate title case to DESIRE / DISDAIN; the mono is also this file's register
   for metadata ABOUT content, not for the content itself, and at 0.6 to 0.82rem it
   under-sells the one word each frame exists to deliver. var(--gold2), not var(--fg), keeps
   it subordinate to the h2 above while still reading as branded gold.
   THE MARGIN-BOTTOM IS A COLLISION BUFFER AND IS DELIBERATE: 1.55rem is the spec's own
   0.6rem plus roughly 15px of clearance, so the landed-state transient scale(1.05) grow on
   .epf-flip that the generation-reveal round adds can never visually reach this label,
   whichever of the two rounds merges first. Do not shrink it below 1.55rem. */
.epf-pole-label {
  display: block; text-align: center;
  font-family: 'DM Sans', system-ui, sans-serif; font-style: italic; font-weight: 500;   /* PART-D96 (2026-09-15) SYE-15 (Q2) */
  font-size: var(--fs-lead); line-height: 1.1;
  color: var(--gold-text); letter-spacing: 0.01em;
  margin: 0 0 1.55rem;
}

/* PART-D76 (2026-09-10) W5, Avery A7 to A9 with Q13 answered "theyre alike just the
   confetti is different": the prototype's grow-and-reveal, ported onto every live
   landing. The numbers are the prototype's POST-ARM values verbatim (200ms overshoot
   to 1.05, then 1.5s later a 500ms settle to 0.95 on the softer curve). Its
   scale(0.6) pre-shrink is NOT ported: that belonged to the blacked-out, tap-gated
   armed state A8 removes, and the live tile is visible at its resting scale 1
   throughout generation, so it has no equivalent state to grow from.
   .epf-flip sits ABOVE .epf-inner, the only node carrying transform-style:
   preserve-3d, so this scale is a plain 2D transform outside the 3D subtree and can
   never mirror anything (the same reasoning that puts PART-D75's pole label on
   .epf-card). THE PEAK IS 1.05 AND MUST STAY THERE: .epf-pole-label's 1.55rem
   margin-bottom is a clearance sized for exactly this bleed.
   NO will-change here, deliberately, against the spec's own draft: this round's
   research rejects blanket will-change on a class, two elements do not need the
   standing GPU cost, and W6 forbids shipping any new layer promotion around the
   rotated subtree while the mirroring cause is still unproven. */
.epf-flip {
  position: relative; width: 100%; aspect-ratio: 540 / 704; perspective: 1200px;
  transform: scale(1); transform-origin: center;
  transition: transform 0.2s var(--ease-overshoot);
}
.epf-flip.epf-landed { transform: scale(1.05); }
.epf-flip.epf-landed.epf-settled { transform: scale(0.95); transition: transform 0.5s var(--ease-cinematic); }

.epf-inner {
  position: absolute; inset: 0;
  transform-style: preserve-3d;
  transition: transform 0.64s var(--ease-heavy);
}
.epf-inner.flipped { transform: rotateY(180deg); }
.epf-face {
  position: absolute; inset: 0;
  -webkit-backface-visibility: hidden; backface-visibility: hidden;
  border: 5px solid var(--face-frame); border-radius: 8px; overflow: hidden;
  background: var(--face-frame-bg);
  /* PART-D72 (2026-09-09) W6: the frame shadow was 0 12px 34px, a 46px reach that bled
     across the passage into .epf-controls just 1.1rem below, so the face pair and the
     controls read as one smudged unit (worst in the dark theme, whose --face-frame-shadow
     is black at 0.45 against light theme's maroon at 0.12). Tightened to a 28px reach and
     paired with a larger controls clearance below, so the two blocks separate cleanly. */
  box-shadow: 0 8px 20px var(--face-frame-shadow);
}
.epf-face.back { transform: rotateY(180deg); }
.epf-face img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* PART-D76 (2026-09-10) W6, Avery A5 (the flip renders NEXT as TXEN): .epf-face already
   carries both the prefixed and unprefixed declarations above, verified at this pin, so
   this is EXTENSION, not a change to it. Every descendant inside the preserve-3d subtree
   that until now only INHERITED the hiding restates it for itself, targeting the
   documented WebKit gotcha that backface-visibility does not reliably propagate through
   a descendant the engine promotes to its own compositing layer inside a doubly-rotated
   3D ancestor. THIS ROUND ADDS NO NEW NODE INSIDE THE SUBTREE: W4's flourish is an
   attribute on nodes that already exist and W5's grow classes land on .epf-flip, which
   is outside it. THE BUG IS NOT CLAIMED CURED: the phase 1 verifier could not reproduce
   the mirroring outside WebKit, so only a real affected Safari build can confirm this,
   and the ordered ladder of next candidates is in the build report. */
.epf-face img, .epf-veil, .knot-loader, .knot-loader-glyph, .knot-phase {
  -webkit-backface-visibility: hidden; backface-visibility: hidden;
}

.epf-controls { margin-top: 2rem; }   /* PART-D72 W6: 1.1rem -> 2rem, clearing the tightened 28px frame shadow so it no longer bleeds into the controls */
.epf-toggle {
  display: inline-flex; align-items: center; gap: 0.5rem;
  min-height: 40px; padding: 0.45rem 1.15rem; border-radius: 999px;
  background: var(--surface); border: 1px solid var(--border2); color: var(--fg2);
  font-family: 'DM Sans', system-ui, sans-serif; font-weight: 600; font-size: var(--fs-label);
  letter-spacing: var(--ls-label); text-transform: uppercase;
  transition: border-color 0.2s var(--ease-refined), color 0.2s var(--ease-refined), transform 0.2s var(--ease-overshoot);
  cursor: pointer;
}
.epf-toggle::before {
  content: "\21C4"; /* ⇄ swap glyph */ font-size: var(--fs-label); color: var(--gold-text); line-height: 1;
}
.epf-toggle:hover { border-color: var(--gold2); color: var(--gold-text); transform: translateY(-1px); }
.epf-toggle:active { transform: translateY(0) scale(0.98); }
.epf-toggle:focus-visible { outline: 2px solid var(--gold2); outline-offset: 2px; }
.epf-toggle:disabled { opacity: 1; color: var(--fg3); border-color: var(--fg4); cursor: default; transform: none; }   /* PART-D96 (2026-09-15) SYE-3: no opacity on text */
.epf-toggle:disabled::before { color: var(--fg3); }

/* 7.2: same font as the page body, same size as the hero eye caption (.figcap, 0.72rem) */
.epf-credit {
  font-family: 'DM Sans', system-ui, sans-serif; font-size: var(--fs-label);
  color: var(--fg2); margin-top: 1.3rem; letter-spacing: 0.02em; line-height: 1.4;
}
/* PART-D72 W1(b): applyGenState empties the credit when no slot is ready, so it must
   collapse out of flow rather than leave a blank margin, exactly as .epf-note:empty does. */
.epf-credit:empty { display: none; }
/* 7.3: HARMONIA in the display face, a step under the "This is Your Eye" heading */
.epf-wordmark {
  font-family: var(--font-display); font-weight: 600;
  letter-spacing: 0.24em; text-indent: 0.24em;
  font-size: var(--fs-title); line-height: 1.15;
  color: var(--gold-text); margin-top: 0.2rem;
  /* inline-flex so the flanking hairlines below can sit on the same baseline row; the parent
     .eye-print-faces is text-align:center, which still centres an inline-flex box */
  display: inline-flex; align-items: center; white-space: nowrap;
}
/* Same flourish as the homepage footer wordmark (assets/css/styles.css, .footer-home
   .logo-text::before/::after): thin gold hairlines flanking the wordmark, fading outward.
   Copied rather than shared because this page does not load assets/css/styles.css.
   --gold-champagne is not defined in demo.css, so it falls back to --gold here.
   The 0.24em text-indent above already balances the trailing letter-space, so both
   hairlines take the same margin -- exactly as the homepage comment notes. */
.epf-wordmark::before, .epf-wordmark::after {
  content: ""; flex: none; width: 56px; height: 1px; opacity: 0.55;
}
.epf-wordmark::before {
  margin-right: 0.9rem;
  background: linear-gradient(to left, var(--gold-champagne, var(--gold)), transparent);
}
.epf-wordmark::after {
  margin-left: 0.9rem;
  background: linear-gradient(to right, var(--gold-champagne, var(--gold)), transparent);
}
@media (max-width: 480px) {
  .epf-wordmark::before, .epf-wordmark::after { display: none; }   /* homepage hides them here too */
}
:root[data-theme="light"] .epf-wordmark { color: var(--gold-text); }

@media (prefers-reduced-motion: reduce) {
  /* cross-fade fallback: no 3D rotation; the two faces stack and swap opacity */
  .epf-inner { transition: none; }
  .epf-inner.flipped { transform: none; }
  .epf-face { transition: opacity 0.32s ease; -webkit-backface-visibility: visible; backface-visibility: visible; }
  .epf-face.back { transform: none; opacity: 0; }
  .epf-face.front { opacity: 1; }
  .epf-inner.flipped .epf-face.front { opacity: 0; }
  .epf-inner.flipped .epf-face.back { opacity: 1; }
  .epf-toggle { transition: none; }
}

/* =====================================================================
   2026-09-05 · THE GENERATING VEIL and THE READY CARD (js/overhaul.js
   genVeilPaint / openFacesReadyPopup).
   The veil is a WHITE plate in the portraits' own pink-cream 5px frame, laid
   over the pair while the pair on screen is still being made, with the knot
   turning in its centre: three full turns in 1.5 s, then a 3 s rest, on a
   loop. It is white in BOTH themes, like the frame, because it stands in for
   the portraits and not for the page. Hidden with the hidden attribute, which
   the explicit [hidden] rule has to honour over the flex display below.
   ===================================================================== */
/* fit-content: the plate is inset on the pair, so the pair must be exactly as wide as
   its two frames or the plate's frame overhangs the portraits' and jumps inward at the
   reveal (16px a side at 1440px). max-width keeps the phone shrink behaviour. */
.epf-pair { position: relative; width: fit-content; max-width: 100%; margin-left: auto; margin-right: auto; }
/* PART-D76 (2026-09-10) W1, Avery A1: THE BLOCKING VEIL IS GONE. The plate lay over
   both portrait frames for the whole wait and hid the per-slot cream fields and knot
   loaders PART-D71 and PART-D72 exist to show. Its NODE survives, empty, because
   genVeilPaint still querySelects it and its genCelebrated.veiledRun latch is what
   lets a sitting that reopens mid-generation still get the ready card and fireworks;
   only the paint dies. display:none in BOTH states, so no future [hidden] removal can
   ever resurrect a plate. .epf-gen-title, .epf-gen-logo and @keyframes epfKnotSpin are
   deleted outright: dead once the markup stops creating those elements. */
.epf-gen-veil, .epf-gen-veil[hidden] { display: none !important; }
/* the ready card: the same centred scrim + card as the consent card and the batch panel */
.epf-ready-scrim {
  position: fixed; inset: 0; z-index: 1200;
  display: flex; align-items: center; justify-content: center;
  padding: clamp(1rem, 5vw, 2rem); overflow-y: auto;
  background: rgba(18, 9, 10, 0.9);                                   /* fallback */
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
  animation: batchScrimIn 0.32s var(--ease-cinematic);
}
.batch-card.epf-ready-card { max-width: 440px; }   /* must outrank .batch-card's own 520px */
.epf-ready-card h3 { font-size: var(--fs-title); }
.epf-ready-text { color: var(--fg2); font-size: var(--fs-body); line-height: 1.6; margin-top: 0.6rem; }
.epf-ready-row { display: flex; gap: 0.8rem; justify-content: center; flex-wrap: wrap; margin-top: 1.5rem; }
@media (prefers-reduced-motion: reduce) {
  /* PART-D76 W1: the .epf-gen-logo line went with the element it targeted. */
  .epf-ready-scrim { animation: none; }
  /* PART-D76 W5: the grow lands on its END state instantly rather than being deleted,
     so a reduced-motion reader still sees the picture at the size it settles to. The
     sitewide clamp at the top of this file already collapses the durations; these are
     stated explicitly so the end states can never drift apart from the animated path.
     Confetti needs no rule here: burstConfetti returns before it draws anything. */
  .epf-flip { transition: none; }
  .epf-flip.epf-landed { transform: scale(1.05); }
  .epf-flip.epf-landed.epf-settled { transform: scale(0.95); transition: none; }
}

/* =====================================================================
   PART-D71 (2026-09-08) THE KNOT LOADER
   Per-slot loading motion on the Your Eye results card. Design authority:
   research/KNOT-LOADER-SPEC-20260908.md. Authored FRESH under this round's own
   names; the marketing precedents (.synthesis-knot-icon, .hero-knot-eclipse,
   knotAppear, vennSpin) live in assets/css/styles.css, which local-network.html
   does NOT load, so they are precedent for VALUES only and are never referenced
   here. The knot is GOLD, never red (Avery's standing correction). No percentage,
   no ETA, no deceleration; brightness is arrival, fired once per slot at the
   wire-confirmed landing, never a progress ramp.
   REDUCED MOTION is handled by demo.css's sitewide blanket rule (about :761):
   every animation below RESTS on a calm, fully visible state and none uses a
   forwards fill, so a zeroed animation lands on the correct calm appearance. The
   two JS-timed exceptions are the phase-word clock (poll-driven text) and the
   flare, which shows for its full life on a plain JS timer (base opacity 1).
   ===================================================================== */

/* The knot overlays the composite inside each .epf-face (position:absolute;
   inset:0; overflow:hidden). The phase word sits beneath it. */
.knot-loader {
  position: absolute; inset: 0; z-index: 2;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 0.7rem; pointer-events: none;
  opacity: 1;   /* BASE RESTING STATE: visible and calm, where a reduced-motion pass lands */
}
.knot-loader[data-knot="hidden"] { display: none; }
/* PART-D76 (2026-09-10) W3, Avery A3 answered at Q8 ("half the size of the logo
   itself"): 58% becomes 29%, exactly half, and the size is CONSTANT across every
   state. No per-state size override existed and none is added; W4's running flourish
   below changes rotation speed and brightness only, never scale. */
.knot-loader-glyph {
  width: 29%; height: 29%; display: block; transform-origin: 50% 50%;
  /* fill is set on the svg so the fill-less <symbol> path inherits it across the
     <use> shadow boundary; one rule per theme colours all four instances with no
     MutationObserver and no poll (the working form of the spec's intent). */
  fill: url(#knotGoldLight);
  filter: drop-shadow(0 0 8px rgba(154,106,40,0.22));
}
:root:not([data-theme="light"]) .knot-loader-glyph { fill: url(#knotGoldDark); filter: drop-shadow(0 0 9px rgba(240,200,110,0.30)); }
:root[data-theme="dark"] .knot-loader-glyph { fill: url(#knotGoldDark); filter: drop-shadow(0 0 9px rgba(240,200,110,0.30)); }
:root[data-theme="light"] .knot-loader-glyph { fill: url(#knotGoldLight); filter: drop-shadow(0 0 8px rgba(154,106,40,0.22)); }

/* NOT YET RUNNING: breathe. Oscillates opacity 0.85..0.55 and scale 1..0.94; the
   base (no animation, reduced motion) rests at opacity 1 scale 1, calm and visible. */
.knot-loader[data-knot="breathe"] .knot-loader-glyph { animation: knotBreathe 3200ms var(--ease-refined) infinite; }
@keyframes knotBreathe {
  0%, 100% { opacity: 0.85; transform: scale(1); }
  50%      { opacity: 0.55; transform: scale(0.94); }
}
/* RUNNING: constant-speed rotation, LINEAR, never eased, never slowed. */
.knot-loader[data-knot="spin"] .knot-loader-glyph { animation: knotSpin 2200ms linear infinite; }
@keyframes knotSpin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
/* PART-D76 (2026-09-10) W4, Avery A4 with Q9 answered as the RUNNING state: the moment
   a slot is known on the wire to be running, its knot spins at exactly double rate for
   a bounded 1400ms window and its card brightens. THE SELECTOR IS HALF OF MERGE GATE
   B2: it requires data-knot="spin", a LIVE state, so genActiveLoader's own per-repaint
   write of data-knot="still" under the D64 give-up extinguishes the fast spin with no
   JS race left to lose. Reverting needs no second rule: the steady knotSpin underneath
   simply applies again once data-knot-flourish is removed. */
.knot-loader[data-knot="spin"][data-knot-flourish="1"] .knot-loader-glyph { animation: knotSpinFast 1100ms linear infinite; }
@keyframes knotSpinFast { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
/* THE THIRD CHANNEL, THE GLYPH'S OWN BRIGHTNESS, flagged in the PR for Avery's word:
   A4 names the knot's brightness ("the brightness of it gets heightened"), which the
   card's ambient glow alone under-serves. A heightened drop-shadow tier for the same
   window, per theme, extending the glyph's existing per-theme drop-shadow above. The
   dark-to-light alpha ratio 1.35 matches the glyph's OWN static baseline ratio
   (0.30/0.22 = 1.36), not the card tier's steeper family. Strike these four rules and
   the two-channel form stands with nothing else changed. */
.knot-loader[data-knot="spin"][data-knot-flourish="1"] .knot-loader-glyph { filter: drop-shadow(0 0 11px rgba(154,106,40,0.34)); }
:root:not([data-theme="light"]) .knot-loader[data-knot="spin"][data-knot-flourish="1"] .knot-loader-glyph { filter: drop-shadow(0 0 13px rgba(240,200,110,0.46)); }
:root[data-theme="dark"] .knot-loader[data-knot="spin"][data-knot-flourish="1"] .knot-loader-glyph { filter: drop-shadow(0 0 13px rgba(240,200,110,0.46)); }
:root[data-theme="light"] .knot-loader[data-knot="spin"][data-knot-flourish="1"] .knot-loader-glyph { filter: drop-shadow(0 0 11px rgba(154,106,40,0.34)); }
/* THE SETTLE: the loader fades and scales while the glyph keeps its spin (or
   breathe) beneath it, so the motion is never hard-cut; the JS hides the loader
   once the 260ms is done. Rests at opacity 0, so no forwards fill is needed. */
.knot-loader.knot-settling { animation: knotSettle 260ms var(--ease-overshoot); opacity: 0; }
@keyframes knotSettle { from { opacity: 1; transform: scale(1); } to { opacity: 0; transform: scale(1.06); } }

.knot-phase {
  font-family: 'DM Sans', system-ui, sans-serif; font-weight: 600; font-size: var(--fs-label);
  letter-spacing: var(--ls-label); text-transform: uppercase; color: var(--gold-text);
  text-shadow: 0 1px 8px var(--face-frame-shadow); max-width: 86%;
  text-align: center; line-height: 1.3;
}
.knot-phase:empty { display: none; }

/* THE ARRIVAL GLOW, GOLD, a per-tile bloom fired once at the wire-confirmed
   landing (never a filter:brightness ramp, never a progress meter). Set on
   .epf-face itself so it blooms OUTWARD past the tile (a child ::after would be
   clipped by the face's overflow:hidden). The optional wine inner ring of the
   marketing gradient is removed; these are gold only. Peak/resting values are the
   spec's, carried in per-theme custom properties so one keyframe serves both
   themes. Rise 0-120ms with the settle overshoot, decay to 1020ms, then the
   resting glow fades to the plain frame over 1200ms (first/middle) or 3000ms
   (final, so it coincides with the flare). */
:root {
  --face-shadow-base: 0 12px 34px var(--face-frame-shadow);
  --knot-glow-zero: 0 0 0 0 rgba(212,168,83,0);
  --knot-glow-peak: 0 0 28px 6px rgba(212,168,83,0.38);
  --knot-glow-rest: 0 0 8px 2px rgba(212,168,83,0.15);
  --knot-glow-peak-half: 0 0 18px 4px rgba(212,168,83,0.20);
  --knot-glow-rest-half: 0 0 6px 2px rgba(212,168,83,0.10);
  /* PART-D76 W4: the RUNNING tier, one step below the arrival peak. */
  --knot-glow-running-peak: 0 0 22px 5px rgba(212,168,83,0.30);
  --knot-glow-running-rest: 0 0 7px 2px rgba(212,168,83,0.13);
}
:root:not([data-theme="light"]) {
  --knot-glow-zero: 0 0 0 0 rgba(240,200,110,0);
  --knot-glow-peak: 0 0 32px 8px rgba(240,200,110,0.55);
  --knot-glow-rest: 0 0 10px 3px rgba(240,200,110,0.28);
  --knot-glow-peak-half: 0 0 20px 5px rgba(240,200,110,0.30);
  --knot-glow-rest-half: 0 0 8px 3px rgba(240,200,110,0.16);
  /* PART-D76 W4: the RUNNING tier, one step below the arrival peak. */
  --knot-glow-running-peak: 0 0 24px 6px rgba(240,200,110,0.42);
  --knot-glow-running-rest: 0 0 9px 3px rgba(240,200,110,0.22);
}
:root[data-theme="dark"] {
  --knot-glow-zero: 0 0 0 0 rgba(240,200,110,0);
  --knot-glow-peak: 0 0 32px 8px rgba(240,200,110,0.55);
  --knot-glow-rest: 0 0 10px 3px rgba(240,200,110,0.28);
  --knot-glow-peak-half: 0 0 20px 5px rgba(240,200,110,0.30);
  --knot-glow-rest-half: 0 0 8px 3px rgba(240,200,110,0.16);
  /* PART-D76 W4: the RUNNING tier, one step below the arrival peak. */
  --knot-glow-running-peak: 0 0 24px 6px rgba(240,200,110,0.42);
  --knot-glow-running-rest: 0 0 9px 3px rgba(240,200,110,0.22);
}
:root[data-theme="light"] {
  --knot-glow-zero: 0 0 0 0 rgba(212,168,83,0);
  --knot-glow-peak: 0 0 28px 6px rgba(212,168,83,0.38);
  --knot-glow-rest: 0 0 8px 2px rgba(212,168,83,0.15);
  --knot-glow-peak-half: 0 0 18px 4px rgba(212,168,83,0.20);
  --knot-glow-rest-half: 0 0 6px 2px rgba(212,168,83,0.10);
  /* PART-D76 W4: the RUNNING tier, one step below the arrival peak. */
  --knot-glow-running-peak: 0 0 22px 5px rgba(212,168,83,0.30);
  --knot-glow-running-rest: 0 0 7px 2px rgba(212,168,83,0.13);
}
/* PART-D76 W4: the RUNNING glow, a distinct and EARLIER event from the three arrival
   tiers below, which fire at landing; the two never collide for the same slot because
   genGlow's landing write simply overwrites data-glow. Per-theme calibration (Q10):
   light's running-peak alpha 0.30 sits proportionally closer to its own arrival-peak
   ceiling 0.38 than dark's 0.42 sits to its own 0.55, because a light-on-cream ground
   has no dark canvas to bloom into and needs firmer chroma to read as brighter at all.
   THE OTHER HALF OF MERGE GATE B2 IS IN JS: this selector cannot reach the loader's
   own data-knot without :has(), so genActiveLoader's give-up branch removes the
   attribute explicitly on the same repaint that freezes the knot to "still". */
.epf-face[data-glow="running"] { animation: knotGlowRunning 2220ms var(--ease-refined); }
@keyframes knotGlowRunning {
  0%    { box-shadow: var(--face-shadow-base), var(--knot-glow-zero); }
  5.4%  { box-shadow: var(--face-shadow-base), var(--knot-glow-running-peak); }
  46%   { box-shadow: var(--face-shadow-base), var(--knot-glow-running-rest); }
  100%  { box-shadow: var(--face-shadow-base), var(--knot-glow-zero); }
}
.epf-face[data-glow="first"] { animation: knotGlowFull 2220ms var(--ease-refined); }
.epf-face[data-glow="half"]  { animation: knotGlowHalf 2220ms var(--ease-refined); }
.epf-face[data-glow="peak"]  { animation: knotGlowPeak 4020ms var(--ease-refined); }
@keyframes knotGlowFull {
  0%    { box-shadow: var(--face-shadow-base), var(--knot-glow-zero); }
  5.4%  { box-shadow: var(--face-shadow-base), var(--knot-glow-peak); }
  46%   { box-shadow: var(--face-shadow-base), var(--knot-glow-rest); }
  100%  { box-shadow: var(--face-shadow-base), var(--knot-glow-zero); }
}
@keyframes knotGlowHalf {
  0%    { box-shadow: var(--face-shadow-base), var(--knot-glow-zero); }
  5.4%  { box-shadow: var(--face-shadow-base), var(--knot-glow-peak-half); }
  46%   { box-shadow: var(--face-shadow-base), var(--knot-glow-rest-half); }
  100%  { box-shadow: var(--face-shadow-base), var(--knot-glow-zero); }
}
@keyframes knotGlowPeak {
  0%    { box-shadow: var(--face-shadow-base), var(--knot-glow-zero); }
  3%    { box-shadow: var(--face-shadow-base), var(--knot-glow-peak); }
  25%   { box-shadow: var(--face-shadow-base), var(--knot-glow-rest); }
  100%  { box-shadow: var(--face-shadow-base), var(--knot-glow-zero); }
}

/* HEARTBEAT: one restrained asymmetric two-beat pulse per landing on the revealed
   tile. Never repeating; rests at scale 1 so reduced motion is still. */
.epf-face[data-beat="1"] img { animation: knotHeartbeat 430ms var(--ease-refined); }
@keyframes knotHeartbeat {
  0%   { transform: scale(1); }
  21%  { transform: scale(1.045); }
  44%  { transform: scale(1); }
  60%  { transform: scale(1); }
  79%  { transform: scale(1.03); }
  100% { transform: scale(1); }
}

/* THE N-OF-4 PROGRESS, in flow between the pair and the controls. Shown by
   applyGenState only while a slot is still active; a finished card carries none.
   The only number is the N-of-4 integer. */
.epf-progress {
  display: flex; flex-direction: column; align-items: center; gap: 0.55rem;
  margin: 1.2rem auto 0; max-width: 15rem;
}
.epf-segbar { width: 100%; }
.epf-count {
  font-family: 'DM Sans', system-ui, sans-serif; font-weight: 600; font-size: var(--fs-label);
  letter-spacing: var(--ls-label); text-transform: uppercase; color: var(--fg2);
}

/* THE FLARE: fires once, on the completing landing. In flow between the pair and
   the controls (inserted there by JS), pushing the toggle down by its own height,
   so it can NEVER overlay a photograph by construction. pointer-events none. Gold
   gradient text via background-clip. Base opacity 1 so under reduced motion it is
   read for its full JS-timed life; the animation adds the fade for others, no
   forwards fill. */
.knot-flare {
  margin: 1rem auto 0.2rem; max-width: 40rem;
  font-family: 'DM Sans', system-ui, sans-serif; font-style: italic;   /* PART-D96 (2026-09-15) SYE-15 (Q2) */
  font-size: var(--fs-title); text-align: center; line-height: 1.3;
  pointer-events: none;
  background: linear-gradient(120deg, var(--gold), var(--gold2) 55%, var(--gold3));
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
  opacity: 1; animation: knotFlare 3000ms var(--ease-refined);
}
@keyframes knotFlare {
  0%      { opacity: 0; transform: translateY(4px); }
  13.33%  { opacity: 1; transform: translateY(0); }   /* 400ms in */
  73.33%  { opacity: 1; transform: translateY(0); }   /* 1800ms hold, to 2200ms */
  100%    { opacity: 0; transform: translateY(-2px); }  /* 800ms out */
}

/* =====================================================================
   §5 · "NICELY DONE" AS AN OVERLAY (not a page swap) + gated shimmering
   finish control.
   ===================================================================== */
/* 5.1: #batchPanel is a fixed scrim that dims the rating screen ~88%; the copy sits in a
   centred .batch-card. Shown via display:flex (JS), hidden via display:none.
   Specificity note: demo.css pins every #stage-rate child to position:relative; z-index:1
   (the freeze-safe layer, at (1,1,0)). This must beat it, so it is written with the id + class
   and lives later in the file — otherwise the scrim lays out in-flow instead of fixed. */
#stage-rate > #batchPanel.batch-panel {
  position: fixed; inset: 0; z-index: 1100;
  margin: 0; max-width: none;
  align-items: center; justify-content: center;
  padding: clamp(1rem, 5vw, 2rem);
  background: rgba(18, 9, 10, 0.9);                                   /* fallback */
  background: color-mix(in srgb, var(--bg) 88%, transparent);        /* dims the screen behind ~88% */
  -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
  overflow-y: auto;
  animation: batchScrimIn 0.32s var(--ease-cinematic);
}
@keyframes batchScrimIn { from { opacity: 0; } to { opacity: 1; } }
.batch-card {
  width: 100%; max-width: 520px; margin: auto; text-align: center;
  background: var(--card); border: 1px solid var(--border2); border-radius: 18px;
  padding: clamp(1.6rem, 5vw, 2.6rem);
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.5), 0 0 0 1px var(--border);
  animation: batchCardIn 0.44s var(--ease-cinematic);
}
:root[data-theme="light"] .batch-card { box-shadow: 0 30px 80px rgba(92, 26, 27, 0.16), 0 0 0 1px var(--border); }
@keyframes batchCardIn { from { opacity: 0; transform: translateY(18px) scale(0.98); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  .batch-panel, .batch-card { animation: none; }
}

/* 5.5: shimmer on the finish control, stepped redder + faster every 25 rated beyond the first
   25 (level 0→4). Same sweeping-sheen idiom as the terminal CTA; capped at wine. */
#endBtn.endshimmer { position: relative; overflow: hidden; }
#endBtn.endshimmer::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(115deg, transparent 38%, var(--endsheen, rgba(240,200,110,0.5)) 50%, transparent 62%);
  transform: translateX(-130%);
  animation: endSheen var(--endsheen-dur, 4.6s) var(--ease-cinematic) 0.6s infinite;
}
@keyframes endSheen { 0% { transform: translateX(-130%); } 32%, 100% { transform: translateX(130%); } }
#endBtn.endshimmer[data-level="0"] { --endsheen: rgba(240,200,110,0.50); --endsheen-dur: 4.6s; border-color: var(--border3); color: var(--gold-text); }
#endBtn.endshimmer[data-level="1"] { --endsheen: rgba(233,168,118,0.58); --endsheen-dur: 3.9s; border-color: rgba(233,168,118,0.5); color: var(--gold-text); }
#endBtn.endshimmer[data-level="2"] { --endsheen: rgba(223,128,108,0.64); --endsheen-dur: 3.3s; border-color: rgba(223,128,108,0.55); }
#endBtn.endshimmer[data-level="3"] { --endsheen: rgba(202,92,92,0.70);  --endsheen-dur: 2.8s; border-color: rgba(202,92,92,0.6); color: #E9B7B2; }
#endBtn.endshimmer[data-level="4"] { --endsheen: rgba(154,61,72,0.82);  --endsheen-dur: 2.3s; border-color: var(--wine2); color: #E9B7B2; }
@media (prefers-reduced-motion: reduce) {
  #endBtn.endshimmer::after { display: none; }
}

/* =====================================================================
   BLANK-CONTENT SAFETY FLOOR (pairs with the transform-only `rise` above)
   The always-on-load stages must never rely on an animation to become
   visible. Pin their content opaque so a stalled entrance can only ever
   leave it slightly offset, never invisible.
   ===================================================================== */
#stage-hero.active > *:not(.ambient-canvas):not(.hero-halo),
#stage-entry.active .panel > h2,
#stage-entry.active .panel > .entry-sub,
#stage-entry.active #entryForm > *,
#stage-entry .ln-cover-inner,
#stage-entry .ln-cover-inner > * {
  opacity: 1;
}

/* =====================================================================
   HARD VISIBILITY FLOOR for the stage system.
   An !important declaration beats a running animation AND beats any
   non-!important rule an extension injects, so the active stage cannot be
   left invisible by a stalled entrance or by injected CSS. Entrances are
   transform-only above, so nothing here removes a visual effect.
   ===================================================================== */
section.stage.active { opacity: 1 !important; visibility: visible !important; }
section.stage.active > *:not(.ambient-canvas) { visibility: visible !important; }

/* =====================================================================
   ENTRY REVEAL — after "Read my eye", the three info cards and then the
   questions rise gently into place as they scroll up over the cover,
   instead of arriving fully formed and static.

   The old rules at the top of this file animated these on STAGE
   ACTIVATION, which nobody ever saw: the cover fills the viewport, so the
   cards and the form are far below the fold and had finished animating
   long before they were scrolled to. This is scroll-triggered instead, so
   the motion happens while you are looking at it.

   FREEZE-SAFE, the same rule the stage entrances now follow: the starting
   offset is added by JS at runtime (no JS → no offset), it is TRANSFORM
   ONLY so nothing is ever transparent, and overhaul.js releases everything
   after 3s regardless. Worst case a block rests 30px low; it can never be
   invisible or stranded.
   ===================================================================== */
@media (prefers-reduced-motion: no-preference) {
  .ln-reveal {
    transform: translateY(30px);
    transition: transform 0.9s var(--ease-heavy);
    transition-delay: calc(var(--rv, 0) * 90ms);
    animation: none !important;   /* supersedes the unseen stage-activation rise */
  }
  .ln-reveal.in-view { transform: none; }
}

/* Retire the legacy stage-activation rise on the entry content. It is superseded by the
   scroll reveal above, and it was never actually seen: it plays out below the fold while
   the cover fills the viewport. Leaving it also stranded reduced-motion users 18px low —
   the global reduce clamp shortens animation-duration but NOT animation-delay, so with
   fill-mode:both the element sits at its from-frame instead of settling. */
#stage-entry.active .panel > h2,
#stage-entry.active .panel > .entry-sub,
#stage-entry.active #entryForm > * { animation: none !important; }

/* =====================================================================
   NEW 2026-08-24 · "WHERE YOU AND THE CROWD PARTED" CARD DECK
   Replaces the two horizontal .diverge-row strips with one stacked deck.
   Card surface is --face-frame (#F3DED7), the same cream mat that frames the
   generated portraits higher up the page; that token is deliberately theme
   invariant ("the object's own colour"), so the deck needs no dark-mode branch.
   ===================================================================== */
/* The deck's own surface colour. Deliberately its OWN token rather than reusing
   --face-frame: the lead asked for the cards to change colour while everything else
   stays put, and --face-frame also paints the mat around the generated portraits
   higher up the page -- redefining it would have silently restyled those too. */
:root { --dv-card: #E8D4CA; --dv-card-ink: #2A1416; }
.dv-deck { margin: 1.2rem auto 0.4rem; max-width: 26rem; }
.dv-stage {
  position: relative;
  width: 100%; aspect-ratio: 5 / 6;
  /* pan-y is load-bearing: the deck swipes horizontally inside a vertically
     scrolling page, so the browser must keep vertical panning for the page and
     hand only horizontal drags to the pointer handlers. */
  touch-action: pan-y;
  -webkit-user-select: none; user-select: none;
}
.dv-stage:focus-visible { outline: 2px solid var(--gold2); outline-offset: 6px; border-radius: 14px; }
.dv-card {
  position: absolute; inset: 0; margin: 0;
  display: flex; flex-direction: column;
  background: var(--dv-card); color: var(--dv-card-ink);
  border-radius: 12px; overflow: hidden;
  box-shadow: 0 14px 34px rgba(0,0,0,0.34), 0 0 0 1px rgba(0,0,0,0.06);
  /* --d is the depth the JS sets: 0 = front, rising toward the back. Each step
     back sinks the card a little and shrinks it, which is what reads as a stack. */
  transform: translateY(calc(var(--d, 0) * 14px)) scale(calc(1 - var(--d, 0) * 0.05));
  opacity: 1;
  transition: transform 0.42s var(--ease-heavy), opacity 0.42s var(--ease-refined);
  will-change: transform;
}
/* only the top few are worth painting; the rest fade out so the stack has a floor */
.dv-card { visibility: visible; }
.dv-deck .dv-card[style*="--d: 4"], .dv-deck .dv-card[style*="--d: 5"],
.dv-deck .dv-card[style*="--d: 6"], .dv-deck .dv-card[style*="--d: 7"],
.dv-deck .dv-card[style*="--d: 8"], .dv-deck .dv-card[style*="--d: 9"] { opacity: 0; }
.dv-card.is-front { cursor: grab; }
.dv-card.is-front:active { cursor: grabbing; }
/* the recede: the outgoing card shrinks and dims IN PLACE while still on top, so it
   reads as dropping back through the stack rather than sliding off to one side */
.dv-card.is-receding { transform: translateY(-6px) scale(0.86); opacity: 0.25; }

.dv-shot { flex: 1 1 auto; min-height: 0; background: var(--dv-card); }
.dv-shot img { width: 100%; height: 100%; object-fit: cover; display: block; }
.dv-foot {
  flex: 0 0 auto; padding: 0.7rem 0.9rem 0.8rem;
  display: flex; flex-direction: column; gap: 0.28rem;
  background: var(--dv-card);
  border-top: 1px solid rgba(42,20,22,0.12);
}
/* the group line: which direction this particular face parted in */
.dv-label {
  font-family: 'DM Sans', system-ui, sans-serif; font-size: var(--fs-label); line-height: 1.35;
  color: var(--dv-card-ink);   /* PART-D96 SYE-3: was the ink at 72% alpha (5.74:1); solid, no alpha on text (Q8) */
}
/* the numbers, in the same mono the rest of the readout uses for figures */
.dv-nums {
  font-family: 'DM Sans', system-ui, sans-serif; font-weight: 600; font-size: var(--fs-label);
  letter-spacing: var(--ls-label); text-transform: uppercase; color: #7A2F35;
}

.dv-controls {
  display: flex; align-items: center; justify-content: center; gap: 1rem;
  margin-top: 0.9rem;
}
/* same circular chevron the Unwrap reel uses, so the two carousels share one idiom */
.dv-nav {
  width: 38px; height: 38px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--surface); border: 1px solid var(--border2); color: var(--gold-text);
  cursor: pointer;
  transition: border-color 0.2s var(--ease-refined), color 0.2s var(--ease-refined), transform 0.2s var(--ease-overshoot);
}
.dv-nav svg { width: 17px; height: 17px; display: block; }
.dv-nav:hover { border-color: var(--gold2); color: var(--gold-text); transform: scale(1.07); }
.dv-nav:active { transform: scale(0.93); }
.dv-nav:focus-visible { outline: 2px solid var(--gold2); outline-offset: 3px; color: var(--gold-text); }
.dv-count {
  font-family: 'DM Sans', system-ui, sans-serif; font-weight: 600; font-size: var(--fs-label);
  letter-spacing: var(--ls-label); text-transform: uppercase; color: var(--fg3);
  min-width: 5.5em; text-align: center;
}

@media (max-width: 640px) {
  .dv-deck { max-width: 20rem; }
  .dv-nav { width: 34px; height: 34px; }
}
@media (prefers-reduced-motion: reduce) {
  /* no recede and no drag-follow; the deck simply cuts to the next card */
  .dv-card { transition: none; }
  .dv-card.is-receding { transform: none; opacity: 1; }
  .dv-nav { transition: none; }
}

/* NEW 2026-08-24 · the eye-print swatches are real buttons that repaint the share card.
   Colour alone never carries the selected state -- the chosen one also gains a ring. */
.ep-glyph-swatches { display: flex; gap: 0.5rem; justify-content: center; }
.ep-sw {
  width: 26px; height: 26px; border-radius: 6px; padding: 0; cursor: pointer;
  background: var(--sw); border: 1px solid rgba(0,0,0,0.25);
  transition: transform 0.16s var(--ease-overshoot), box-shadow 0.16s var(--ease-refined);
}
.ep-sw:hover:not(:disabled) { transform: translateY(-2px); }
.ep-sw.is-on { box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--gold); }
.ep-sw:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; }
.ep-sw:disabled { cursor: default; opacity: 0.55; }
@media (prefers-reduced-motion: reduce) { .ep-sw { transition: none; } }

/* The generation wiring's states, re-applied 2026-08-29. */
.epf-veil {
  position: absolute; inset: 0; display: none;
  align-items: center; justify-content: center;
  background: var(--face-frame-bg); color: var(--fg3);
  font-family: 'DM Sans', system-ui, sans-serif; font-weight: 600; font-size: var(--fs-label);
  letter-spacing: var(--ls-label); text-transform: uppercase;
}
.epf-face.epf-blank img { visibility: hidden; }
.epf-face.epf-blank .epf-veil { display: flex; }
.epf-note {
  font-family: 'DM Sans', system-ui, sans-serif; font-size: var(--fs-label);
  color: var(--fg2); margin-top: 1.1rem; line-height: 1.5;
}
.epf-note:empty { display: none; }
.epf-reagree { margin-top: 0.6rem; }

/* ============ PART-D88 (2026-09-11) - THE FINER-DETAIL MAGNITUDE LOLLIPOP ============
   Ledger item 4's visual half, built to the filed candidate sheet of 2026-09-11 as amended
   by its dispositions, and to Avery's cap ruling of the same date.

   APPENDED RULES ONLY. Nothing above this line is edited: .dd-nums, .dd-k and .dd-v keep
   every declaration they shipped with (:443-449), and the visually-hidden text reuses the
   EXISTING .sr-only utility (:61) rather than declaring a second one. js/overhaul.js's
   finerMagnitudeVisuals() adds .dd-mag to the .dd-v cell it fills and sets --i inline.

   The colours are var() references, which is also the only way they can be written: a CSS
   custom property does not substitute inside an SVG presentation attribute, so stroke and
   fill belong here in the stylesheet rather than on the elements. */
.dd-nums li .dd-mag {
  flex-shrink: 0;          /* W4: a long .dd-k label never squeezes the track */
  display: inline-flex; align-items: center; justify-content: flex-end;
}
.dd-nums li .dd-mag .dd-mag-svg { display: block; width: 120px; height: 20px; }
/* the track takes the colour of the row's own dashed divider (:446), the stem takes .dd-k's
   (:448) and the dot takes .dd-v's (:449), so the drawing is made of the colours the row
   already had. W5: no colour carries value, and no dot reaches the track's end. */
.dd-nums li .dd-mag .dd-mag-track { stroke: var(--border); stroke-width: 1; }
.dd-nums li .dd-mag .dd-mag-stem { stroke: var(--fg2); stroke-width: 1.5; stroke-linecap: round; }
.dd-nums li .dd-mag .dd-mag-dot { fill: var(--fg); }

/* W4, narrow viewports: the rendered width drops and the label takes the room it needs,
   wrapping onto a second line rather than crushing the track. The viewBox is unchanged, so
   every row rescales together and the proportions hold. 480px is this stylesheet's own
   narrow breakpoint (:281, :895, :1410); checked at the longest real pole label,
   "more classically average features" (js/science.js:176), on a 375px viewport. */
@media (max-width: 480px) {
  .dd-nums li .dd-mag .dd-mag-svg { width: 84px; }
}

/* W3, the entrance. The mechanism is modelled on the LIVE .ro-reveal / roCardIn pair
   (:695-697), which is the one that actually renders: demo.css defines roCardIn twice
   (:466-467 and again at :695-697) and the later definition wins by cascade order. This
   block takes its OWN keyframe name, so neither definition is touched or collided with.
   Opacity and transform only, auto-playing on mount, per-row stagger off the inline --i.

   THE OPACITY: 0 EXISTS ONLY INSIDE THIS KEYFRAME, AND THE KEYFRAME IS ONLY EVER APPLIED
   INSIDE THE NO-PREFERENCE QUERY. A reduced-motion reader therefore has no hidden state to
   recover from at all; the reduce block below states the same thing a second time. */
@keyframes d88MagIn { from { opacity: 0; transform: translateX(-6px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: no-preference) {
  .dd-nums li .dd-mag {
    animation: d88MagIn 0.42s var(--ease-cinematic) both;
    animation-delay: calc(var(--i, 0) * 45ms);
  }
}
@media (prefers-reduced-motion: reduce) {
  .dd-nums li .dd-mag { animation: none; opacity: 1; transform: none; }
}
/* PART-D89 (2026-09-11) the Folio II consensus bullet graph (Card 1, "You and the shared
   consensus"). APPENDED ONLY: no rule above this line is touched.

   Every colour that carries the ENCODING is inline in the SVG itself (js/overhaul.js,
   bulletSVG): the bands are var(--fg) at three named ascending tints, the nought rule and
   the ticks share a fourth, and the value bar and the marker are var(--gold). So the figure
   is already correct with nothing below this line applied. What lives here is TYPOGRAPHY,
   classed the way styles/folio.css classes the three sibling builders, and THE ENTRANCE.

   FREEZE SAFETY. folio.css:395-398 records the reasoning for the scroll-driven charts; this
   figure reaches the same property by the on-mount route that demo.css:695-697
   already ships for .ro-reveal. The zero states, scaleX(0) and opacity 0, live ONLY inside
   @media (prefers-reduced-motion: no-preference), so a reduced-motion frame, a frozen frame
   and any engine that does not honour the query all render the COMPLETE figure at full width
   and full opacity. There is no feature gate that could fail shut, because there is no
   feature to gate: no scroll timeline is used here and none is wanted. */
.fig-bullet .bl-bandlab {
  font-size: var(--fs-label); fill: var(--fg2); text-anchor: middle;
  letter-spacing: 0.14em; text-transform: uppercase;
}
.fig-bullet .bl-ticklab { font-size: var(--fs-label); fill: var(--fg3); text-anchor: middle; }
.fig-bullet .bl-read { font-size: var(--fs-label); fill: var(--gold-text); text-anchor: middle; }
@media (prefers-reduced-motion: no-preference) {
  /* the bar grows out of nought, in whichever direction the reading lies: its origin is set
     inline per sign, so a part-played frame never shows the bar starting anywhere but nought */
  .fig-bullet:not(.is-static) .bl-value {
    transform: scaleX(0);
    animation: blValueGrow 0.62s var(--ease-luxury) 0.06s both;
  }
  /* The marker and its figure arrive once the bar has SETTLED, so the value is never printed
     beside a bar still on its way to it. The delay is 0.70s against the bar's 0.06s + 0.62s,
     so the two windows do not overlap at all: an earlier 0.52s left a 160ms overlap, which
     the craft pass measured and which made this comment untrue. */
  .fig-bullet:not(.is-static) .bl-marker,
  .fig-bullet:not(.is-static) .bl-read {
    opacity: 0;
    animation: blMarkerIn 0.42s var(--ease-luxury) 0.70s both;
  }
}
@keyframes blValueGrow { to { transform: scaleX(1); } }
@keyframes blMarkerIn { to { opacity: 1; } }

/* PART-D92, 2026-09-12. Folio V starts in the ordinary readout before the
   folio enhancer groups it. Every selector is chapter-owned, and every data
   mark is complete without motion. */
.finer-family-card { position: relative; overflow: visible; }
.finer-family-card.ro-reveal {
  animation: none !important;
  opacity: 1 !important;
  transform: none !important;
}
.finer-control { display: grid; gap: 0.45rem; max-width: 34rem; margin: 1.15rem 0 1.5rem; }
.finer-control label {
  color: var(--fg2);
  font-family: 'DM Sans', system-ui, sans-serif;
  font-weight: 600;
  font-size: var(--fs-body);
  letter-spacing: normal;
}
.finer-control select {
  width: 100%;
  min-height: 44px;
  padding: 0.7rem 2.5rem 0.7rem 0.8rem;
  border: 1px solid var(--border3);
  border-radius: 4px;
  background: var(--bg2);
  color: var(--fg);
  font: inherit;
}
.finer-control select:focus-visible,
.finer-detail summary:focus-visible {
  outline: 2px solid var(--gold2);
  outline-offset: 3px;
}
.finer-control-note { color: var(--fg2); font-size: var(--fs-label); margin: -0.85rem 0 1.25rem; max-width: 56ch; }
.finer-figure {
  position: relative;
  overflow: hidden;
  margin: 0;
  padding: clamp(1rem, 2.8vw, 1.7rem);
  border: 1px solid var(--border2);
  background: linear-gradient(145deg, var(--surface2), transparent 62%);
}
.finer-axis-title {
  color: var(--gold-text);
  font-family: 'DM Sans', system-ui, sans-serif;   /* PART-D96 (2026-09-15) SYE-15 (Q2): a figcaption, not a heading */
  font-size: var(--fs-title);
  line-height: 1.2;
  margin-bottom: 0.65rem;
}
.finer-reading-guide {
  color: var(--fg2);
  font-size: var(--fs-body);
  line-height: 1.6;
  margin: 0 0 1.15rem;
  max-width: 72ch;
}
.finer-columns,
.finer-score-row {
  display: grid;
  grid-template-columns: 3.2rem minmax(0, 1fr) 3.6rem;
  gap: 0.65rem;
  align-items: center;
}
.finer-columns {
  color: var(--fg2);
  font-family: 'DM Sans', system-ui, sans-serif;
  font-weight: 600;
  font-size: var(--fs-label);
  letter-spacing: normal;
  margin-bottom: 0.25rem;
}
.finer-columns span:last-child { text-align: right; }
.finer-score-row { min-height: 46px; border-top: 1px solid var(--border); }
.finer-score,
.finer-face-count {
  color: var(--fg2);
  font-family: 'DM Sans', system-ui, sans-serif;
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-label);
}
.finer-face-count { text-align: right; }
.finer-plot { display: flex; align-items: center; min-width: 0; min-height: 38px; }
.finer-plot svg {
  display: block;
  width: 100%;
  max-width: 100%;
  min-width: 0;
  height: auto;
  overflow: hidden;
}
.finer-rule,
.finer-zero,
.finer-tail-cap { fill: none; stroke: var(--border3); stroke-width: 1.25; }
.finer-zero { stroke-dasharray: 3 4; }
.finer-middle-half { fill: none; stroke: var(--gold3); stroke-width: 2; }
.finer-median-bin { fill: var(--gold3); }
.finer-sparse-bin { fill: var(--gold3); fill-opacity: 0.28; stroke: var(--gold3); stroke-width: 1; }
.finer-surface-bin { fill: transparent; stroke: var(--border3); stroke-width: 1; }
.finer-surface-bin.is-used {
  fill: var(--gold3);
  fill-opacity: calc(0.16 + var(--finer-share, 0) * 0.64);
  stroke: var(--gold3);
}
.finer-surface-count {
  fill: var(--fg);
  stroke: var(--bg);
  stroke-width: 3px;
  paint-order: stroke;
  text-anchor: middle;
  dominant-baseline: middle;
  font: 600 var(--fs-label) 'DM Sans', system-ui, sans-serif;
}
.finer-empty-row { color: var(--fg2); font-size: var(--fs-label); line-height: 1.25; }
.finer-axis-scale {
  display: flex;
  justify-content: space-between;
  margin: 0.45rem 4.2rem 0 3.85rem;
  color: var(--fg3);
  font-family: 'DM Sans', system-ui, sans-serif;
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-label);
}
.finer-axis-scale span,
.finer-endpoints span { min-width: 0; overflow-wrap: anywhere; }
.finer-endpoints {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  margin: 0.6rem 4.2rem 0 3.85rem;
  color: var(--fg2);
  font-size: var(--fs-label);
}
.finer-endpoints span:last-child { text-align: right; }
.finer-support { color: var(--fg); font-size: var(--fs-body); line-height: 1.6; margin: 1rem 0 0.35rem; }
.finer-reading-note { color: var(--fg2); font-size: var(--fs-body); line-height: 1.6; margin: 0 0 0.7rem; }
.finer-detail { margin-top: var(--gap-block); overflow-x: auto; }
.finer-detail summary {
  cursor: pointer;
  list-style: none;
  display: inline-block;
  font-family: 'DM Sans', system-ui, sans-serif;
  font-weight: 600;
  font-size: var(--fs-label);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--fg);
  border: 1px solid var(--border2);
  border-radius: 999px;
  padding: 4px 12px;
}
.finer-detail summary::-webkit-details-marker { display: none; }
.finer-detail[open] summary { background: var(--gold-dim); margin-bottom: var(--space-1); }
/* PART-D96 (2026-09-15) SYE-22 (C9): Folio V drawer text at the readout body size; its table sits on --fs-label. */
.finer-detail > p { color: var(--fg); font-size: var(--fs-body); line-height: 1.6; max-width: 72ch; }
.finer-detail-table { border-collapse: collapse; width: 100%; min-width: 42rem; margin: 1rem 0 1.5rem; font-size: var(--fs-label); }
.finer-detail-table caption { color: var(--fg); text-align: left; font-weight: 600; margin-bottom: 0.45rem; }
.finer-detail-table th,
.finer-detail-table td { padding: 0.5rem 0.4rem; border-bottom: 1px solid var(--border2); text-align: left; }
.finer-detail-table th { color: var(--fg2); font-weight: 500; }
.finer-accent { pointer-events: none; opacity: 0; transform-origin: left center; transform-box: fill-box; }
.finer-rule-accent { stroke: var(--gold2); stroke-width: 2.2; }
.finer-surface-accent {
  position: absolute;
  inset: 0 0 auto;
  height: 2px;
  background: var(--gold2);
}
.finer-hair-accent {
  position: absolute;
  right: 0;
  width: clamp(2.5rem, 8vw, 5rem);
  height: 1px;
  background: var(--gold2);
}
.finer-hair-accent-a { top: 8.5rem; }
.finer-hair-accent-b { top: 10.2rem; width: clamp(1.5rem, 5vw, 3rem); }
@media (max-width: 480px) {
  .finer-figure { padding: 0.9rem 0.75rem 1rem; }
  .finer-columns { grid-template-columns: 1fr 1fr; gap: 0.35rem; }
  .finer-columns span:nth-child(2) { display: none; }
  .finer-score-row { grid-template-columns: 1fr 1fr; gap: 0.35rem; padding: 0.35rem 0; }
  .finer-score-row .finer-score { grid-column: 1; grid-row: 1; }
  .finer-score-row .finer-face-count { grid-column: 2; grid-row: 1; }
  .finer-score-row .finer-plot { grid-column: 1 / -1; grid-row: 2; }
  .finer-axis-scale,
  .finer-endpoints { margin-left: 0; margin-right: 0; }
  .finer-axis-scale { gap: 0.35rem; }
  .finer-axis-scale span { flex: 1 1 0; text-align: center; }
  .finer-axis-scale span:first-child { text-align: left; }
  .finer-axis-scale span:last-child { text-align: right; }
  .finer-endpoints { gap: 0.6rem; }
}
@media (prefers-reduced-motion: reduce) {
  .finer-accent { display: none !important; animation: none !important; }
}
/* PART-D93, 2026-09-12. Complete illustrations and numbers are visible before
   any decorative motion. Native text sizes never shrink with an SVG viewBox. */
.finer-toolbar { margin: 0 0 2.6rem; max-width: 42rem; }
.finer-mode-control { display: flex; flex-wrap: wrap; gap: .35rem; padding: .35rem; border: 1px solid var(--border3); border-radius: 28px; width: fit-content; background: var(--bg2); }
.finer-mode-control button, .finer-score-control button, .finer-replay { min-height: 44px; border: 1px solid transparent; color: var(--fg); background: transparent; cursor: pointer; font: inherit; }
.finer-mode-control button { padding: .6rem 1.2rem; border-radius: 24px; font-size: var(--fs-label); }
.finer-mode-control button[aria-pressed="true"] { background: var(--gold2); color: var(--bg); }
.finer-score-control { margin-top: 1.25rem; }
.finer-score-control[hidden] { display: none; }
.finer-score-control > span { display: block; font-weight: 600; font-size: var(--fs-body); color: var(--fg2); }
.finer-score-control > div { display: flex; gap: .35rem; margin: .7rem 0; }
.finer-score-control button { width: 44px; border-radius: 50%; border-color: var(--border3); }
.finer-score-control button[aria-pressed="true"] { border-color: var(--gold2); box-shadow: inset 0 0 0 1px var(--gold2); color: var(--gold-text); background: var(--gold-dim); }
.finer-toolbar button:focus-visible, .finer-replay:focus-visible { outline: 2px solid var(--gold2); outline-offset: 3px; }
.finer-illustrated { border: 0; background: none; padding: 0; overflow: visible; }
.finer-feature-description { font-size: var(--fs-body); color: var(--fg2); max-width: 58ch; margin: .6rem 0; }
.finer-illustrated .finer-reading-guide { font-size: var(--fs-body); color: var(--fg); margin: 1.3rem 0; }
.finer-groups { display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); gap: clamp(.7rem,2vw,1.5rem); padding: 1rem 0 1.6rem; border-bottom: 1px solid var(--border2); }
.finer-group { text-align: center; min-width: 0; }
.finer-illustration { display: block; width: 100%; max-width: 236px; height: auto; margin: 0 auto .7rem; overflow: visible; }
.finer-illustration path, .finer-illustration ellipse { fill: none; stroke-linecap: round; stroke-linejoin: round; }
.finer-drawing-ink { stroke: var(--fg); stroke-width: 1.5; opacity: .8; }
.finer-drawing-faint { stroke: var(--fg2); stroke-width: 1; opacity: .6; }
.finer-drawing-gold { stroke: var(--gold3); stroke-width: 1.3; }
.finer-drawing-pupil { fill: var(--fg); }
.finer-drawing-light { fill: var(--bg); }
.finer-hair-strand { stroke-width: 3; }
.finer-hair-tone-0 { stroke: #4e352a; }
.finer-hair-tone-1 { stroke: #a07043; }
.finer-hair-tone-2 { stroke: #d5b77f; }
:root:not([data-theme="light"]) .finer-hair-tone-0 { stroke: #917366; }
.finer-channel-track { fill: var(--border3); stroke: var(--gold3); stroke-width: .6; }
.finer-feature-trace { stroke: var(--gold); stroke-width: 2.6; stroke-dasharray: 1; stroke-dashoffset: 0; opacity: 0; }
.finer-group h4 { font: 500 var(--fs-label)/1.35 'DM Sans', sans-serif; color: var(--fg); margin: .3rem 0 .8rem; min-height: 2.7em; }
.finer-group-value { font: 500 clamp(2.1rem,5vw,3.5rem)/1 'DM Sans',system-ui,sans-serif; color: var(--gold-text); margin: 0; font-variant-numeric: tabular-nums; }
.finer-value-unit { font: 400 var(--fs-label)/1.3 'DM Sans',sans-serif; color: var(--fg2); }
.finer-empty { font: 400 var(--fs-body)/1.6 'DM Sans',sans-serif; color: var(--fg2); }
.finer-group-count { font-size: var(--fs-label); line-height: 1.4; color: var(--fg2); margin: .55rem 0 0; }
.finer-illustration-note, .finer-support { color: var(--fg2); font-size: var(--fs-body); line-height: 1.6; }
.finer-illustration-note { margin: 1rem 0 .3rem; }
.finer-replay { color: var(--gold-text); font-size: var(--fs-label); padding: 0; text-decoration: underline; text-underline-offset: 4px; }
.finer-unavailable { padding: .8rem 1rem; border-left: 2px solid var(--gold2); }
.consensus-reading { margin: 1.5rem 0; padding: 1.5rem 0 .4rem; }
.consensus-directions, .consensus-ticks { display: flex; justify-content: space-between; font-size: var(--fs-label); color: var(--fg2); }
.consensus-track { height: 4px; position: relative; background: var(--border3); margin: 1.35rem 8px .9rem; }
.consensus-zero { position: absolute; left: 50%; top: -7px; height: 18px; border-left: 1px solid var(--fg2); }
.consensus-marker { position: absolute; top: 50%; transform: translate(-50%,-50%); width: 15px; height: 15px; border-radius: 50%; background: var(--gold2); box-shadow: 0 0 0 5px var(--bg); }
.consensus-value { text-align: center; color: var(--fg2); margin: 1.6rem 0 .6rem; font-size: var(--fs-body); }
.consensus-value strong { color: var(--gold-text); font: 500 var(--fs-h2)/1 'DM Sans',system-ui,sans-serif; margin-left: .45rem; }
.consensus-zero-note { color: var(--fg2); text-align: center; font-size: var(--fs-label); }
/* The state adapter never paints. Only the phase outside .epf-inner does. */
.epf-face .knot-phase { display: none !important; }
.epf-phase-label { position: absolute; left: 7%; right: 7%; top: calc(64.5% + .7rem); z-index: 3; text-align: center; pointer-events: none; color: var(--gold-text); font: 600 var(--fs-label)/1.3 'DM Sans',system-ui,sans-serif; text-transform: uppercase; letter-spacing: var(--ls-label); }
.epf-phase-label[hidden], .epf-phase-flipping .epf-phase-label { display: none; }
@media (max-width: 480px) {
  .finer-groups { gap: .6rem; }
  .finer-score-control > div { gap: 2px; }
  .finer-score-control button { width: calc((100% - 12px)/7); min-width: 32px; }
  .finer-mode-control button { padding: .6rem .85rem; }
  .finer-group-value { font-size: 2.3rem; }
}
@media (prefers-reduced-motion: reduce) {
  .finer-replay { display: none; }
  .finer-feature-trace { opacity: 0 !important; }
}
/* PART-D93, 2026-09-12. Scoped contrast for new ordinary text and essential
   marks. Existing site tokens and the surrounding chapters stay unchanged. */
.folio-room, .folio-leaned, .folio-finer, .epf-phase-label {
  --d93-gold: #D4A853; --d93-crowd: #DE8A96;
}
:root[data-theme="light"] :is(.folio-room, .folio-leaned, .folio-finer, .epf-phase-label) {
  --d93-gold: #865C21; --d93-crowd: #9A3D48;
}
.finer-mode-control button[aria-pressed="true"] { background: var(--d93-gold); }
.finer-score-control button[aria-pressed="true"], .finer-group-value, .finer-replay,
.folio-room .ro-card .consensus-value strong, .epf-phase-label { color: var(--gold-text); }   /* PART-D96 SYE-3 (Q8) */
.consensus-marker { background: var(--d93-gold); }
.finer-drawing-gold { stroke: var(--d93-gold); }
.finer-score-control > div { flex-wrap: wrap; }
.finer-score-control button { flex: 0 0 44px; }
