/* ─────────────────────────────────────────────────────────────────────────
   Sŏn — the track
   The scrub-led build (docs/build-spec.md governing). Pass 1: eleven
   panels, grounds, all copy at its type steps, the three composition
   grammars. Motion beyond the ratified gates arrives in its own passes.
   ───────────────────────────────────────────────────────────────────────── */

@import url("../tokens/fonts.css");
@import url("../tokens/colors.css");
@import url("../tokens/typography.css");
@import url("../tokens/spacing.css");
@import url("../tokens/motion-track.css");
@import url("../tokens/seon.css");
@import url("../tokens/base.css");
@import url("../tokens/components.css");

html {
  /* The reading axis is the only axis. The architectural 선 crops sideways
     by stance and must never mint a horizontal scrollbar. */
  overflow-x: clip;
}

/* The document ground behind the panel stack matches the dinner opening
   and close, so rubber-band overscroll never flashes daylight over a dark
   frame (pass 1 fix, carried from the gate A verification note). Set as a
   direct ground, not a body-level theme: the base editorial panels must
   keep resolving against :root. */
body {
  /* Matches the dark-plum dinner ground so rubber-band overscroll never
     flashes a mismatched frame at the opening or the close. */
  background: var(--son-color-ground);
}

/* The path definition renders nothing and must occupy no flow space. */
.svg-defs { position: absolute; }

/* Every drawn-mark instance, carried from the settled component
   (site/site.css). */
.seon-mark {
  display: block;
  fill: currentColor;
  aspect-ratio: 847 / 930.26;
}

/* ── Type steps (tokens/typography.css, ratified R2) ──
   The eyebrow step is .son-eyebrow from tokens/base.css. Tracking sits on
   the element that carries the font-size; em tracking inherited from a
   smaller ancestor computes near zero. */
.t-solo {
  font-family: var(--son-font-display);
  font-weight: var(--son-weight-regular);
  font-size: var(--son-text-solo);
  line-height: var(--son-leading-solo);
  letter-spacing: var(--son-tracking-display);
}
.t-display {
  font-family: var(--son-font-display);
  font-weight: var(--son-weight-regular);
  font-size: var(--son-text-display);
  line-height: var(--son-leading-display);
  letter-spacing: var(--son-tracking-display);
}
.t-headline {
  font-family: var(--son-font-display);
  font-weight: var(--son-weight-regular);
  font-size: var(--son-text-headline);
  line-height: var(--son-leading-headline);
  letter-spacing: var(--son-tracking-headline);
  max-width: 24ch;
}
.t-subhead {
  font-family: var(--son-font-serif);
  font-weight: var(--son-weight-regular);
  font-size: var(--son-text-subhead);
  line-height: var(--son-leading-subhead);
  /* Subhead carries no tracking; without this the base h3 rule's headline
     tracking, computed at the heading's own size, is inherited in. */
  letter-spacing: var(--son-tracking-normal);
}
.t-lead {
  font-family: var(--son-font-body);
  font-weight: var(--son-weight-light);
  font-size: var(--son-text-lead);
  line-height: var(--son-leading-lead);
  max-width: var(--son-measure);
}
.t-body {
  font-family: var(--son-font-body);
  font-weight: var(--son-weight-body);
  font-size: var(--son-text-body);
  line-height: var(--son-leading-body);
  max-width: var(--son-measure);
}
.t-descriptor {
  font-family: var(--son-font-body);
  font-size: var(--son-text-menu);
  line-height: var(--son-leading-caption);
}
.t-fine {
  font-family: var(--son-font-fine);
  font-weight: var(--son-weight-light);
  font-size: var(--son-text-fine);
  line-height: var(--son-leading-fine);
}

/* ── Strophes ──
   Locked breaks are choreography units: one element per line, and the
   stack MUST be a flex column (tokens/typography.css, verification-caught;
   the clip-rise pads arrive with the entrances in pass 3). Bands switch
   where the type floors engage: hero ~800px, Solo ~580px. */
.strophe {
  display: flex;
  flex-direction: column;
}
.strophe .line { display: block; }
.strophe--hero-mobile { display: none; }
.strophe--solo-mobile { display: none; }

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ── The chrome: the revealed header (§2.4, Decision 7) ──
   Three elements, fixed. At the top, over P1: transparent, part of the
   hero frame. Hides on scroll down (the chrome-hide token, feedback
   register, carried from the settled header); returns on scroll up as a
   solid bar in the ACTIVE panel's ground and theme, set by track.js: the
   summoned object is solid in the world it is summoned into. Never
   suppressed during the pin. Focus into the chrome always reveals it, so
   A1 stays keyboard-reachable. Z-bands: layer 1, content 2, chrome 3. */
.chrome {
  /* The revealed header is the ENHANCEMENT (the inversion, scratch 3f):
     without JS the chrome is absolute and scrolls away with P1, the
     designed no-JS default; the fixed hide/return machinery exists only
     under html.js. Verification-caught: an ungated fixed stance floated
     Bone strings invisibly over the daylight chapter for the no-JS
     reader. */
  position: absolute;
  inset: 0 0 auto 0;
  z-index: 3;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding: var(--son-space-4) var(--son-margin-page);
  color: var(--son-text-primary);
  transition: transform var(--son-track-chrome-hide) var(--son-ease-standard);
}
.js .chrome { position: fixed; }
.js .chrome.is-away { background: var(--son-surface-primary); }
.js .chrome.is-away[data-ground="secondary"] { background: var(--son-surface-secondary); }
.chrome.is-hidden { transform: translateY(-110%); }
/* The mark never waits on the network: the arrival subset carries S, ŏ, n,
   the only glyphs the mark sets, and the metric fallback covers a
   woff2-less engine. The loading full faces are deliberately absent from
   this stack: a pending face ANYWHERE in a stack blocks the whole run's
   paint (probe-verified under stalled fonts), so the wordmark token's
   stack cannot deliver the instant mark and this scoped stack is the
   ratified §2.3 mechanism. Same face, same cut, delivery name only. */
.wordmark {
  font-family: "GT Sectra Fine Arrival", "GT Sectra Book Fallback", Georgia, serif;
  font-weight: var(--son-weight-book);
  font-size: var(--son-text-subhead);
  line-height: 1;
}
.chrome-right {
  display: flex;
  align-items: center;
  gap: var(--son-space-3);
}
/* Header CTA (owner 2026-07-24): a quiet access note beside a hairline button
   with real motion. The ink sweeps in from the left on hover, the label
   inverts, the button lifts; on click it presses and springs back. */
.access-note {
  opacity: 0.66;
  align-self: center;
}
.chrome-cta {
  text-decoration: none;
  border: var(--son-border-hairline) solid var(--son-border-strong);
  padding: 8px 20px;
  line-height: 1;
  white-space: nowrap;
  color: var(--son-text-primary);
  cursor: pointer;
  /* The fill is a gradient grown by background-size, so it paints under the
     label with no pseudo-element and no z-index juggling. */
  background-color: transparent;
  background-image: linear-gradient(var(--son-text-primary), var(--son-text-primary));
  background-repeat: no-repeat;
  background-position: left center;
  background-size: 0% 100%;
}
.chrome-cta:hover,
.chrome-cta:focus-visible {
  background-size: 100% 100%;
  color: var(--son-surface-primary);
  border-color: var(--son-text-primary);
  transform: translateY(-1px);
}
.chrome-cta:active {
  transform: scale(0.93);
}
/* Higher specificity than the fade-entrance transition so the motion survives
   while the entrance opacity fade is preserved. The transform release carries
   a slight overshoot, so the button pops back after a press. */
.js .chrome-cta[data-enter="fade"] {
  transition:
    opacity var(--enter-fade-duration, var(--son-track-fade-duration)) linear var(--enter-delay, 0ms),
    background-size 360ms cubic-bezier(0.66, 0, 0.34, 1),
    color 260ms ease,
    border-color 260ms ease,
    transform 240ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
.js .chrome-cta[data-enter="fade"]:active {
  transition: transform 70ms ease-out;
}

/* ── Panels ──
   100svh floor, growth allowed (build-spec Decision 4). Each panel carries
   its own theme; the ground is the themed surface. Two grounds abut at a
   1px-crisp moving line: the color cut is the line, no rule, no shadow. */
.panel {
  min-height: 100svh;
  background: var(--son-surface-primary);
  /* Re-applied per panel: the theme swaps the custom property at the seam,
     but an inherited computed color would carry the body's resolution into
     every panel regardless of its ground. */
  color: var(--son-text-primary);
}
.panel[data-ground="secondary"] {
  background: var(--son-surface-secondary);
}

/* Opening-sequence tightening (owner request 2026-07-23): the dark statement
   panels sit closer so the scroll reads intentional, not empty. min-height
   only — content-heavy panels still grow (Decision 4). Hero (P1) keeps its
   full frame; the pinned model, the ask, and the coda are untouched. */
.panel--column,
.panel--isolate {
  min-height: 82svh;
}

/* Content sits above the still layer, per element, never via the panel. */
.panel-content {
  position: relative;
  z-index: 2;
}

/* ── The wipe (build gate 1, PASSED; paint-order occlusion ratified) ──
   Z-order carries the seam-tracked clip. The still layer is fixed between
   the dark grounds and the daylight panels; daylight panels are stacked
   above it, so their opaque grounds clip the layer at exactly the moving
   seam, in the compositor, with zero per-frame script. The occlusion IS
   the wipe: the layer holds still while the Bone edge crosses it at P6/P7,
   and the rising Aubergine edge at P9/P10 hands it back in the position it
   was left, because nothing ever moved it. Dark panels stay unpositioned
   so their grounds paint beneath the layer; their content is lifted above
   it per element from pass 1 on (position relative, z-index 2 or higher).
   LOAD-BEARING INVARIANT (verification-probed): body, main.track, and the
   dark panels must never gain a stacking-context-creating property
   (transform, opacity below 1, filter, backdrop-filter, will-change,
   contain paint or layout, isolation, mix-blend-mode, clip-path, mask).
   Any of these demotes the panel below the fixed layer and locally scopes
   its content z-order, drowning content under the glyph; at 0.06 opacity
   the failure is visually subtle. will-change stays on the entering
   ELEMENTS inside a panel, never on a panel. Z-bands: layer 1, content 2,
   chrome 3 and above. Enforced by adherence/check-track.mjs. */
.panel--daylight {
  position: relative;
  z-index: 2;
}

/* ── The still layer ──
   Geometry and opacity are the seon tokens verbatim. The layer never
   enters: no transition, no animation, present from frame one. The size
   token clamps to the frame's short axis (tokens/seon.css), so the whole
   silhouette resolves on a phone too; overflow: clip stays as the guard
   against a frame narrower than any clamp anticipated, not as a stance. */
.still-layer {
  position: fixed;
  inset: 0;
  z-index: 1;
  display: grid;
  place-items: center;
  overflow: clip;
  pointer-events: none;
}
.still-glyph {
  height: var(--son-seon-still-size);
  width: auto;
  color: var(--son-glyph);
  opacity: var(--son-seon-still-opacity);
}

/* ── Grammar: the column (P2, P3, P4, P6; the reading sides of P7, P9) ──
   One reading column, left edge on the 10% margin, measure --son-measure,
   block optically centered: midpoint ≈ 46svh when seated. */
.panel--column {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding-inline: var(--son-margin-page);
}
/* The measure lives on the reading elements (.t-body, .t-lead), not the
   wrapper: a wrapper cap in body-ch units crushes display lines that carry
   their own fit formulas. */
.panel--column .panel-content {
  margin-bottom: 8svh;
}
.panel-eyebrow { margin-bottom: var(--son-space-4); --enter-mb: var(--son-space-4); }
.panel--column p + p { margin-top: var(--son-space-3); --enter-mt: var(--son-space-3); }
/* ── The register axis (build-spec §7, amendments 2 and 4) ──
   The moat is a story followed by a verdict: story register P2, P3, P4;
   case register P6 at Body 16, matching chapter II. A panel attribute,
   never a theme override (typography.css holds sizes invariant across
   dayparts; the register axis is orthogonal to theme). The story size and
   its compensated measure arrive as one token pair, so the column's
   rendered width holds constant across the register seam under
   co-visibility; the left edge is the shared 10% margin, untouched. The
   case attribute records the ruling in the markup and consumes Body
   unchanged: no rule keys on it by design. Leading and weight are Body's
   own (never Lead's face); the unitless leading scales with the size. */
.panel--column[data-register="story"] .t-body {
  font-size: var(--son-text-body-story);
  max-width: var(--son-measure-body-story);
}
/* ── The departure's tail (build-spec §7, amendments 5 and 6) ──
   P3 is the page's second designed extension and the third height
   class, emptiness-driven: the 100svh seat frame carries the two
   sentences exactly as the column grammar composes them (border-box:
   the tail padding leaves a 100svh content box, so the centering and
   the 8svh optical shift are untouched), then the tail of empty ground
   before P4's edge — the first moment the rate-zero relationship reads
   rather than merely being present: the text exits and the glyph holds
   the frame alone. Pure travel: no scrub, no pin, no keyed motion, no
   full-opacity event; height is static CSS, so the motion, reduced,
   and no-JS paths carry identical geometry. The glyph-alone frames the
   amendment names are real and bounded (measured at pass 9): P3 and P4
   share a ground, so the visible window between the couplet's exit and
   P4's text entering runs 17 to 25svh of travel on the phones and
   ~31svh on desktop — well inside one frame's worth of contentless
   travel, where the ratified boundary sits. What keeps those frames out of the
   coda's ending grammar is the grammar itself: the glyph holds its
   rest opacity through the whole tail (asserted; the scrub is
   end-anchored at the document, untouched here), and the ending was
   taught as the scrub to full — only the coda performs it. The felt
   judgment is the device pass's. The entrance arms at full seat
   (amendment 6, track.js). */
.panel--departure {
  min-height: calc(82svh + var(--son-track-departure-tail));
  padding-bottom: var(--son-track-departure-tail);
}
/* The blockquote's rhythm margin rides the quote line itself, not the
   wrapper: at this seam three margins would otherwise collapse together
   and swallow the body clip's negative pad margin (verification-caught,
   pass 3: the gap grew by exactly one top pad). With the margin on the
   entering element, --enter-mb composes it the same way as every other
   entering element, and the no-JS path keeps the plain margin. */
.moat-quote {
  margin: 0;
}
/* The blockquote is a locked single line (one clip, one rise, pass 3); it
   takes its width from the panel, not the 24ch headline cap, and wraps
   only where the phone forces it. */
.moat-quote .t-headline {
  max-width: none;
  margin-bottom: var(--son-space-4);
  --enter-mb: var(--son-space-4);
}

/* ── Grammar: the isolate (P5; P10's centered stack; the coda's frame) ──
   Centered both axes, optical center slightly high (≈ 47svh). */
.panel--isolate {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding-inline: var(--son-margin-page);
  text-align: center;
}
.panel--isolate .panel-content { margin-bottom: 6svh; }

/* ── P1, the diagonal ──
   Sentence upper-left on the margin, lock-up low-right on the margin at
   Headline scale over the still layer (field-under-mark), the open
   low-left as the named void. The top horizon is the chrome. */
.panel--hero {
  display: flex;
  padding: var(--son-space-6) var(--son-margin-page);
}
.panel--hero .panel-content {
  flex: 1;
  display: flex;
  flex-direction: column;
}
.hero-copy { margin-top: 14svh; }
.hero-loop { margin-top: var(--son-space-5); --enter-mt: var(--son-space-5); max-width: none; }
.lockup {
  display: inline-block;
  font-family: "GT Sectra Fine Arrival", "GT Sectra Book Fallback", Georgia, serif;
  font-weight: var(--son-weight-book);
  font-size: var(--son-text-subhead);
  line-height: 1.05;
}
.lockup-stack {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
}
.lockup-glyph {
  height: 1.02em;
  width: auto;
  margin-top: 0.16em;
}
.lockup--hero {
  font-size: var(--son-text-headline);
  margin-top: auto;
  align-self: flex-end;
  margin-bottom: 4svh;
  --enter-mb: 4svh;
}
/* The flex push must survive the entrance clip's margin composition. */
.js .lockup--hero[data-enter] { margin-top: auto; }
/* Short frames (ruled 2026-07-23, build-spec §7 amendment 9): the diagonal
   completes in the SEEN frame. "Low-right" is the low of the arrival frame,
   not of the overgrown panel's foot — the foot-anchor handed the mark to
   the fold, and the no-partial-mark rule outranks it. Entry air compresses
   14svh → 9svh and the strophe→loop gap 40px → 32px; type untouched; the
   panel's sanctioned growth (Decision 4) moves below the completed lock-up
   as ground. Measured on the built page at 375px: the natural stack
   completes with its 4svh floor at frame heights ≥ ~716px, the compressed
   one down to ~665px; no portrait device ≥ 360px wide (the wrap-free
   floor) sits below that. */
@media (max-width: 800px) and (max-height: 720px) {
  .hero-copy { margin-top: 9svh; }
  .hero-loop { margin-top: 32px; --enter-mt: 32px; }
}

/* ── P7, the accent split ──
   Column left; the peacock field right, ≈38vw, bleeding to the top, right,
   and bottom panel edges: a vertical hard seam, static. The 선 accent is
   Bone on Peacock, no frame, no caption. */
.panel--split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 38vw;
  /* The Ma margin lives on the panel so the percentage resolves against
     the viewport-wide box: the reading column's left edge must sit at
     exactly the same 10% as every other column panel. */
  padding-inline-start: var(--son-margin-page);
}
.split-copy {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding-block: var(--son-space-8);
  padding-inline-end: var(--son-space-8);
  margin-bottom: 8svh;
}
.split-copy .t-lead { margin-top: var(--son-space-6); --enter-mt: var(--son-space-6); }
.split-copy .t-body { margin-top: var(--son-space-4); --enter-mt: var(--son-space-4); }
.accent-field {
  background: var(--son-color-peacock);
  color: var(--son-color-bone);
  display: grid;
  place-items: center;
}
.accent-glyph {
  height: var(--son-seon-accent-size);
  width: auto;
}

/* ── P8, the team ──
   Two founder columns, portrait-led: hairline-framed 3:4 reserved slot,
   then name · role, then bio. The sanctioned growth panel on mobile. */
.panel--team {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: var(--son-space-12) var(--son-margin-page);
}
.founders {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--son-space-8);
}
.founder-portrait {
  aspect-ratio: 3 / 4;
  max-width: 320px; /* the carried slot width; P8 holds 100svh on desktop */
  border: var(--son-border-hairline) solid var(--son-border-default);
  margin-bottom: var(--son-space-3);
  overflow: clip;
  /* Subtle placeholder tint if a portrait is ever missing. */
  background: color-mix(in srgb, var(--son-text-primary) 5%, transparent);
}
/* The portrait image fills the framed slot; swap the file at
   assets/portraits/<name>.jpg to change a headshot. */
.founder-photo {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}
.founder-name { margin-bottom: var(--son-space-1); --enter-mb: var(--son-space-1); }
/* Role is a clean subtitle beneath the name (owner request 2026-07-23). */
.founder-role {
  margin-bottom: var(--son-space-3);
  --enter-mb: var(--son-space-3);
  opacity: 0.72;
}

/* ── P9, the model ──
   The headline is the anchor; the band sits inside the reading column,
   bounded by its labels, inside the margins, never full-bleed. Stripes are
   the carried treatment: color rides ::before so the pass-4 scrub can
   scale the field without distorting the label. Static and complete here.
   Density flag (build-spec P9): verified at capture this pass. */
.panel--model {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: var(--son-space-8) var(--son-margin-page);
}
.panel--model .panel-content { margin-bottom: 8svh; }

/* ── The pin (pass 4) ──
   The site's one bounded pin-and-scrub, realized as position: sticky
   inside a wrapper one pin-distance taller than the panel: the pin
   engages exactly when the panel is fully seated, holds for
   --son-track-pin-distance of travel while the stripes build, and
   releases into normal travel. No scroll is hijacked: the reader's
   scroll keeps moving the document; only the panel's position holds,
   and scrubbing back reverses everything by construction. The daylight
   compound keeps the stacking exemption (daylight panels are the
   sanctioned positioned panels). Mobile and reduced motion never pin. */
.js .pin-track {
  height: calc(100svh + var(--son-track-pin-distance));
}
.js .panel--daylight.panel--model {
  position: sticky;
  top: 0;
  height: 100svh;
  min-height: 0;
}
/* Without JavaScript there is no build to hold for: the band is complete
   by default, so the no-JS reader gets normal travel, not a frozen pin
   (verification-caught; mirrors the reduced path). */
@media (prefers-reduced-motion: reduce) {
  .js .pin-track { height: auto; }
  .js .panel--daylight.panel--model {
    position: static;
    height: auto;
    min-height: 100svh;
  }
}
.panel--model .t-headline { margin-bottom: var(--son-space-6); --enter-mb: var(--son-space-6); }
.daypart-band {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: var(--son-space-2);
  align-items: stretch;
  height: clamp(220px, 32vh, 320px);
  /* The band spans the reading column, bounded by its labels, never
     full-bleed; ch here computes against the body size. */
  max-width: var(--son-measure);
}
.band-bound {
  writing-mode: vertical-lr;
  align-self: center;
}
.band-stripes {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
}
.stripe {
  flex: 1;
  writing-mode: vertical-lr;
  padding: var(--son-space-2);
  position: relative;
}
.stripe::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--stripe-color);
  transform-origin: left center;
  /* Scrub-keyed by the band controller; defaults to complete, so the
     no-JS, reduced-motion, and font-hang paths all render the band
     built. Color rides the pseudo so scaling never distorts the label. */
  transform: scaleX(var(--stripe-build, 1));
}
.arrive-complete .stripe::before { transform: none !important; }
.stripe-label { position: relative; }
.stripe-coffee { --stripe-color: var(--son-color-jade); }
.stripe-lunch { --stripe-color: var(--son-color-onggi); }
.stripe-dinner { --stripe-color: var(--son-color-ground); }
.stripe-late { --stripe-color: var(--son-color-aubergine); }
/* Label ink is declared on the label itself: .son-eyebrow carries a direct
   color declaration, so a color inherited from the stripe never reaches
   the span (verification-caught: the dinner label vanished plum-on-plum
   on its own stripe). Bone on the three dark stripes, plum ink on jade. */
.stripe-coffee .stripe-label { color: var(--son-color-plum-ink); }
.stripe-lunch .stripe-label,
.stripe-dinner .stripe-label,
.stripe-late .stripe-label { color: var(--son-color-bone); }
.model-body { margin-top: var(--son-space-6); }
.model-body p + p { margin-top: var(--son-space-3); --enter-mt: var(--son-space-3); }

/* ── P10, the ask ──
   The isolate: centered stack, headline over the Bone card, deliberate
   kinship with P5. The card re-resolves the light tokens (carried); the
   form is static this pass, behavior is pass 5. */
.panel--ask {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  /* Asymmetric block padding carries the isolate's slightly-high optical
     center (≈47svh, the P5 kinship) inside the seated frame; the panel has
     no slack for a margin lift at 900px. */
  padding: var(--son-space-2) var(--son-margin-page) var(--son-space-8);
}
.panel--ask .panel-content {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  width: 100%;
}
.ask-headline { margin-bottom: var(--son-space-4); }

/* Honeypot. Taken out of the visual and reading order without display:none,
   which many bots detect and skip. The subject here is .ask-hp, a leaf inside
   the card, so the panel stacking invariant is untouched. */
.ask-hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.ask-card {
  --son-text-primary: var(--son-color-plum-ink);
  --son-border-default: color-mix(in srgb, var(--son-color-plum-ink) 22%, transparent);
  --son-border-strong: var(--son-color-plum-ink);
  --son-focus-ring: var(--son-color-peacock);
  --son-accent: var(--son-color-peacock);
  background: var(--son-color-bone);
  color: var(--son-text-primary);
  width: 100%;
  max-width: 40rem;
  margin-inline: auto;
  padding: var(--son-space-5);
  text-align: left;
}
.ask-form .field + .field { margin-top: var(--son-space-2); }
.field-label {
  display: block;
  margin-bottom: var(--son-space-1);
}
.field-input {
  width: 100%;
  font-family: var(--son-font-body);
  font-size: var(--son-text-body);
  line-height: var(--son-leading-body);
  color: inherit;
  background: none;
  border: var(--son-border-hairline) solid var(--son-border-default);
  border-radius: var(--son-radius-none);
  padding: var(--son-space-1) var(--son-space-2);
}
textarea.field-input { resize: vertical; }
.ask-submit {
  font-family: var(--son-font-body);
  font-size: var(--son-text-menu);
  letter-spacing: var(--son-tracking-open);
  color: inherit;
  background: none;
  border: var(--son-border-hairline) solid var(--son-border-strong);
  border-radius: var(--son-radius-none);
  padding: var(--son-space-2) var(--son-space-4);
  margin-top: var(--son-space-4);
  cursor: pointer;
  transition:
    background 200ms var(--son-ease-standard),
    color 200ms var(--son-ease-standard),
    border-color 200ms var(--son-ease-standard),
    transform 240ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
/* Fill-and-invert on hover, press-and-pop on click (owner 2026-07-24). */
.ask-submit:hover,
.ask-submit:focus-visible {
  background: var(--son-text-primary);
  color: var(--son-color-bone);
  border-color: var(--son-text-primary);
}
.ask-submit:active { transform: scale(0.95); transition: transform 70ms ease-out; }
.disclaimer { margin-top: var(--son-space-2); }

/* The in-card state swap (Decision 18): fields, submit, and disclaimer
   give way to the confirmation line, a 200ms functional cross-fade in the
   feedback register; the eyebrow and headline remain. Carried from the
   settled form. When the state is rendered from storage on load it is a
   condition, not an event: no fade plays. */
.ask-form,
.disclaimer {
  transition: opacity var(--son-motion-standard) var(--son-ease-standard);
}
.ask-form.is-leaving,
.disclaimer.is-leaving { opacity: 0; }
.ask-confirmation {
  opacity: 0;
  transition: opacity var(--son-motion-standard) var(--son-ease-standard);
}
.ask-confirmation.is-in { opacity: 1; }
.ask-confirmation.is-instant { transition: none; }

/* ── The coda (gate pass B, build gate 2) ──
   P11 runs ~180svh — since the departure's tail (amendment 5) one of the
   page's two designed extensions beside the pin distance, and the only
   one that ends the page. The
   seat frame (first 100svh) carries the lock-up and the location line; the
   final ~80vh of travel is empty ground, so the footer text exits upward
   at rate one past the held glyph while track.js scrubs the layer's
   opacity from the rest token to the coda token. The scroll ends on the
   glyph alone at full opacity. Nothing may sit over the glyph at full
   opacity: the close empties rather than stacks. The travel distance is
   the one track token, consumed here in svh and in track.js against
   innerHeight, so the two can never drift apart. */
.panel--coda {
  min-height: calc(100svh + var(--son-track-coda-travel) * 1svh);
}
.coda-frame {
  position: relative;
  z-index: 2;
  height: 100svh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--son-space-2);
  color: var(--son-text-primary);
}
.coda-location {
  font-family: var(--son-font-body);
  font-size: var(--son-text-menu);
  line-height: var(--son-leading-caption);
}

/* ── The placed close (pass 6; ratified 2026-07-22, track-scratch 10) ──
   The DEFAULT stance, serving no-JS and reduced motion: P11 occludes the
   resting layer by document order (the sanctioned second positioned
   panel, whitelisted in the invariant check by its dedicated class) and
   closes with a placed glyph frame in flow. Content can never overlap
   the mark: the separation is sequential in layout. track.js opts into
   the scrubbed stance only when motion is permitted, where the closing
   glyph is the still layer itself and this frame stands down. */
.panel--coda.panel--placed-close {
  position: relative;
  z-index: 2;
}
.coda-close {
  height: 100svh;
  display: grid;
  place-items: center;
  overflow: clip;
}
.coda-close-glyph {
  height: var(--son-seon-still-size);
  width: auto;
  color: var(--son-glyph);
}
.track-motion .panel--coda.panel--placed-close {
  position: static;
  z-index: auto;
}
.track-motion .coda-close { display: none; }

/* ── Entrances (pass 3) ──
   Registry per tokens/motion-track.css; clip geometry per
   tokens/typography.css: pads on the clip with equal negative margins so
   visual leading is unchanged, the mover unpadded, the rise origin 120%
   of the padded clip. Everything here is gated under html.js: without
   JavaScript no clip exists and the page renders complete. Layout margins
   on entering elements travel through --enter-mt/--enter-mb so the clip's
   negative pad margins compose with them instead of fighting them.
   track.js sets --enter-delay per element (per line inside strophes) and
   scopes will-change to the animation window. Movers ride transform and
   opacity only. */
.js [data-enter="lines"] .line,
.js [data-enter="line"],
.js [data-enter="block"] {
  overflow: clip;
  padding-top: var(--son-clip-pad-top);
  padding-bottom: var(--son-clip-pad-bottom);
  margin-top: calc(var(--enter-mt, 0px) - var(--son-clip-pad-top));
  margin-bottom: calc(var(--enter-mb, 0px) - var(--son-clip-pad-bottom));
}
.line-mover,
.enter-mover {
  display: block;
}
.lockup .enter-mover {
  display: inline-flex;
}

/* Lines: transform only; the clip does the hiding. */
.js [data-enter="lines"]:not(.is-in) .line-mover,
.js [data-enter="line"]:not(.is-in) .line-mover {
  transform: translateY(var(--son-rise-from));
  opacity: var(--enter-pre-opacity, 1);
}
.js [data-enter="lines"] .line-mover,
.js [data-enter="line"] .line-mover {
  transition:
    transform var(--enter-line-duration, var(--son-track-line-duration)) var(--enter-line-ease, var(--son-track-ease-line)) var(--enter-delay, 0ms),
    opacity var(--enter-line-duration, var(--son-track-line-duration)) linear var(--enter-delay, 0ms);
}
.js [data-enter].is-in .line-mover {
  transform: none;
  opacity: 1;
}

/* Blocks: one clip per block, rise and fade. */
.js [data-enter="block"]:not(.is-in) .enter-mover {
  transform: translateY(var(--son-rise-from));
  opacity: 0;
}
.js [data-enter="block"] .enter-mover {
  transition:
    transform var(--son-track-block-duration) var(--son-track-ease-block) var(--enter-delay, 0ms),
    opacity var(--son-track-block-duration) linear var(--enter-delay, 0ms);
}
.js [data-enter="block"].is-in .enter-mover {
  transform: none;
  opacity: 1;
}

/* Fades: headshots and chrome; never a rise. */
.js [data-enter="fade"] {
  transition: opacity var(--enter-fade-duration, var(--son-track-fade-duration)) linear var(--enter-delay, 0ms);
}
.js [data-enter="fade"]:not(.is-in) { opacity: 0; }
.chrome [data-enter="fade"] { --enter-fade-duration: var(--son-track-chrome-fade); }

/* The two locked single lines are desktop-band only (ruled 2026-07-22,
   the R3 rule applying): below their measured single-line thresholds the
   breaks are not locked and the block register governs. */
@media (max-width: 1108px) {
  .hero-loop {
    --enter-line-duration: var(--son-track-block-duration);
    --enter-line-ease: var(--son-track-ease-block);
    --enter-pre-opacity: 0;
  }
}
@media (max-width: 892px) {
  .moat-quote .t-headline {
    --enter-line-duration: var(--son-track-block-duration);
    --enter-line-ease: var(--son-track-ease-block);
    --enter-pre-opacity: 0;
  }
}

/* The font-hang path (build-spec §2.3, gate 8): past the ceiling the page
   renders the reduced path complete; choreography never fires on fallback
   faces. track.js sets the class and marks everything entered. */
.arrive-complete [data-enter] .line-mover,
.arrive-complete [data-enter] .enter-mover,
.arrive-complete [data-enter="fade"] {
  transition: none !important;
  transform: none !important;
}

/* Reduced motion (required at this pass, ruled 2026-07-22): the stagger's
   delays die here and the transforms are removed, so entrances are
   opacity-only and instant; durations die in the global kill
   (tokens/spacing.css), which zeroes durations but NOT delays, the exact
   gap that regressed the v3 fallback at pass 2. The full designed second
   path is pass 6. */
@media (prefers-reduced-motion: reduce) {
  .js [data-enter] .line-mover,
  .js [data-enter] .enter-mover,
  .js [data-enter="fade"] {
    transition-delay: 0ms !important;
    transform: none !important;
  }
}

/* ── Bands and mobile ──
   Type strophes switch where the floors engage; composition stacks at the
   carried mobile breakpoint. */
@media (max-width: 800px) {
  .strophe--hero-desktop { display: none; }
  .strophe--hero-mobile { display: flex; }
}
@media (max-width: 580px) {
  .strophe--solo-desktop { display: none; }
  .strophe--solo-mobile { display: flex; }
}
@media (max-width: 640px) {
  /* The chrome holds all three elements (ruled 2026-07-22; the v3 drop is
     not carried). The right block stacks, access note over A1, so the
     note frames the shortcut at exactly the point the reader might use
     it; the bar's shape change is the price of the third element. */
  .chrome-right {
    flex-direction: column;
    align-items: flex-end;
    gap: var(--son-space-1);
  }
  .founders { grid-template-columns: 1fr; }
  /* No pin on the phone: the stacked band builds top to bottom through
     the viewport; the column form does the pin's work. */
  .js .pin-track { height: auto; }
  .js .panel--daylight.panel--model {
    position: static;
    height: auto;
    min-height: 100svh;
  }
  .panel--split { display: block; padding-inline-start: 0; }
  .split-copy {
    min-height: 100svh;
    margin-bottom: 0;
    padding-inline: var(--son-margin-page);
    /* The margin lift would open a gap above the field; the optical
       midpoint (≈46svh) is carried by padding asymmetry instead. */
    padding-block: var(--son-space-8) calc(var(--son-space-8) + 8svh);
  }
  .accent-field { min-height: 46svh; }
  .accent-glyph { height: var(--son-seon-accent-size-mobile); }
  .daypart-band {
    grid-template-columns: none;
    grid-template-rows: auto auto auto;
    height: auto;
    justify-items: start;
  }
  .band-bound { writing-mode: horizontal-tb; }
  .band-stripes {
    flex-direction: column;
    width: 100%;
  }
  .stripe {
    writing-mode: horizontal-tb;
    min-height: 64px;
    display: flex;
    align-items: center;
  }
}
