/* Landing v10 — "Fixed Stage." One background stage (position:fixed, inset:0,
   no vh/svh/lvh sizing anywhere on it), all copy in a normal-flow scroll
   column above it. See docs/v10-fixed-stage-spec.md for the architecture
   rationale — this file replaces landing-v7.css for index.html only;
   subpages never referenced landing-v7.css/js (confirmed: they only load
   src/styles.css + src/main.js), so nothing else changes. */
@import url("https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,300..500&family=Inter:wght@400;500;600&display=swap");

body.v7-page {
  --v7-ink: #0d1421;
  --v7-night: #071020;
  --v7-paper: #f6f3ec;
  --v7-haze: #e9edf2;
  --v7-gold: #c9a257;
  /* v29: same deep-gold value as subpages.css's --sp-gold-deep (#93783f) —
     reused, not reinvented (Constitution §4). Needed because the rim-light
     --v7-gold, measured against this room's own bright paper fill, tops out
     near ~2.2:1 by the WCAG formula (a gold this light can never clear 3:1
     against a background this bright, no matter how the text is treated) —
     the same ceiling subpages.css already hit and solved with a deeper
     shade for on-light-panel gold text. See docs/v29-header-real-screens-spec.md. */
  --v7-gold-deep: #93783f;
  --v7-teal: #7fa8b8;
  --v7-ink-80: rgba(13, 20, 33, 0.8);
  --v7-ink-72: rgba(13, 20, 33, 0.72);
  --v7-ink-62: rgba(13, 20, 33, 0.62);
  --v7-ivory-96: rgba(246, 243, 236, 0.96);
  --v7-ivory-60: rgba(246, 243, 236, 0.6);
  --v7-r-outer: 24px;
  --v7-r-inner: 16px;
  /* v21: floating glass panel radius — one size up from --v7-r-outer,
     concentric with it (32 vs 24/16), per the room-panel spec. */
  --v7-r-panel: 32px;
  --v7-r-panel-mobile: 24px;
  --v7-ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --v7-ease-in: cubic-bezier(0.6, 0, 0.84, 0.14);
  background: var(--v7-paper);
}

/* ---------- Nav dark-theme flip (Night + Principles + hero-night-variant) ---------- */
body.v7-page .site-header.is-dark {
  color: var(--v7-ivory-96);
  border-color: rgba(246, 243, 236, 0.16);
  background:
    linear-gradient(135deg, rgba(246, 243, 236, 0.14), rgba(246, 243, 236, 0.03)),
    rgba(7, 16, 32, 0.56);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.08),
    inset 0 -1px 0 rgba(0, 0, 0, 0.3);
}

body.v7-page .site-header.is-dark .brand-mark {
  filter: invert(1);
}

body.v7-page .site-header.is-dark .nav-cta {
  color: var(--v7-ink);
  background:
    radial-gradient(circle at 20% 0, rgba(255, 255, 255, 0.4), transparent 40%),
    var(--v7-gold);
}

body.v7-page .site-header.is-dark .mobile-menu {
  border-color: rgba(246, 243, 236, 0.2);
  background:
    linear-gradient(145deg, rgba(246, 243, 236, 0.16), rgba(246, 243, 236, 0.05)),
    rgba(7, 16, 32, 0.72);
}

body.v7-page .site-header.is-dark .mobile-menu a {
  color: var(--v7-ivory-96);
}

body.v7-page .site-header.is-dark .mobile-menu a:hover,
body.v7-page .site-header.is-dark .mobile-menu a[aria-current="page"] {
  background: rgba(246, 243, 236, 0.12);
}

/* ---------- Base ---------- */
.landing-v7 {
  color: var(--v7-ink);
}

/* v10: the flow column itself must be transparent — every "room" section
   (range/ventures/night/principles) already carries its own opaque
   background; a background here would sit above the stage (main has an
   explicit z-index to stack above it) and paint solid paper over every
   transparent hero/film-chapter section, hiding the stage completely. */
body.v10-page main.landing-v7 {
  background: transparent;
}

.landing-v7 h1,
.landing-v7 h2,
.landing-v7 h3 {
  /* v12: margin-block only (not the shorthand) — the UA default margin on
     headings is vertical anyway, and zeroing all four sides here used to
     silently win a specificity fight against component rules further down
     this file that set `margin: ... auto` to horizontally center a heading
     (e.g. .v7-hero-title, .v7-hero-sub, .v7-hero-signature): a two-part
     selector like ".landing-v7 h1" carries more specificity than a lone
     class like ".v7-hero-sub", so its "margin: 0" was clobbering their
     "auto" inline margins and leaving those boxes flush at the left edge
     instead of centered — the real cause of the v12 hero-alignment bug. */
  margin-block: 0;
  font-family: "Fraunces", ui-serif, Georgia, serif;
  font-weight: 400;
  line-height: 0.98;
  letter-spacing: -0.01em;
  text-wrap: balance;
}

.landing-v7 p {
  /* v12: see margin-block note above — same clobbering issue applied to
     every centered paragraph (.v7-hero-sub, .v7-hero-signature, etc). */
  margin-block: 0;
  font-family: "Inter", ui-sans-serif, system-ui, sans-serif;
  font-weight: 400;
}

.landing-v7 .v7-eyebrow,
.landing-v7 .v7-lane-label,
.landing-v7 .v7-venture-name,
.landing-v7 .v7-principle-num {
  font-variant-numeric: tabular-nums;
}

.v7-mask {
  display: block;
  overflow: hidden;
}

.v7-mask-line {
  display: block;
  transform: translateY(0);
}

/* ---------- Global texture (grain) ---------- */
body.v7-page ::selection {
  background: var(--v7-gold);
  color: var(--v7-ink);
}

.v7-grain {
  position: fixed;
  inset: 0;
  z-index: 60;
  pointer-events: none;
  opacity: 0.035;
  mix-blend-mode: overlay;
  background-repeat: repeat;
  background-size: 180px 180px;
  background-position: 0 0;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch' result='t'/%3E%3CfeColorMatrix in='t' type='matrix' values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.4 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  animation: v7-grain-shift 0.9s steps(8) infinite;
}

@keyframes v7-grain-shift {
  0% { background-position: 0 0; }
  12.5% { background-position: -42px 18px; }
  25% { background-position: 28px -32px; }
  37.5% { background-position: -18px 42px; }
  50% { background-position: 46px 8px; }
  62.5% { background-position: -30px -26px; }
  75% { background-position: 22px 36px; }
  87.5% { background-position: -14px -44px; }
  100% { background-position: 0 0; }
}

@media (prefers-reduced-motion: reduce), (prefers-reduced-data: reduce) {
  .v7-grain {
    display: none;
  }
}

/* ============================================================
   v10 STAGE — one fixed background, no vh/svh/lvh sizing at all.
   inset:0 always fills the current visual viewport by construction;
   there is no section-height math to desync against dynamic browser
   chrome, touch timing, or pin math, because nothing here is sized
   to the viewport in the first place.
   ============================================================ */
.v10-stage {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  background: var(--v7-ink);
}

/* Every direct child of the stage is an explicitly stacked, explicitly
   positioned full-bleed layer — position + a set z-index integer (not
   "auto") guarantees each one establishes its own stacking context
   regardless of the opacity value the JS conductor happens to be driving
   at any moment, so the legacy negative z-index scheme *inside* e.g. the
   hero layer (media/fog/godray/towers) never leaks into or fights with
   sibling layers (journey video, stills, night ambience) at the stage's
   own top level. */
.v10-stage > .v10-layer {
  position: absolute;
  inset: 0;
}

.v10-hero-layer {
  z-index: 5;
  opacity: 1;
  isolation: isolate;
}

.v10-journey-layer {
  z-index: 4;
  opacity: 0;
  isolation: isolate;
}

.v10-still {
  z-index: 6;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 55%;
  opacity: 0;
}

.v10-night-ambience {
  z-index: 7;
  opacity: 0;
  transition: opacity 0.2s linear;
}

.v10-stage-scrim {
  z-index: 8;
  opacity: 0;
  background: rgba(7, 16, 32, 0.4);
  transition: opacity 0.4s ease-out, filter 0.4s ease-out;
}

.v10-stage.is-dimmed .v10-stage-scrim {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .v10-stage-scrim {
    transition: none;
  }
}

/* ---------- Hero diorama (lives inside .v10-hero-layer on the stage) ---------- */
.v7-hero-media {
  position: absolute;
  inset: 0;
  z-index: -6;
  animation: v7-hero-breathe 36s ease-in-out infinite;
}

@keyframes v7-hero-breathe {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.045); }
}

.v7-hero-plate {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 60%;
  transform: scale(1);
  will-change: transform;
}

.v7-hero-fallback {
  display: none;
}

.v7-hero-media.is-static .v7-hero-video {
  display: none;
}

.v7-hero-media.is-static .v7-hero-fallback {
  display: block;
}

.v7-hero-scrim {
  position: absolute;
  inset: 0;
  z-index: -2;
  background:
    linear-gradient(180deg, rgba(13, 20, 33, 0.34) 0%, rgba(13, 20, 33, 0.08) 34%, rgba(13, 20, 33, 0.03) 58%, rgba(13, 20, 33, 0.24) 100%);
}

.v7-hero-fog {
  position: absolute;
  inset: -18% -22%;
  overflow: hidden;
  pointer-events: none;
}

.v7-hero-fog-back {
  z-index: -5;
  animation: v7-fog-sway-back 84s ease-in-out infinite alternate;
}

.v7-hero-fog-front {
  z-index: -4;
  animation: v7-fog-sway-front 63s ease-in-out infinite alternate;
}

.v7-hero-fog-inner {
  position: absolute;
  inset: 0;
  will-change: transform;
}

.v7-hero-fog img {
  width: 130%;
  height: 130%;
  object-fit: cover;
}

.v7-hero-fog-back img {
  opacity: 0.32;
  filter: blur(22px) saturate(0.92);
}

.v7-hero-fog-front img {
  opacity: 0.46;
  filter: blur(10px) saturate(0.96);
}

@keyframes v7-fog-sway-back {
  from { transform: translateX(-3.2%); }
  to { transform: translateX(3.2%); }
}

@keyframes v7-fog-sway-front {
  from { transform: translateX(2.6%); }
  to { transform: translateX(-2.6%); }
}

.v7-hero-godray {
  position: absolute;
  inset: 0;
  z-index: -3;
  pointer-events: none;
  background: radial-gradient(ellipse 68% 58% at 88% 4%, rgba(255, 231, 180, 0.95), transparent 62%);
  mix-blend-mode: soft-light;
  opacity: 0.14;
  animation: v7-godray-breathe 9s ease-in-out infinite;
}

@keyframes v7-godray-breathe {
  0%, 100% { opacity: 0.1; }
  50% { opacity: 0.22; }
}

.v7-hero-godray.v10-handoff-quiet {
  animation: none;
}

/* Tower cutout: depth separation, hidden in static-fallback mode (its own
   photo already has baked-in towers) except dusk/night, whose relit still
   needs the animated cutout to keep the scene reading as alive. */
.v8-towers-wrap {
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  will-change: transform;
  animation: v7-hero-breathe 36s ease-in-out infinite;
}

.v8-towers {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  will-change: transform, opacity;
}

.v7-hero-media.is-static ~ .v8-towers-wrap {
  display: none;
}

@media (prefers-reduced-motion: reduce) {
  .v8-towers-wrap {
    animation: none;
    display: none;
  }
}

.v8-cloudshadow {
  position: absolute;
  inset: -22% -30%;
  z-index: -3;
  pointer-events: none;
  background:
    radial-gradient(38% 30% at 22% 60%, rgba(13, 20, 33, 0.16), transparent 70%),
    radial-gradient(30% 24% at 64% 74%, rgba(13, 20, 33, 0.12), transparent 70%),
    radial-gradient(24% 20% at 86% 52%, rgba(13, 20, 33, 0.1), transparent 70%);
  mix-blend-mode: multiply;
  animation: v8-shadow-drift 84s ease-in-out infinite alternate;
}

@keyframes v8-shadow-drift {
  from { transform: translateX(-4%) translateY(0.5%); }
  to { transform: translateX(5%) translateY(-0.8%); }
}

.v8-birds {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 4;
  pointer-events: none;
}

/* ---------- Journey film (lives inside .v10-journey-layer on the stage) ---------- */
.v10-journey-poster,
.v10-journey-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 55%;
}

.v10-journey-video {
  opacity: 0;
  transition: opacity 0.18s linear;
}

.v10-journey-video.is-ready {
  opacity: 1;
}

/* ---------- Night ambience (aurora + stars) ---------- */
.v7-night-aurora {
  position: absolute;
  left: -20%;
  right: -20%;
  top: 36%;
  height: 16%;
  z-index: 0;
  pointer-events: none;
  background: linear-gradient(90deg, transparent, rgba(127, 168, 184, 0.9), rgba(201, 162, 87, 0.55), transparent);
  background-size: 220% 100%;
  filter: blur(24px);
  opacity: 0.1;
  mix-blend-mode: screen;
  animation: v7-aurora-drift 42s linear infinite;
}

@keyframes v7-aurora-drift {
  0% { background-position: 0% 0; }
  100% { background-position: 220% 0; }
}

.v7-night-stars {
  position: absolute;
  inset: 0 0 45% 0;
  pointer-events: none;
}

.v7-star {
  position: absolute;
  left: var(--x);
  top: var(--y);
  width: 3px;
  height: 3px;
  border-radius: 999px;
  background: var(--v7-ivory-96);
  box-shadow: 0 0 6px 1px rgba(246, 243, 236, 0.6);
  animation: v7-twinkle var(--d, 5s) ease-in-out infinite;
}

@keyframes v7-twinkle {
  0%, 100% { opacity: 0.25; }
  50% { opacity: 0.9; }
}

/* ============================================================
   FLOW — normal-flow copy column above the stage. Sections are
   transparent where the stage should show through, opaque(-ish)
   where they are "rooms." main/footer get an explicit stacking
   context above the stage (z-index:0 fixed) — a plain in-flow,
   non-positioned element would otherwise paint *behind* a
   positioned z-index:0 sibling per the CSS stacking order, letting
   the fixed stage show through solid content.
   ============================================================ */
body.v10-page main.landing-v7,
body.v10-page .site-footer {
  position: relative;
  z-index: 1;
}

/* ---------- Hero copy (transparent flow section) ---------- */
.v10-hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  isolation: isolate;
}

.v7-hero-copy {
  position: relative;
  z-index: 2;
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: clamp(16px, 2.4vw, 26px);
  width: min(880px, 92vw);
  margin: 0 auto;
  padding: 0 max(20px, calc((100vw - 1280px) / 2)) clamp(56px, 11vh, 150px);
  text-align: center;
  color: var(--v7-ink);
}

.v7-hero-glow {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: -1;
  width: min(1150px, 155%);
  height: 140%;
  transform: translate(-50%, -50%);
  background: radial-gradient(
    ellipse 55% 42% at 50% 50%,
    rgba(246, 243, 236, 0.9) 0%,
    rgba(246, 243, 236, 0.62) 30%,
    rgba(246, 243, 236, 0.28) 54%,
    rgba(246, 243, 236, 0) 76%
  );
  filter: blur(36px);
  pointer-events: none;
}

.v7-hero-hairline {
  display: block;
  width: 64px;
  height: 1px;
  margin: 0 auto 2px;
  background: linear-gradient(90deg, transparent, var(--v7-gold), transparent);
  opacity: 0.85;
}

/* v30: the hero eyebrow sits on the pale dawn sky by default. Rim-light
   --v7-gold (#c9a257) tops out ~2:1 against that brightness — the identical
   ceiling the range room hit and solved at .v7-range-meta below by switching
   to --v7-gold-deep (#93783f), which clears ~3.8:1 on a light field. Reuse
   that same token here (Constitution §4, reuse before invention), plus the
   same light dark-shadow the range meta carries for a small honest margin.
   Scoped to the light (dawn) state only: the dusk/night override in the
   time-of-day block restores bright --v7-gold, correct on those dark skies. */
.v7-eyebrow {
  justify-self: center;
  margin: 0 auto;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--v7-gold-deep);
  text-shadow: 0 1px 6px rgba(13, 20, 33, 0.3);
}

.v7-hero-title {
  /* v12: text-wrap:balance (from the base h1/h2/h3 rule) can render this
     box narrower than the fully-stretched flex width it gets inside
     .v7-hero-copy; margin-inline:auto re-centers whatever width the
     balance algorithm settles on, matching the eyebrow/sub above and
     below it instead of leaving it flush at the flex-start edge. */
  margin-inline: auto;
  font-size: clamp(4rem, 10.5vw, 10rem);
  line-height: 0.96;
  color: var(--v7-ink);
  text-shadow: 0 2px 30px rgba(246, 243, 236, 0.6);
}

.v7-hero-sub {
  max-width: 46ch;
  margin: 0 auto;
  font-size: clamp(1.05rem, 1.6vw, 1.3rem);
  line-height: 1.6;
  color: var(--v7-ink);
  text-shadow: 0 2px 22px rgba(246, 243, 236, 0.65);
}

.v7-hero-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px;
  margin-top: 6px;
}

.v7-hero-signature {
  margin: 10px auto 0;
  font-family: "Inter", ui-sans-serif, system-ui, sans-serif;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.1em;
  font-variant-numeric: tabular-nums;
  color: var(--v7-ink-62);
  text-shadow: 0 2px 16px rgba(246, 243, 236, 0.6);
}

.v7-btn {
  min-height: 52px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* v19: padding aligned to .sp-btn's 28px (subpages.css) — was 26px here,
     a one-off drift between the two otherwise-identical button primitives.
     28px picked as the canon since it's already used in more places
     (every subpage button). See docs/v19-fit-finish-spec.md. */
  padding: 0 28px;
  border-radius: 999px;
  font-family: "Inter", ui-sans-serif, system-ui, sans-serif;
  font-size: 0.95rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  border: 1px solid transparent;
  transition:
    transform 220ms var(--v7-ease-out),
    box-shadow 220ms var(--v7-ease-out),
    background-color 220ms var(--v7-ease-out);
}

.v7-btn:hover {
  transform: translateY(-3px);
}

.v7-btn:active {
  transform: scale(0.98);
}

.v7-btn-dark {
  color: var(--v7-paper);
  background:
    radial-gradient(circle at 18% 0%, rgba(255, 255, 255, 0.16), transparent 42%),
    var(--v7-ink);
  box-shadow: 0 16px 34px rgba(13, 20, 33, 0.22);
}

.v7-btn-glass {
  --mx: 50%;
  --my: 50%;
  --v7-glow-a: 0;
  color: var(--v7-ink);
  border-color: rgba(13, 20, 33, 0.12);
  background:
    radial-gradient(circle at var(--mx) var(--my), rgba(255, 255, 255, var(--v7-glow-a)), transparent 40%),
    linear-gradient(145deg, rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.2));
  backdrop-filter: blur(18px) saturate(1.4);
  -webkit-backdrop-filter: blur(18px) saturate(1.4);
}

@media (hover: hover) and (pointer: fine) {
  .v7-btn-glass:hover,
  .v7-btn-glass.is-gliding {
    --v7-glow-a: 0.85;
  }
}

/* ---------- Film chapter cards (transparent flow spacers) ---------- */
.v10-film-chapter {
  position: relative;
  height: 95vh;
  background: transparent;
}

/* Per-chapter heights — tuned so each film second gets roughly 60-90px of
   scroll at a ~900px viewport (desktop acceptance height), per spec. */
.v10-film-chapter[data-film-start="0"] { height: 95vh; }
.v10-film-chapter[data-film-start="10.08"] { height: 48vh; }
.v10-film-chapter[data-film-start="15.1"] { height: 48vh; }
.v10-film-chapter[data-film-start="20.2"] { height: 48vh; }

.v9-drive-card {
  position: absolute;
  left: 50%;
  top: 50%;
  z-index: 3;
  isolation: isolate;
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  width: max-content;
  max-width: min(90vw, 620px);
  opacity: 0;
  text-align: center;
  pointer-events: none;
}

.v9-drive-card::before {
  content: "";
  position: absolute;
  inset: -34px -70px;
  z-index: -1;
  border-radius: 999px;
  background: radial-gradient(
    ellipse 62% 64% at 50% 50%,
    rgba(246, 243, 236, 0.88) 0%,
    rgba(246, 243, 236, 0.52) 42%,
    rgba(246, 243, 236, 0) 76%
  );
  filter: blur(22px);
  pointer-events: none;
}

.v9-drive-eyebrow {
  font-family: "Inter", ui-sans-serif, system-ui, sans-serif;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--v7-gold);
  font-variant-numeric: tabular-nums;
  text-shadow: 0 1px 3px rgba(13, 20, 33, 0.6), 0 0 10px rgba(13, 20, 33, 0.35);
}

.v9-drive-line {
  margin: 0;
  font-family: "Fraunces", ui-serif, Georgia, serif;
  font-style: italic;
  font-weight: 400;
  font-size: clamp(1.6rem, 2.6vw, 2.6rem);
  line-height: 1.3;
  letter-spacing: 0.01em;
  color: rgba(13, 20, 33, 0.7);
  text-wrap: balance;
}

.v9-drive-card--teal-glow::before {
  background: radial-gradient(
    ellipse 62% 64% at 50% 50%,
    rgba(11, 33, 38, 0.72) 0%,
    rgba(15, 45, 50, 0.4) 42%,
    rgba(15, 45, 50, 0) 78%
  );
}

.v9-drive-card--teal-glow .v9-drive-line {
  color: rgba(246, 243, 236, 0.92);
}

.v9-drive-card--dark-glow::before {
  background: radial-gradient(
    ellipse 62% 64% at 50% 50%,
    rgba(7, 16, 32, 0.6) 0%,
    rgba(7, 16, 32, 0.32) 46%,
    rgba(7, 16, 32, 0) 78%
  );
}

.v9-drive-card--dark-glow .v9-drive-line {
  color: rgba(246, 243, 236, 0.94);
}

/* ---------- Frosted-glass rooms (range / ventures / night / principles),
   TRANSLUCENT body over a backdrop blur so the film's light (and its
   frame-to-frame color shift as the stage moves between hills/tunnel/arch/
   home) keeps bleeding through the whole time a room is on screen.
   Opacity per room is the contrast lever: each room's rgba alpha below is
   the value that survived measuring worst-case contrast (body text over the
   brightest film still vs the darkest) at WCAG AA (4.5:1) — see
   docs/v15-frosted-rooms-spec.md and docs/v21-floating-panels-spec.md for
   the measured numbers. Raise a room's alpha (never remove blur) if a
   future asset change regresses contrast.
   v17 governed this as a "frosted wall" pattern: full-bleed bands, feathered
   top mask. v21 (founder direction): the rooms still read as full-width
   solid-feeling fields that hide the journey film at the edges. Retired the
   feathered-top-band entirely and converted every content room into a
   CONTAINED FLOATING GLASS PANEL — centered, rounded, with the moving film
   visible on every side (comfortable side margins, generous vertical gaps
   between consecutive panels) instead of a wall the film disappears behind.
   Night + principles opt back INTO a real glass material here too (their own
   dark/slate variant, set on their own selectors further down this file) —
   see docs/v21-floating-panels-spec.md. Since a panel no longer spans the
   viewport, the old top-feather mask (which existed to blend a full-bleed
   band's leading edge into the stage) has nothing left to do; the reveal is
   now a real entrance transition on the panel itself (rise + fade,
   landing-v10.js), not a mask blend. */
.landing-v7 .v10-room {
  position: relative;
  z-index: 1;
  width: min(1280px, 90vw);
  margin: clamp(56px, 8vw, 112px) auto;
  border-radius: var(--v7-r-panel);
  overflow: hidden;
  /* v27: flat fallback border lowered — the gold refraction ring (::after
     below) now carries the panel's primary edge cue; this is just a faint
     structural line under it. Inset top-highlight moved OFF this shared
     rule (light/dark rooms need different alphas — see the two-group split
     further down) — the shared box-shadow here only keeps the two drop-
     shadow layers common to every room. */
  border: 1px solid rgba(201, 162, 87, 0.14);
  box-shadow:
    0 8px 24px rgba(13, 20, 33, 0.1),
    0 48px 100px rgba(13, 20, 33, 0.26);
}

/* v27: true-glass recipe — founder feedback from a live screenshot: at the
   old 0.84/0.5 fills + blur(26px), the film's own light/dark structure was
   already smeared to near-uniform mush by the blur BEFORE it even reached
   the alpha composite, so every panel read as a flat cream (or slate)
   slab no matter how low its alpha was. Blur dropped 26px -> 14px across
   every room (see the two backdrop-filter groups below) so the film's
   motion/structure survives the blur step and still visibly moves/glows
   through the panel interior, not just at its edges. Gold refraction ring:
   the dual-layer gradient-border trick (mask + padding) — unlike
   border-image, this respects border-radius — paints a 1.5px ring only in
   the padding gutter between the panel's content-box and border-box,
   gold-tinted at the top-left corner fading fully transparent by the far
   edge (a refraction glint, not a uniform frame). See
   docs/v27-true-glass-spec.md. */
.landing-v7 .v10-room::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 3;
  border-radius: inherit;
  padding: 1.5px;
  background: linear-gradient(
    135deg,
    rgba(201, 162, 87, 0.65),
    rgba(201, 162, 87, 0.12) 38%,
    rgba(201, 162, 87, 0) 62%
  );
  -webkit-mask:
    linear-gradient(#000 0 0) content-box,
    linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  pointer-events: none;
}

/* v27: light rooms (range/ventures) vs dark rooms (night/principles) each
   get their own blur + inset-highlight tuning — same two-group split shape
   the touch-tier fallback further down already uses for these same pairs.
   Blur: 26px -> 14px for both (see the file-level comment above). Light
   rooms keep a saturate lift so the film's warm color still reads through
   the frost; dark rooms don't need one against already-desaturated
   night/tunnel footage. Inset top-highlight: 1.5px (was 1px), and now
   alpha-differentiated per the founder's brief — 55% white on light paper,
   14% white on dark slate (was a flat 35% shared across both, too weak on
   light paper and slightly too strong on dark slate). Same specificity as
   the shared rule above (two classes each) — wins because it's physically
   later in the file, same "physical order decides ties" mechanism v15/v21
   already established. */
.v10-room.v7-range,
.v10-room.v7-ventures {
  backdrop-filter: blur(14px) saturate(1.2);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
  box-shadow:
    inset 0 1.5px 0 rgba(255, 255, 255, 0.55),
    0 8px 24px rgba(13, 20, 33, 0.1),
    0 48px 100px rgba(13, 20, 33, 0.26);
}

.v10-room.v7-night,
.v10-room.v7-principles {
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  box-shadow:
    inset 0 1.5px 0 rgba(255, 255, 255, 0.14),
    0 8px 24px rgba(13, 20, 33, 0.1),
    0 48px 100px rgba(13, 20, 33, 0.26);
}

/* v21: mobile panels go near-full-width (16px side margins) with a slightly
   tighter radius — the "contained panel" language still holds at phone
   width, just with less side margin than the ≥5vw desktop band affords. */
@media (max-width: 860px) {
  .landing-v7 .v10-room {
    width: calc(100% - 32px);
    margin: 32px auto;
    border-radius: var(--v7-r-panel-mobile);
  }
}

/* ---------- v15: breathing gap — a transparent flow spacer inserted only
   where two rooms would otherwise butt directly against each other with no
   film chapter between them (today: range -> ventures). Lets the journey
   film reassert at full clarity (no blur, no tint) between chapters instead
   of one frosted room handing off straight into the next. ---------- */
.v10-breathing-gap {
  position: relative;
  height: 35vh;
  background: transparent;
}

@media (max-width: 860px) {
  .v10-breathing-gap {
    height: 22vh;
  }
}

/* ---------- Beat 2/3: The range ---------- */
.v7-range {
  /* v21: interior padding is symmetric on all sides now (was vertical-only,
     with horizontal centering delegated to .v7-range-inner's own max-width)
     — the room itself is the panel now, so it owns its own inset on every
     edge. */
  padding: clamp(40px, 6vw, 96px);
  /* v31: no fill at all — founder call ("layer after layer, box after box"
     vs the Aether liquid-glass reference): the range room stops being a
     glass PANEL entirely. The single glass layer is the lane cards below;
     the room is now a bare layout region and the journey film flows
     unbroken behind the header and straight through the transparent cards.
     Card text contrast moved from the retired room fill + copy micro-scrim
     onto the cards' own denser uniform frost (0.46) and hardened copy ink —
     see .v7-lane / .v7-lane-copy p. History of the panel era (v15 0.84 ->
     v20 0.5 -> v27 0.55 + blur 14px) retired with it. */
  background: transparent;
  scroll-margin-top: 100px;
}

/* v31: the panel-killing exception — must out-rank the shared
   .landing-v7 .v10-room recipe and the .v10-room.v7-range light-group
   tuning above; three classes beat their two regardless of position.
   v32: ventures joins range — same founder call ("layer after layer"),
   same mechanism: the room stops being a panel, the glass moves onto the
   components inside it (stage rail + stage panels). */
.landing-v7 .v10-room.v7-range,
.landing-v7 .v10-room.v7-ventures {
  border-color: transparent;
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  overflow: visible;
}
.landing-v7 .v10-room.v7-range::after,
.landing-v7 .v10-room.v7-ventures::after {
  content: none; /* the panel's gold refraction ring goes with the panel */
}

.v7-range-inner {
  position: relative;
  z-index: 1;
  /* v21: was min(1240px, 92vw) — a viewport-relative width made sense when
     the room itself spanned the viewport; now the room is a padded panel
     with its own inset, so the inner wrapper just fills the panel's content
     box (100%), capped so it doesn't sprawl on the largest panel width. */
  width: 100%;
  max-width: 1160px;
  margin: 0 auto;
}

/* v29: was a two-up flex row (eyebrow flush-left, H2 flush-right,
   space-between) — the split-editorial pattern, but paired with duplicated
   wording it read as two headers instead of one. Rebuilt as a single
   centered stack per the Constitution's hero alignment law (every child on
   the same axis, at every viewport — see docs/SITE-CONSTITUTION.md §4). The
   responsive override that used to flip this to left-aligned at <=1080px
   is gone: centered now holds at every width, so there is nothing left to
   flip. See docs/v29-header-real-screens-spec.md. */
.v7-range-header {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(10px, 1.6vw, 16px);
  margin: 0 auto clamp(48px, 6vw, 80px);
  text-align: center;
}

/* v29: this eyebrow used to sit directly on the room's own bright
   0.55-alpha paper fill and, per the founder's screenshot, was unreadable
   there. Measured: rim-light gold (--v7-gold, relative luminance ~0.39)
   against this room's fill (~0.90) tops out around ~2.2:1 by the WCAG
   formula — that's a hard ceiling of the color pair, not a shadow problem
   (a first attempt at a soft dark wash behind the word strong enough to
   close that gap rendered as exactly the visible smudge/plate the founder's
   v27 screenshot already called out once — reverted). The actual fix
   reuses a token this codebase already solved this same ceiling with:
   `--v7-gold-deep` (== subpages.css's `--sp-gold-deep`, #93783f) clears
   ~3.8:1 against this room's fill, comfortably past 3:1, with no wash and
   no plate at all — see docs/v29-header-real-screens-spec.md for the
   measured numbers. A light text-shadow still carries a small, honest share
   of the margin (not counted in the formal ratio, but real to the eye). */
.landing-v7 .v7-range-meta {
  flex: none;
  font-family: "Inter", ui-sans-serif, system-ui, sans-serif;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--v7-gold-deep);
  text-shadow: 0 1px 6px rgba(13, 20, 33, 0.3);
}

.landing-v7 .v7-range-intro {
  position: relative;
  z-index: 1;
  max-width: 640px;
  margin: 0 auto;
  font-family: "Fraunces", ui-serif, Georgia, serif;
  font-size: clamp(1.4rem, 2.4vw, 2rem);
  font-weight: 350;
  line-height: 1.3;
  text-align: center;
  color: var(--v7-ink);
}

/* v28: v27's micro-scrim here removed outright — founder called out this
   exact shape (a rounded pill sitting behind an H2) as a visible box, and
   measurement confirms it never needed one: at clamp(1.4rem,2.4vw,2rem)
   font-weight 350, this heading renders >=32px on every viewport where
   .v10-room's blur/fill recipe is active (pointer:fine), which is WCAG
   "large text" (needs only 3:1) — it clears 3:1 against the room's own
   0.55-alpha fill with wide margin at both film extremes, unscrimmed. See
   docs/v28-invisible-scrims-spec.md for the measured numbers. Below the
   pointer:fine breakpoint the flat 0.82-alpha touch fallback (no blur)
   clears it even more easily. A subtle text-shadow now carries a small
   share of the margin per the brief, harmless on the (rare) narrower
   fine-pointer widths where the clamp dips under 32px. */
.landing-v7 .v7-range-intro {
  text-shadow: 0 1px 12px rgba(246, 243, 236, 0.4);
}

/* ---------- v12: unified card anatomy (Homes / Apps / Systems) — one card
   structure, three data instances. Replaces the old v7-lane system (photo /
   tilted screenshot / dark crop) with a single card + media-field
   treatment so all three read as one family.
   v17: the engraved-bookplate emblems were swapped for glass-render plates
   (glass-homes / glass-apps / glass-night) — see docs/v17-glass-pass-spec.md.
   The renders each carry their own background (ivory / warm gradient /
   slate-to-ivory), so the field goes FULL-BLEED (object-fit: cover, no more
   flat-ivory `background: var(--v7-paper)` field) instead of contain-on-
   ivory — the render's own backdrop IS the field now. Field size, radius,
   and shadow stay identical across all three so they still read as one
   family despite the differing background tones. */
.v7-lane-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 22px;
  align-items: stretch;
}

/* v20: true glass panels — was a flat, opaque var(--v7-paper) fill (no
   backdrop-filter at all), which read as "flat opaque ivory" against the
   now-lighter room (see .v7-range above). Reused the .sp-card recipe
   (src/subpages.css) verbatim rather than inventing a new one: translucent
   paper + blur24/saturate1.15 (a touch stronger than .sp-card's 20/1.3,
   since this card sits over moving video rather than a static page and
   wants a slightly firmer frost), gold-tinted border, inset top highlight.
   The media field (full-bleed glass renders) is untouched by this — only
   the card's OWN body material changes. */
.v7-lane {
  --mx: 50%;
  --my: 50%;
  --v7-glow-a: 0;
  --v7-border-a: 0;
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 18px;
  padding: 28px 26px 30px;
  border-radius: var(--v7-r-outer);
  border: 1px solid rgba(201, 162, 87, var(--v7-border-a));
  /* v31: THE liquid-glass layer (founder-picked material, 46% frost). The
     room panel above it is retired, so this card now composites directly
     against the journey film: uniform ivory frost dense enough to carry
     its own small text at every frame of the moving film (the ledger rule
     — contrast comes from the card's ONE tint, never a local patch; the
     v27/v28 copy micro-scrim is deleted), blur 18px so the film's
     structure survives, and the card's visible edge is the 1.4px specular
     rim (::before below) instead of a resting border. The gold border
     var is kept at 0 and only rises on hover — the rim's hover glow. */
  background:
    radial-gradient(circle at var(--mx) var(--my), rgba(255, 255, 255, var(--v7-glow-a)), transparent 42%),
    rgba(246, 243, 236, 0.46);
  backdrop-filter: blur(18px) saturate(1.25);
  -webkit-backdrop-filter: blur(18px) saturate(1.25);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.6),
    0 2px 8px rgba(13, 20, 33, 0.06),
    0 22px 48px rgba(13, 20, 33, 0.16);
  transition:
    box-shadow 220ms var(--v7-ease-out),
    border-color 260ms var(--v7-ease-out),
    transform 260ms var(--v7-ease-out);
}

/* v31: the specular rim — Aether's mask-exclude mechanism (paints 1.4px
   only in the padding gutter, respects border-radius), gradient retuned
   for a LIGHT ground: white top catch + deep-gold bottom hairline. Pure
   gold-on-cream was proven invisible in the v30-era experiments. */
.v7-lane::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  border-radius: inherit;
  padding: 1.4px;
  background: linear-gradient(
    rgba(255, 255, 255, 0.95) 0%,
    rgba(255, 255, 255, 0.35) 18%,
    rgba(201, 162, 87, 0) 42%,
    rgba(201, 162, 87, 0) 58%,
    rgba(147, 120, 63, 0.4) 84%,
    rgba(255, 255, 255, 0.65) 100%
  );
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  pointer-events: none;
}

@media (hover: hover) and (pointer: fine) {
  .v7-lane:hover,
  .v7-lane.is-gliding {
    --v7-glow-a: 0.7;
    --v7-border-a: 0.55;
    transform: translateY(-6px);
    /* v20: added a soft gold outer glow (the new third box-shadow layer)
       alongside the existing lift + inset highlight — brief-specified
       "hover: lift + sheen sweep (exists) + border glow." */
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.65),
      0 34px 70px rgba(13, 20, 33, 0.18),
      0 0 0 1px rgba(201, 162, 87, 0.28),
      0 0 36px rgba(201, 162, 87, 0.22);
  }

  .v7-lane:hover .v7-lane-media img,
  .v7-lane.is-gliding .v7-lane-media img {
    transform: scale(1.05);
  }

  .v7-lane:hover .v7-lane-arrow,
  .v7-lane.is-gliding .v7-lane-arrow {
    transform: translateX(6px);
  }
}

.v7-lane:active {
  transform: scale(0.98) !important;
}

.v7-lane-index {
  font-family: "Fraunces", ui-serif, Georgia, serif;
  font-size: 1.1rem;
  font-weight: 300;
  font-variant-numeric: tabular-nums;
  color: var(--v7-gold);
}

/* v31: unboxed — was a rounded, overflow-hidden, inset-shadowed FRAME
   around an opaque emblem tile: the literal "box within box" the founder
   rejected three times. The emblems are now alpha cutouts (assets/emblems/
   glass-*-alpha.png, BiRefNet-matted from the originals) floating free on
   the card's glass; a drop-shadow filter (follows the object's silhouette,
   not its bounding box) grounds them. */
.v7-lane-media {
  position: relative;
  isolation: isolate;
  height: 240px;
}

.v7-lane-media img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  filter: drop-shadow(0 18px 22px rgba(13, 20, 33, 0.22));
  transition: transform 700ms var(--v7-ease-out);
}

.v7-lane-copy {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  flex: 1;
  gap: 10px;
}

/* v31: the v27/v28 copy micro-scrim is DELETED, not retuned — the third
   founder rejection (v30 night beat) settled the durable rule: over a
   translucent fill, ANY local darkening/brightening shape reads as a box
   eventually. Card copy contrast now comes from the card's own uniform
   0.46 frost (see .v7-lane) plus hardened ink and a per-glyph ivory halo
   below — the halo follows the letterforms, so there is no edge for the
   eye to find. */

.v7-lane-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  /* v31: deep gold + glyph halo — bright gold at 11px fails contrast on
     the light frost now that the film shows through. */
  color: var(--v7-gold-deep);
  text-shadow: 0 0 6px rgba(246, 243, 236, 0.8);
}

.v7-lane-title-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
}

.v7-lane-copy h3 {
  font-size: clamp(1.5rem, 2vw, 1.9rem);
  /* v28: text-shadow carries a small share of the contrast load per the
     invisible-scrims brief, alongside the softened ::before above. */
  text-shadow: 0 1px 12px rgba(246, 243, 236, 0.45);
}

.v7-lane-arrow {
  font-size: 1.1rem;
  color: var(--v7-gold);
  transition: transform 260ms var(--v7-ease-out);
}

.v7-lane-copy p {
  font-size: 0.98rem;
  line-height: 1.6;
  /* v31: near-full ink + a tighter per-glyph ivory halo — opacity-muted
     body text can't hold 4.5:1 over the moving film through 0.46 frost. */
  color: rgba(13, 20, 33, 0.88);
  text-shadow:
    0 0 6px rgba(246, 243, 236, 0.9),
    0 1px 16px rgba(246, 243, 236, 0.6);
}

/* ---------- Beat 4: Ventures — instrument stage (v17) ----------
   Founder feedback on the v12 hover-thumbnail rack: it read as "lazy" and
   the thumbnail was too small to actually see the product. Replaced with a
   split stage: LEFT is a plain list of the five entries (number / name /
   essence — no chip, no thumb; the chip moved into the stage). RIGHT is
   one persistent glass panel that always shows the ACTIVE app's real
   -altai screen at a size big enough to read, over that app's atmosphere
   wash (the same slate/teal/honey/night/felt identities the old rack
   used), with its chip and a real "Visit page" link.
   DRY trick: each `.v10-stage-item` <article> in index.html holds BOTH its
   row and its panel — the copy is authored once. At >=861px the article is
   unwrapped (`display: contents`, see the media query below) so its two
   children become independent CSS Grid items of `.v10-stage-wrap`: the row
   falls into the left column in its own row, and the panel is explicitly
   placed into the SAME right-column cell as the other four (grid allows
   co-located items; they simply overlap in DOM order) — `data-active`
   drives an opacity crossfade to decide which one is on top. Below 861px
   `.v10-stage-item` is just a normal block, so the same two children stack
   as one full card — the "stacked cards, large screen" mobile spec is a
   direct consequence of the layout switch, not separate markup. */
.v7-ventures {
  position: relative;
  z-index: 1;
  /* v32: no fill — ventures follows range out of the panel era (see the
     v31/v32 comments on .v7-range): the film flows unbroken through the
     beat, and the glass lives on the stage rail (one uniform frost card
     behind the five tab rows, replacing the v28 gradient wash — see the
     .v10-stage-wrap::before rewrite) and the stage panels, which already
     carry their own glass + gold ring. Panel-era history (v15 0.84 ->
     v27 0.55) retired. */
  background: transparent;
  /* v21: symmetric panel inset, same rationale as .v7-range above. */
  padding: clamp(40px, 6vw, 96px);
}

.v7-ventures-frame {
  /* v21: was min(1320px, calc(100vw - 48px)) — viewport-relative; now fills
     the panel's own content box. */
  width: 100%;
  max-width: 1160px;
  margin: 0 auto;
}

.landing-v7 .v7-ventures-intro {
  position: relative;
  z-index: 1;
  max-width: 560px;
  margin: 0 auto clamp(36px, 5vw, 56px);
  font-family: "Fraunces", ui-serif, Georgia, serif;
  font-size: clamp(1.4rem, 2.4vw, 2rem);
  font-weight: 350;
  line-height: 1.3;
  text-align: center;
}

/* v28: removed — same rationale/measurement as .v7-range-intro above (this
   h2 is the same size class, >=32px regular on every pointer:fine width,
   large-text 3:1 clears unscrimmed). See docs/v28-invisible-scrims-spec.md. */
.landing-v7 .v7-ventures-intro {
  text-shadow: 0 1px 12px rgba(246, 243, 236, 0.4);
}

.v10-stage-frame {
  position: relative;
  z-index: 1;
  /* v21: was min(1320px, calc(100vw - 48px)); see .v7-ventures-frame above. */
  width: 100%;
  max-width: 1160px;
  margin: 0 auto;
}

.v10-stage-wrap {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

/* Mobile-first base: each item is a self-contained card (row text, then
   its panel) stacked in normal flow — the ">=861px" media query below is
   what turns this into the split desktop stage; nothing here is mobile-
   *specific* styling bolted on afterward. */
.v10-stage-item {
  position: relative;
  padding: 22px;
  border-radius: var(--v7-r-outer);
  /* v32: was opaque var(--v7-paper) — with the ventures room de-paneled
     this was the last solid slab on the beat (and a panel-in-panel with
     the glass stage part inside it). Now the same glass as the range
     lanes; the stage part inside reads as the card's media field, like
     the app cards' device frames. */
  background: rgba(246, 243, 236, 0.46);
  backdrop-filter: blur(18px) saturate(1.25);
  -webkit-backdrop-filter: blur(18px) saturate(1.25);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.6),
    0 2px 8px rgba(13, 20, 33, 0.06),
    0 20px 40px rgba(13, 20, 33, 0.14);
}

/* v32: mobile card rim (same recipe as .v7-lane::before). Desktop MUST
   opt out: >=861px sets .v10-stage-item to display:contents, and a
   pseudo of a display:contents element becomes a stray child of the
   stage grid — the content:none guard lives in that media query. */
.v10-stage-item::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  border-radius: inherit;
  padding: 1.4px;
  background: linear-gradient(
    rgba(255, 255, 255, 0.95) 0%,
    rgba(255, 255, 255, 0.35) 18%,
    rgba(201, 162, 87, 0) 42%,
    rgba(201, 162, 87, 0) 58%,
    rgba(147, 120, 63, 0.4) 84%,
    rgba(255, 255, 255, 0.65) 100%
  );
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  pointer-events: none;
}

.v10-stage-row-part {
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: 100%;
  padding: 0 0 16px;
  border: 0;
  background: none;
  text-align: left;
  color: var(--v7-ink);
  font: inherit;
  cursor: default;
}

.v10-stage-num {
  font-family: "Inter", ui-sans-serif, system-ui, sans-serif;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.06em;
  font-variant-numeric: tabular-nums;
  color: var(--v7-gold);
}

.v10-stage-name {
  font-family: "Fraunces", ui-serif, Georgia, serif;
  font-weight: 400;
  font-size: clamp(1.3rem, 2vw, 1.7rem);
  letter-spacing: -0.01em;
  text-shadow: 0 1px 12px rgba(246, 243, 236, 0.4);
  transition: opacity 220ms var(--v7-ease-out);
}

.v10-stage-essence {
  font-family: "Fraunces", ui-serif, Georgia, serif;
  font-style: italic;
  font-weight: 400;
  font-size: 0.98rem;
  line-height: 1.4;
  /* v32: hardened like .v7-lane-copy p — the rail frost is 0.46 now. */
  color: rgba(13, 20, 33, 0.88);
  text-shadow: 0 0 6px rgba(246, 243, 236, 0.9);
  text-shadow: 0 1px 12px rgba(246, 243, 236, 0.4);
  transition: opacity 220ms var(--v7-ease-out);
}

/* v20: was a bare border+shadow with no background/backdrop-filter at
   all — the per-app ".v10-stage-wash" gradient (below) was the ONLY
   visible material, and at its old near-opaque alpha (0.88-0.98) it read
   as a flat solid color wash (founder: "boring solid background, missing
   glass"). Added the panel's own translucent glass body + blur (matching
   the app-hero stacking language exactly), and gave every wash the exact
   same lowered alpha values already tuned "by eye against real captures"
   in src/subpages.css's .sp-scene-* (v18 app-hero pass) — reused verbatim
   rather than re-tuning, since these are the identical five app moods. */
.v10-stage-panel-part {
  position: relative;
  isolation: isolate;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 55vw;
  border-radius: var(--v7-r-inner);
  overflow: hidden;
  /* v27: fill and blur LEFT AT their original values (0.16 / blur(20px)
     saturate(1.3)) — tried the same "lower fill, lighter blur" move as the
     rooms/cards first, but this container sits BEHIND the per-app wash +
     aurora (both semi-opaque layers that already do the real "glass" work
     here, per the brief: "internal aurora/emblem fields stay"), so those
     layers dominate the composite and the container's own fill barely
     shows through either way (0.16 -> 0.22 moved the measured contrast by
     <0.1:1). The blur DID matter, though — dropping it 20px -> 14px let
     more of the raw (less-smeared) outer journey film show through the
     wash, measurably thinning this zone's contrast margin against its own
     worst-case app mood: `.v10-stage-essence` measured 6.20:1 before this
     pass, only 5.14:1 at 14px blur (aurora frozen for a clean A/B), and as
     low as 4.45:1 with the aurora playing live and unlucky (the moving
     aurora loop makes this zone's true worst case a moving target, unlike
     the rooms' deterministic scroll-scrubbed film). Since the outer film
     isn't even the thing this panel is showing (that's the aurora's job),
     the founder's "film must read through" goal doesn't apply here the
     same way — reverted blur/fill, kept every other v27 glass cue (gradient
     ring, wider inset highlight). See docs/v27-true-glass-spec.md. */
  background: rgba(246, 243, 236, 0.16);
  backdrop-filter: blur(20px) saturate(1.3);
  -webkit-backdrop-filter: blur(20px) saturate(1.3);
  border: 1px solid rgba(201, 162, 87, 0.14);
  box-shadow:
    inset 0 1.5px 0 rgba(255, 255, 255, 0.4),
    0 24px 48px rgba(13, 20, 33, 0.18);
}

/* v27: gold refraction ring, same dual-layer mask trick as
   .landing-v7 .v10-room::after — see that rule's comment for the mechanism. */
.v10-stage-panel-part::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 3;
  border-radius: inherit;
  padding: 1.5px;
  background: linear-gradient(
    135deg,
    rgba(201, 162, 87, 0.6),
    rgba(201, 162, 87, 0.1) 38%,
    rgba(201, 162, 87, 0) 62%
  );
  -webkit-mask:
    linear-gradient(#000 0 0) content-box,
    linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  pointer-events: none;
}

/* ---------- v20: aurora backdrop — assets/v9/aurora-loop.mp4, tinted per
   app with the SAME filter/opacity values as that app's own detail-page
   hero (src/subpages.css .sp-app-hero-aurora-video--*). Sits behind the
   wash and the device, inside the panel's own rounded clip (overflow:
   hidden above), so it plays as the panel's atmosphere rather than a
   room-wide layer. Resilience (poster fallback / reduced-motion static /
   IO-driven play-pause) is wired in landing-v10.js; only the ACTIVE
   panel's video is ever playing (JS pauses the rest), so switching apps
   never runs more than one video at once regardless of viewport. ---------- */
.v10-stage-aurora {
  position: absolute;
  inset: 0;
  z-index: -1;
  overflow: hidden;
}

.v10-stage-aurora-video,
.v10-stage-aurora-fallback {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0.4;
}

.v10-stage-aurora-fallback {
  display: none;
}

.v10-stage-aurora.is-static .v10-stage-aurora-video {
  display: none;
}

.v10-stage-aurora.is-static .v10-stage-aurora-fallback {
  display: block;
}

@media (prefers-reduced-motion: reduce), (prefers-reduced-data: reduce) {
  .v10-stage-aurora-video {
    display: none;
  }

  .v10-stage-aurora-fallback {
    display: block;
  }
}

.v10-stage-aurora-video--slate,
.v10-stage-aurora-fallback--slate {
  filter: hue-rotate(200deg);
}

.v10-stage-aurora-video--teal,
.v10-stage-aurora-fallback--teal {
  filter: hue-rotate(140deg);
}

.v10-stage-aurora-video--honey,
.v10-stage-aurora-fallback--honey {
  filter: saturate(1.2);
}

.v10-stage-aurora-video--night,
.v10-stage-aurora-fallback--night {
  filter: hue-rotate(190deg) brightness(0.5);
  opacity: 0.38;
}

.v10-stage-aurora-video--felt,
.v10-stage-aurora-fallback--felt {
  filter: hue-rotate(100deg) brightness(0.45);
}

.v10-stage-aurora-video--critterbook,
.v10-stage-aurora-fallback--critterbook {
  filter: hue-rotate(60deg) saturate(1.05);
}

.v10-stage-wash {
  position: absolute;
  inset: 0;
  z-index: 0;
}

/* v20: alpha values match src/subpages.css .sp-scene-* exactly (same app,
   same mood) — was 0.88-0.98 per stop, opaque enough to hide the aurora
   entirely; now 0.55-0.78, so the moving film carries the atmosphere and
   the wash only tints it. */
.v10-stage-panel-part--slate .v10-stage-wash {
  background: linear-gradient(160deg, rgba(219, 210, 238, 0.55), rgba(242, 238, 249, 0.72));
}

.v10-stage-panel-part--teal .v10-stage-wash {
  background: linear-gradient(150deg, rgba(127, 168, 184, 0.32), rgba(233, 237, 242, 0.62) 55%, rgba(246, 243, 236, 0.78));
}

.v10-stage-panel-part--honey .v10-stage-wash {
  background: linear-gradient(150deg, rgba(214, 168, 74, 0.36), rgba(250, 233, 205, 0.62) 42%, rgba(252, 227, 220, 0.66) 72%, rgba(248, 238, 232, 0.78));
}

.v10-stage-panel-part--night .v10-stage-wash {
  background:
    radial-gradient(ellipse 70% 45% at 18% 82%, rgba(45, 138, 150, 0.22), transparent 60%),
    linear-gradient(180deg, rgba(7, 16, 32, 0.62), rgba(7, 16, 32, 0.78));
}

.v10-stage-panel-part--felt .v10-stage-wash {
  background: radial-gradient(ellipse 80% 70% at 50% 28%, rgba(28, 56, 44, 0.6), rgba(7, 12, 11, 0.75));
}

/* alpha values match src/subpages.css .sp-scene-critterbook exactly */
.v10-stage-panel-part--critterbook .v10-stage-wash {
  background: linear-gradient(150deg, rgba(101, 117, 40, 0.3), rgba(224, 226, 205, 0.6) 48%, rgba(246, 241, 231, 0.8));
}

.v10-stage-device {
  position: relative;
  z-index: 1;
  height: 55vw;
  max-height: 420px;
  aspect-ratio: 704 / 1529;
  border-radius: 26px;
  overflow: hidden;
  border: 3px solid var(--v7-ink);
  box-shadow:
    0 2px 0 rgba(255, 255, 255, 0.5) inset,
    0 30px 60px rgba(13, 20, 33, 0.3);
}

.v10-stage-device img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.v10-stage-footer {
  position: absolute;
  z-index: 2;
  left: 18px;
  right: 18px;
  bottom: 18px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* v24 mobile-QA fix: chip + CTA are both nowrap now (see .v10-stage-cta),
     but the narrower phone-mockup panels don't always have room for both on
     one line — e.g. the "Language" chip next to "Visit page". Wrapping to
     a clean second row beats either an inline text-wrap or an overlap. */
  flex-wrap: wrap;
  gap: 10px 12px;
}

/* v26: four apps have real icon art now; the row list stays icon-free by
   design (v17's whole point was stripping the row to number/name/essence,
   no chip, no thumb — an icon there would undo that). The icon joins the
   chip in the panel footer instead, grouped so justify-content:space-between
   still splits into (icon+chip) vs. the CTA. NeuRest has no icon — its
   footer keeps just the chip, no wrapper needed. */
.v10-stage-footer-left {
  display: flex;
  align-items: center;
  gap: 10px;
}

.v10-stage-icon {
  flex: none;
  width: 40px;
  height: 40px;
  border-radius: 22%;
  object-fit: cover;
  border: 1px solid rgba(255, 255, 255, 0.55);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.35),
    0 2px 6px rgba(13, 20, 33, 0.16),
    0 8px 16px rgba(13, 20, 33, 0.22);
}

.v10-stage-chip {
  font-family: "Inter", ui-sans-serif, system-ui, sans-serif;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 6px 14px;
  border-radius: 999px;
  color: var(--v7-ink);
  background: rgba(246, 243, 236, 0.88);
  border: 1px solid rgba(13, 20, 33, 0.08);
  white-space: nowrap;
}

.v10-stage-cta {
  /* v24 mobile-QA fix: was 40px — 4px under the 44px tap-target floor on
     the mobile-first stacked ventures cards. Also added white-space:nowrap
     — with a longer sibling chip (e.g. "Language"), this squeezed to two
     lines ("Visit" / "page") while shorter chips ("Kids") left it on one
     line, an inconsistent wrap across otherwise-identical sibling cards. */
  min-height: 44px;
  padding: 0 18px;
  font-size: 0.85rem;
  white-space: nowrap;
}

.v10-stage-cta:focus-visible,
.v10-stage-row-part:focus-visible {
  outline: 2px solid var(--v7-gold);
  outline-offset: 2px;
}

/* ---------- Desktop stage: the split instrument layout ----------
   Turns the mobile-first stacked cards above into a two-column grid: a
   plain list on the left, one shared cell on the right where all five
   panels overlap (opacity-crossfaded by `data-active`). Section height is
   capped at min(52vh, 560px) so it — plus the heading — comfortably stays
   under 100vh on common desktop sizes. */
@media (min-width: 861px) {
  .v10-stage-wrap {
    display: grid;
    grid-template-columns: minmax(280px, 380px) 1fr;
    /* v36: six app rows (was five) — the sixth app (Critterbook) added a
       sixth .v10-stage-row-part; the track count must match or the sixth
       row auto-creates an implicit track that falls outside the frosted
       rail card (.v10-stage-wrap::before spans grid-row 1 / -1) and the
       shared stage height. */
    grid-template-rows: repeat(6, 1fr);
    gap: 0 clamp(24px, 3vw, 48px);
    height: min(56vh, 620px);
    border-top: 1px solid rgba(13, 20, 33, 0.14);
  }

  /* Unwraps the <article> so its two children (row + panel) become
     independent grid items of .v10-stage-wrap instead of one nested box —
     see the file-level comment above for why this keeps the copy DRY. */
  .v10-stage-item {
    display: contents;
  }

  /* v32: kill the mobile card rim here — a pseudo of a display:contents
     element would otherwise render as a stray auto-placed grid item. */
  .v10-stage-item::before {
    content: none;
  }

  .v10-stage-row-part {
    position: relative;
    z-index: 1;
    grid-column: 1;
    justify-content: center;
    height: 100%;
    padding: 0 6px;
    border-bottom: 1px solid rgba(13, 20, 33, 0.14);
    cursor: pointer;
    transition: padding-left 280ms var(--v7-ease-out);
  }

  /* v28: replaces v27's per-row scrim. That version (`.v10-stage-row-part
     ::before`, one hard-edged rectangle per row, inset -2px -10px, no
     radius) was exactly the shape the founder flagged elsewhere on this
     page — abutting rectangles read as a stack of slabs, not a wash, even
     though each one individually faded to 0 on its right edge. v27 tried a
     single shared scrim first (`.v10-stage-wrap::before`, grid-column:1,
     grid-row:1/-1) and hit a real bug: an explicit-position pseudo mixed
     into a grid of five AUTO-placed row-parts collides with the
     auto-placement algorithm (auto items won't share a cell with an
     explicit one), silently growing 5 extra implicit rows — confirmed via
     `getComputedStyle(wrap).gridTemplateRows` reporting 10 tracks instead
     of 5. The actual fix isn't per-row scrims, it's making the shared
     scrim NOT a grid item at all: `position: absolute` removes an element
     from grid auto-placement entirely (it still sizes/positions against
     its `grid-column`/`grid-row` area as a containing block, but never
     competes for a cell), so this single element can span all five rows
     with zero collision. Verified: `gridTemplateRows` stays 5 tracks with
     this rule active (see docs/v28-invisible-scrims-spec.md). One
     continuous horizontal fade (background) plus a vertical mask feather
     at the top/bottom of the whole stack replaces five separate edges with
     zero internal seams — no border-radius, peak 0.42 (down from 0.72),
     per the new house rule for text scrims. */
  /* v32: the v28 gradient wash is DELETED, not retuned — with the ventures
     room panel retired, the rail's small text sits over the raw film and a
     feathered wash is exactly the local-patch shape the founder has now
     rejected three times. Replaced by the durable pattern: ONE uniform
     glass card (same material as the range lanes: 0.46 frost, blur 18px,
     specular rim) backing the whole five-row rail. Still position:absolute
     against the grid area so it never competes with the five auto-placed
     rows for cells (the v27 lesson about explicit-position pseudos in an
     auto-placed grid still applies — see git history for the full writeup). */
  .v10-stage-wrap::before {
    content: "";
    position: absolute;
    /* v32 bugfix: for ABSPOS grid children, "grid-column: 1" = "1 / auto"
       and the auto end line resolves to the container's padding edge — the
       box silently spans every column. (The v28 wash had this same bug,
       masked because its gradient hit zero before the panel.) Explicit
       end line pins the containing block to the rail column. */
    grid-column: 1 / 2;
    grid-row: 1 / -1;
    top: -20px;
    bottom: -20px;
    left: -26px;
    right: -14px;
    z-index: -1;
    border-radius: var(--v7-r-outer);
    background: rgba(246, 243, 236, 0.46);
    backdrop-filter: blur(18px) saturate(1.25);
    -webkit-backdrop-filter: blur(18px) saturate(1.25);
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.6),
      0 2px 8px rgba(13, 20, 33, 0.06),
      0 22px 48px rgba(13, 20, 33, 0.16);
    pointer-events: none;
  }

  /* v32: the rail's specular rim — same 1.4px mask-exclude recipe as
     .v7-lane::before, same geometry as the frost card above. */
  .v10-stage-wrap::after {
    content: "";
    position: absolute;
    grid-column: 1 / 2; /* see the abspos containing-block note on ::before */
    grid-row: 1 / -1;
    top: -20px;
    bottom: -20px;
    left: -26px;
    right: -14px;
    z-index: -1;
    border-radius: var(--v7-r-outer);
    padding: 1.4px;
    background: linear-gradient(
      rgba(255, 255, 255, 0.95) 0%,
      rgba(255, 255, 255, 0.35) 18%,
      rgba(201, 162, 87, 0) 42%,
      rgba(201, 162, 87, 0) 58%,
      rgba(147, 120, 63, 0.4) 84%,
      rgba(255, 255, 255, 0.65) 100%
    );
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
    mask-composite: exclude;
    pointer-events: none;
  }

  .v10-stage-row-part:hover,
  .v10-stage-row-part:focus-visible,
  .v10-stage-row-part[aria-selected="true"] {
    padding-left: 20px;
  }

  .v10-stage-row-part:not([aria-selected="true"]) .v10-stage-name,
  .v10-stage-row-part:not([aria-selected="true"]) .v10-stage-essence {
    /* v32: 0.6 -> 0.72 — the dim tier sits on 0.46 frost now, not 0.55
       frost over a 0.55 room; 0.6 dipped under comfortable contrast. */
    opacity: 0.72;
  }

  /* Explicit placement (not auto-flow): every panel targets the SAME cell
     (col 2, spanning all 5 rows = the panel's full height), so the five
     overlap instead of stacking — only `data-active` + opacity decide
     which one is visible on top. */
  .v10-stage-panel-part {
    grid-column: 2;
    grid-row: 1 / -1;
    min-height: 0;
    border-radius: var(--v7-r-outer);
    opacity: 0;
    z-index: 1;
    transition: opacity 260ms ease;
    pointer-events: none;
  }

  .v10-stage-panel-part[data-active="true"] {
    opacity: 1;
    z-index: 2;
    pointer-events: auto;
  }

  .v10-stage-device {
    /* v20: was 85% — with the panel now showing a real aurora backdrop
       behind it, 85% left almost no visible margin for that film to read.
       78% sits in the brief's specified ~75-80% band and leaves a clear
       aurora-lit edge around the device on all sides. */
    height: 78%;
    max-height: none;
  }
}

/* ---------- Beat 5: Night passage ----------
   v17: founder call — the tunnel film is already near-black, so the v15
   frosted wash + blur was muddying motion that should read crisp. Removed
   entirely (background now transparent; backdrop-filter cancelled via the
   compound override further down, same cascade-safety pattern v15 used for
   its perf fallback — see docs/v17-glass-pass-spec.md). Contrast now comes
   from ONE local vignette (`::before`) sized to sit behind the text column
   only, not a room-wide wash — the film reads full clarity everywhere else. */
.v7-night {
  scroll-margin-top: 100px;
  /* v21: symmetric panel inset — was clamp(90px,16vw,170px) 24px (vertical-
     heavy, thin horizontal gutter), sized for a full-bleed room. Now a
     contained panel with the same clamp on every edge as the other three
     rooms. */
  padding: clamp(40px, 6vw, 96px);
}

/* v21: retired. This local vignette existed to punch contrast into a
   full-bleed room without darkening the whole viewport (v17 rationale:
   the tunnel footage ran edge-to-edge behind it). Now that .v7-night is a
   contained glass panel (see .v7-night's own dark-glass background further
   down this file), the panel's own tint already covers its whole area
   uniformly — a second, narrower vignette on top of that would just double
   up the darkening unevenly. See docs/v21-floating-panels-spec.md. */
.v7-night::before {
  display: none;
}

.v7-night-copy {
  position: relative;
  z-index: 1;
  /* v29: min-height dropped from 56vh. Founder flagged the room as
     mostly empty space with the headline and body floating far apart —
     the tall min-height + justify-center was stretching a compact block
     of copy across half the viewport. The panel now sizes to its content
     (plus the room's own clamp() padding), same as range/ventures. */
  display: flex;
  flex-direction: column;
  gap: clamp(20px, 2.4vw, 30px);
  width: min(760px, 100%);
  margin: 0 auto;
  color: var(--v7-ivory-96);
}

/* v28: v27's single scrim over the WHOLE copy block (h2 + body + list)
   removed — this h2 (clamp(2.2rem,4.5vw,4rem), >=35px on every
   pointer:fine width) is large-text and clears 3:1 against the room's own
   0.55-alpha ink fill unscrimmed, so wrapping it in a scrim built for the
   small body copy below it was doing nothing but adding a visible dark
   patch behind it. Body + capability list still need real protection
   (they're not large text) — that's now `.v7-night-body-group::before`
   below, scoped to just that zone. See docs/v28-invisible-scrims-spec.md. */
.v7-night-copy h2 {
  font-size: clamp(2.2rem, 4.5vw, 4rem);
  /* v29: line-height + vertical padding so the serif's ascenders and the
     'g' descender in "through" aren't shaved by .v7-mask's overflow:hidden
     (which exists for the reveal animation). Negative margin cancels the
     padding so the block's outer box is unchanged — only the clip region
     grows. Founder saw the top/bottom of the headline clipped. */
  line-height: 1.06;
  padding: 0.1em 0.02em;
  margin: -0.1em -0.02em;
  color: var(--v7-ivory-96);
  text-shadow: 0 1px 14px rgba(13, 20, 33, 0.4);
}

/* v29: the local small-text scrim (v28's .v7-night-body-group::before
   radial wash) is RETIRED. It was meant to read as invisible ambient
   darkening but rendered as a hard-edged rectangle behind the body copy —
   the founder's screenshot called it out directly. Rather than keep tuning
   a radial to hide a box that only exists because the room fill was dropped
   to a translucent 0.55, the night room's own fill is now raised back to a
   denser slate (see .v10-room.v7-night below) so the small body/list copy
   clears contrast on the panel's own uniform tint — no local patch needed.
   The wrapper div stays (harmless grouping); it just no longer paints. */
.v7-night-body-group {
  position: relative;
}

.v7-night-body {
  max-width: 46ch;
  font-size: clamp(1rem, 1.4vw, 1.15rem);
  line-height: 1.65;
  color: var(--v7-ivory-60);
  position: relative;
  text-shadow: 0 1px 14px rgba(13, 20, 33, 0.45);
}

/* v29: the two body sentences were butting together as one cramped block —
   a little air between paragraphs so each thought reads on its own. */
.v7-night-body + .v7-night-body {
  margin-top: 0.7em;
}

/* v29: was a tight 12px-gap grid reading as a bullet list. Opened the row
   rhythm up so the three capabilities feel like deliberate editorial lines,
   not a checklist. */
.v7-capabilities {
  display: grid;
  gap: 16px;
  margin: 6px 0 0;
  padding: 0;
  list-style: none;
}

/* v29: the founder read this as an off-brand bullet list. The tell was the
   DOUBLE marker on every row — a gold dash AND a glowing teal/gold dot.
   The dot is gone (see the retired .v7-dot rule note below); each line now
   leads with a single thin gold rule that animates in, which matches the
   site's own hairline/eyebrow vocabulary instead of a checklist bullet.
   align-items:baseline so the rule sits on the text baseline, not floating
   at vertical center of a wrapping line. */
.v7-capability {
  display: flex;
  align-items: center;
  gap: 16px;
  font-size: 1rem;
  font-weight: 500;
  line-height: 1.5;
  color: var(--v7-ivory-96);
  text-shadow: 0 1px 14px rgba(13, 20, 33, 0.45);
}

.v7-hairline {
  flex: none;
  width: 30px;
  height: 1px;
  /* v29: solid gold now, not the gold→teal gradient — a single refined
     rule reads calmer than a two-tone one, and drops the teal that only
     existed to color-match the (now-removed) dot. Vertical placement is
     handled by the row's align-items:center (not a translateY in the
     transform) because the reveal JS overwrites this transform with a bare
     scaleX(1) — see landing-v10.js:274 fallback + the GSAP tween. */
  background: var(--v7-gold);
  transform: scaleX(0);
  transform-origin: left center;
}

/* v29: .v7-dot and .v7-capability:nth-child(2) .v7-dot RETIRED. The glowing
   dots were the second half of the double-marker that made this read as a
   bullet list; the .v7-dot spans are removed from index.html. */

/* ---------- Beat 6: Principles ----------
   v21: this room is a contained floating glass panel now (dark/slate
   variant, set on .v7-principles further down this file), not a full-bleed
   band — see docs/v21-floating-panels-spec.md. The v17 local vignette
   (.v9-principles-scrim) that used to carry contrast on its own is retired
   in favor of the panel's own uniform tint (same call as .v7-night above). */
.v7-principles {
  overflow: hidden;
  /* v21: symmetric panel inset, same rationale as the other three rooms. */
  padding: clamp(40px, 6vw, 96px);
  color: var(--v7-ivory-96);
  scroll-margin-top: 100px;
}

/* v21: retired — see the file-level comment on .v7-principles above. */
.v9-principles-scrim {
  display: none;
}

.landing-v7 .v7-principles-title {
  position: relative;
  z-index: 2;
  width: min(900px, 100%);
  margin: 0 auto clamp(50px, 7vw, 90px);
  font-size: clamp(2.2rem, 4.5vw, 4rem);
  color: var(--v7-ivory-96);
  text-align: center;
  /* v28: v27's micro-scrim (a border-radius'd rounded pill) removed —
     this is the exact box the founder's screenshot called out. At
     >=35px on every pointer:fine width this h2 is WCAG large-text (3:1
     floor) and clears it against the room's own 0.55-alpha ink fill with
     wide margin, unscrimmed. A little text-shadow now stands in for it.
     See docs/v28-invisible-scrims-spec.md. */
  text-shadow: 0 1px 14px rgba(13, 20, 33, 0.4);
}

/* v28: wrapper (index.html) around the three .v7-principle-row divs — a
   single shared wash lives here (see ::before below) instead of three
   per-row hard boxes. */
.v7-principle-rows {
  position: relative;
}

/* v28: replaces v27's `.v7-principle-row::before` — that recipe (inset:0,
   no bleed at all past each row's own box, peak 0.84) was the exact "hard-
   edged darker slab" the founder's screenshot called out: a flat rectangle
   stopping dead at each row's boundary, one per row, so three abutting
   slabs instead of one wash. This single element sits on the ROW WRAPPER
   (a sibling of the individual rows, not nested inside their own
   `overflow: clip`), so it's free to bleed well past any one row's edges —
   three soft radial layers (one centered per row's own vertical position)
   painted in ONE box, not three DOM boxes, so there's no seam between them.
   No border-radius, peak capped at 0.45.

   Tuning note (caught by the luminance-step gate, not by eye): a first
   version of this bled -60px/-70px and centered each layer with plain
   percentages (e.g. "at 44% 14.4%"). That LOOKED right, but percentages
   for radial-gradient position/size resolve against this element's OWN
   (bled-out, larger-than-the-wrapper) box, not the wrapper the numbers
   were measured against — so row 1's gradient center ended up only ~75px
   from the box's own top edge while its radius (sized as a % of that same
   big box) needed 600px+ to reach its 92% stop. The result: at the box's
   real edge, the gradient was still ~90% of peak intensity, and it just
   stops there — a hard cutoff exactly at the pseudo-element's own
   boundary, invisible in a quick visual check but a 50/255 step in the
   on/off luminance diff. Fixed by working entirely in pixels measured
   against the ACTUAL row wrapper (via getBoundingClientRect at the 1440px
   reference width — see docs/v28-invisible-scrims-spec.md) and choosing
   each radius so its 92%-opacity stop lands safely inside the nearest box
   edge with margin to spare, rather than trusting percentages against a
   box that got bigger out from under them. */
.v7-principle-rows::before {
  content: "";
  position: absolute;
  top: -130px;
  bottom: -130px;
  left: -70px;
  right: -70px;
  z-index: -1;
  background:
    /* row1: two layers, not one. `p` here sits furthest into the still-
       bright raw backdrop of any row (same lesson v27 learned tuning the
       old per-row scrim), and one wide-enough ellipse to comfortably
       reach both its h3 and p columns would need a radius bigger than
       what's geometrically safe this close to the box's top edge (row 1
       has ~188px of headroom there). So: one layer centered over h3/num
       (safe at this size), plus a SECOND, separately-safe layer centered
       further right specifically over the `p` column, sized to its own
       (tighter but still safe) margin instead of overreaching from a
       single shared center. */
    radial-gradient(
      ellipse 50% 32% at 48% 28.4%,
      rgba(13, 20, 33, 0.45) 0%,
      rgba(13, 20, 33, 0.34) 48%,
      rgba(13, 20, 33, 0) 88%
    ),
    radial-gradient(
      ellipse 34% 30% at 66% 28.4%,
      rgba(13, 20, 33, 0.42) 0%,
      rgba(13, 20, 33, 0.32) 48%,
      rgba(13, 20, 33, 0) 88%
    ),
    radial-gradient(
      ellipse 48% 26% at 46% 47.9%,
      rgba(13, 20, 33, 0.45) 0%,
      rgba(13, 20, 33, 0.32) 50%,
      rgba(13, 20, 33, 0) 88%
    ),
    radial-gradient(
      ellipse 48% 26% at 46% 69.5%,
      rgba(13, 20, 33, 0.45) 0%,
      rgba(13, 20, 33, 0.32) 50%,
      rgba(13, 20, 33, 0) 88%
    );
  pointer-events: none;
}

.v7-principle-row {
  position: relative;
  z-index: 2;
  display: grid;
  grid-template-columns: 90px minmax(0, 0.7fr) minmax(0, 1fr);
  align-items: baseline;
  gap: clamp(16px, 3vw, 40px);
  width: min(1080px, 100%);
  margin: 0 auto;
  padding: clamp(28px, 4vw, 42px) 0;
  overflow: clip;
}

.v7-principle-row:last-child {
  border-bottom: 1px solid rgba(246, 243, 236, 0.16);
}

.v7-principle-rule {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 1px;
  background: rgba(246, 243, 236, 0.16);
  transform: scaleX(0);
  transform-origin: left center;
}

.v7-principle-ghost {
  position: absolute;
  top: 50%;
  right: 0;
  z-index: 0;
  transform: translateY(-42%);
  font-family: "Fraunces", ui-serif, Georgia, serif;
  font-size: 18vw;
  font-weight: 300;
  line-height: 1;
  color: var(--v7-ivory-96);
  opacity: 0.04;
  pointer-events: none;
  white-space: nowrap;
}

.v7-principle-num,
.v7-principle-row h3,
.v7-principle-row p {
  position: relative;
  z-index: 1;
}

.v7-principle-num {
  font-family: "Inter", ui-sans-serif, system-ui, sans-serif;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--v7-gold);
}

.v7-principle-row h3 {
  font-size: clamp(1.4rem, 2.2vw, 1.9rem);
  color: var(--v7-ivory-96);
  text-shadow: 0 1px 14px rgba(13, 20, 33, 0.45);
}

.v7-principle-row p {
  max-width: 46ch;
  font-size: 1rem;
  line-height: 1.65;
  color: var(--v7-ivory-60);
  /* v28: row1's `p` sits at the outer edge of the shared wash's coverage
     (widest exposure to the still-bright raw backdrop, same lesson v27
     learned tuning the old per-row scrim) — a stronger text-shadow here
     closes the remaining margin without raising the wash's own peak past
     the 0.45 cap. */
  text-shadow: 0 1px 18px rgba(13, 20, 33, 0.75);
}

/* ---------- Beat 7: Closing (transparent overlay over the held final frame) ---------- */
.v7-closing {
  position: relative;
  min-height: 100svh;
  overflow: hidden;
  isolation: isolate;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: clamp(80px, 14vh, 140px) 24px clamp(50px, 8vh, 80px);
  gap: clamp(48px, 8vh, 90px);
}

/* v33: founder flagged the section's top edge as a visible hard cut across
   the sky ("it keeps stopping me"). Root cause: the old linear layer
   STARTED at 0.5 alpha at 0% — an instant 0 -> 50% luminance jump at the
   section boundary, the same "stop parked at the box's own edge" failure
   v28 codified, just at full-section scale; the old radial (at 50% 30%,
   default farthest-corner extent) also reached the top edge with nonzero
   value. Rewritten so BOTH layers are true zero at the top: the linear
   ramps 0 -> 0.62 over the section's upper ~60% (sunset melting into
   dusk, no findable line), and the radial gets an explicit size and moves
   down behind the copy (vertical reach stops ~9% from the top). Copy
   contrast at the words' own zone (~55-70% down) is unchanged. */
.v7-closing-scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    radial-gradient(ellipse 90% 50% at 50% 64%, rgba(7, 16, 32, 0.22), transparent 100%),
    linear-gradient(
      180deg,
      rgba(7, 16, 32, 0) 0%,
      rgba(7, 16, 32, 0.12) 22%,
      rgba(7, 16, 32, 0.3) 42%,
      rgba(7, 16, 32, 0.5) 62%,
      rgba(7, 16, 32, 0.62) 100%
    );
}

.v7-closing-copy {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 34px;
  text-align: center;
}

.v7-closing-copy h2 {
  font-size: clamp(2.6rem, 6vw, 5rem);
  color: var(--v7-ivory-96);
}

.v7-closing-words {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  row-gap: 0.05em;
  column-gap: 0.28em;
}

.v7-cta-pill {
  --mx: 50%;
  --my: 50%;
  --v7-glow-a: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 56px;
  padding: 0 32px;
  border-radius: 999px;
  border: 1px solid rgba(201, 162, 87, 0.4);
  color: var(--v7-ink);
  font-family: "Inter", ui-sans-serif, system-ui, sans-serif;
  font-size: 1rem;
  font-weight: 600;
  background:
    radial-gradient(circle at var(--mx) var(--my), rgba(255, 255, 255, var(--v7-glow-a)), transparent 45%),
    var(--v7-gold);
  box-shadow: 0 20px 46px rgba(7, 16, 32, 0.32);
  transition: box-shadow 220ms var(--v7-ease-out);
  will-change: transform;
}

@media (hover: hover) and (pointer: fine) {
  .v7-cta-pill:hover,
  .v7-cta-pill.is-gliding {
    --v7-glow-a: 0.5;
  }
}

.v7-cta-pill:active {
  transform: scale(0.98);
}

.v7-closing-wordmark {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: clamp(10px, 2vw, 22px);
  opacity: 0.9;
  color: var(--v7-ivory-96);
}

.v7-closing-wordmark img {
  height: clamp(28px, 5vw, 56px);
  width: auto;
}

.v7-closing-wordmark span {
  font-family: "Fraunces", ui-serif, Georgia, serif;
  font-size: clamp(2.2rem, 13.5vw, 14vw);
  font-weight: 400;
  letter-spacing: -0.01em;
  line-height: 1;
  white-space: nowrap;
}

/* ---------- Reduced motion safety ---------- */
@media (prefers-reduced-motion: reduce) {
  .v7-star {
    animation: none;
    opacity: 0.55;
  }

  .v7-grain,
  .v7-hero-fog-back,
  .v7-hero-fog-front,
  .v7-hero-godray,
  .v7-hero-media,
  .v7-night-aurora,
  .v8-cloudshadow {
    animation: none !important;
  }

  .v7-hero-godray {
    opacity: 0.14;
  }

  .v8-birds {
    display: none;
  }
}

/* ---------- Responsive ---------- */
@media (max-width: 1080px) {
  /* v29: the header used to flip to left-aligned here (the old split
     pattern degraded that way below 1080px). Now that the base rule is
     already a centered stack, there's nothing to flip — centered holds at
     every width, per the Constitution's alignment law. */
  .v7-range-header {
    gap: 12px;
  }

  .v7-lane-grid {
    grid-template-columns: 1fr;
  }

  .v7-lane-media {
    height: 220px;
  }
}

@media (max-width: 860px) {
  /* Film chapters simplify on mobile: shorter, no curtain concept needed
     since sections were never overlapped in the first place. */
  .v10-film-chapter[data-film-start="0"] { height: 70vh; }
  .v10-film-chapter[data-film-start="10.08"],
  .v10-film-chapter[data-film-start="15.1"],
  .v10-film-chapter[data-film-start="20.2"] { height: 40vh; }
}

@media (max-width: 720px) {
  .v7-hero-copy {
    width: 100%;
    padding: 0 22px clamp(36px, 9vh, 100px);
  }

  .v9-drive-card {
    display: none;
  }

  .v7-hero-actions {
    width: 100%;
    max-width: 340px;
    margin: 6px auto 0;
  }

  .v7-btn {
    width: 100%;
  }

  .v7-principle-row {
    grid-template-columns: 48px 1fr;
  }

  .v7-principle-row p {
    grid-column: 2 / 3;
  }

  .v7-closing-wordmark {
    flex-direction: column;
    gap: 10px;
  }
}

/* ================= Route thread — fixed connective spine ================= */
.v9-route {
  position: fixed;
  top: 0;
  right: 24px;
  bottom: 0;
  width: 2px;
  z-index: 90;
  pointer-events: none;
}

.v9-route-track,
.v9-route-fill {
  position: absolute;
  inset: 0;
  background: var(--v7-gold);
}

.v9-route-track {
  opacity: 0.12;
}

.v9-route-fill {
  opacity: 0.85;
  transform: scaleY(0);
  transform-origin: top center;
}

.v9-route-marker {
  position: absolute;
  left: 50%;
  top: 0;
  width: 8px;
  height: 8px;
  transform: translate(-50%, -50%) rotate(45deg);
  background: var(--v7-gold);
  box-shadow: 0 0 10px 2px rgba(201, 162, 87, 0.5);
}

.v9-route-tick {
  position: absolute;
  left: 50%;
  top: 0;
  width: 6px;
  height: 6px;
  border-radius: 1px;
  transform: translate(-50%, -50%);
  background: var(--v7-gold);
  opacity: 0.65;
}

@media (max-width: 860px) {
  .v9-route {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce), (prefers-reduced-data: reduce) {
  .v9-route {
    display: none;
  }
}

/* ================= v9.5 — Entry gate (preloader) ================= */
.v95-gate {
  position: fixed;
  inset: 0;
  z-index: 400;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--v7-paper, #f6f3ec);
  transform: translateY(0);
  filter: blur(0);
  transition:
    transform 600ms cubic-bezier(0.65, 0, 0.35, 1),
    filter 600ms cubic-bezier(0.65, 0, 0.35, 1);
}

.v95-gate[hidden] {
  display: none;
}

.v95-gate.is-leaving {
  transform: translateY(-100%);
  filter: blur(14px);
  mask-image: linear-gradient(to bottom, black 0%, black 78%, transparent 100%);
  -webkit-mask-image: linear-gradient(to bottom, black 0%, black 78%, transparent 100%);
}

.v95-gate-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
}

.v95-gate-mark {
  width: 64px;
  height: auto;
  opacity: 0.9;
}

.v95-gate-coords {
  margin: 0;
  font-family: "Inter", ui-sans-serif, system-ui, sans-serif;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.14em;
  font-variant-numeric: tabular-nums;
  color: var(--v7-ink-62, rgba(13, 20, 33, 0.62));
}

/* ================= v9.5 — Time-of-day hero ================= */
/* v30: the base .v7-eyebrow now uses --v7-gold-deep for the light dawn sky.
   On the dusk/night skies the backdrop is dark, so restore the bright
   rim-light --v7-gold (correct, high-contrast on dark) and drop the light
   dark-shadow (which only earned its keep on the pale field). */
body.v95-hour-dusk .v7-eyebrow,
body.v95-hour-night .v7-eyebrow {
  color: var(--v7-gold);
  text-shadow: 0 1px 3px rgba(7, 16, 32, 0.6);
}

body.v95-hour-dusk .v7-hero-title,
body.v95-hour-dusk .v7-hero-sub,
body.v95-hour-dusk .v7-hero-signature,
body.v95-hour-night .v7-hero-title,
body.v95-hour-night .v7-hero-sub,
body.v95-hour-night .v7-hero-signature {
  color: var(--v7-ivory-96);
  text-shadow: 0 1px 3px rgba(7, 16, 32, 0.85), 0 2px 30px rgba(7, 16, 32, 0.55);
}

body.v95-hour-dusk .v7-hero-signature,
body.v95-hour-night .v7-hero-signature {
  width: max-content;
  max-width: calc(100% - 32px);
  padding: 5px 16px;
  border-radius: 999px;
  background: rgba(7, 16, 32, 0.72);
}

body.v95-hour-dusk .v7-hero-glow,
body.v95-hour-night .v7-hero-glow {
  background:
    radial-gradient(
      ellipse 58% 46% at 50% 42%,
      rgba(13, 20, 33, 0.5) 0%,
      rgba(13, 20, 33, 0.36) 32%,
      rgba(13, 20, 33, 0.16) 58%,
      rgba(13, 20, 33, 0) 78%
    ),
    linear-gradient(
      to bottom,
      rgba(13, 20, 33, 0) 55%,
      rgba(13, 20, 33, 0.3) 78%,
      rgba(13, 20, 33, 0.52) 100%
    );
}

body.v95-hour-night .v7-hero-godray {
  filter: brightness(0.35);
}

body.v95-hour-night .v8-cloudshadow {
  display: none;
}

@media (prefers-reduced-motion: no-preference) {
  body.v95-hour-dusk .v7-hero-media.is-static ~ .v8-towers-wrap,
  body.v95-hour-night .v7-hero-media.is-static ~ .v8-towers-wrap {
    display: block;
  }
}

body.v95-hour-dusk .v8-towers-wrap,
body.v95-hour-night .v8-towers-wrap {
  mask-image: linear-gradient(to bottom, black 0%, black 68%, transparent 80%);
  -webkit-mask-image: linear-gradient(to bottom, black 0%, black 68%, transparent 80%);
}

/* ================= v21: night + principles — dark glass panel =================
   v17 removed background from these two rooms entirely (full-bleed room,
   local vignette only, see the old .v7-night::before / .v9-principles-scrim
   rules above, now retired) because a room-wide wash muddied the near-black
   tunnel/night footage running edge-to-edge behind it. v21 changes what a
   "room" IS: no longer a full-bleed band, but a CONTAINED floating panel
   with film visible on every side — so the concern that drove the v17
   removal (hiding motion across the whole viewport) no longer applies; only
   the panel's own contained area needs the tint, and the film outside it
   stays full clarity regardless of pointer type. Opted back into a real
   glass material — same family as .sp-theme-dark .sp-card
   (src/subpages.css), a slate-tinted translucent surface — using single-
   class selectors (not the .v10-room.v7-night compound the v17 rule used)
   so the touch-device flat-alpha fallback in the perf-fallback tier right
   below this naturally wins on specificity alone, same as range/ventures
   already do — no source-order gymnastics needed. Blur AND the gold-tinted
   border both come for free from the shared .landing-v7 .v10-room rule
   above (that rule's border color already suits a dark panel just as well
   as a light one, so it's left alone rather than duplicated at a
   specificity that couldn't win against it anyway). See
   docs/v21-floating-panels-spec.md for the measured contrast numbers. */
.v7-night,
.v7-principles {
  /* v21: alpha raised 0.66 -> 0.74 (on rgba(7,16,32,...)) so body copy
     cleared 4.5:1 against the golden-burst frame.
     v27: founder brief calls for "dark rooms → rgba(13,20,33, ~0.55)" as
     part of the same true-glass fix as the light rooms (lower fill,
     lighter room blur — 26px -> 14px, see the shared .v10-room rule
     above). Switched the base color from --v7-night (7,16,32) to --v7-ink
     (13,20,33) per that brief and dropped to 0.55; text contrast is no
     longer carried by this room-wide tint at all — it's carried locally
     by small-text zone scrims (v28: .v7-night-body-group::before /
     .v7-principle-rows::before; large-text h2s in these rooms clear 3:1
     unscrimmed — see docs/v28-invisible-scrims-spec.md, supersedes
     docs/v27-true-glass-spec.md's per-h2/per-row scrims). */
  background:
    linear-gradient(160deg, rgba(246, 243, 236, 0.08), rgba(246, 243, 236, 0.02)),
    rgba(13, 20, 33, 0.55);
}

/* v29: night room ONLY — fill raised 0.55 -> 0.68. This is the counterpart
   to retiring the .v7-night-body-group scrim box (see that note above):
   the small body/list copy now clears contrast on the panel's own uniform
   tint instead of a local radial patch that rendered as a visible
   rectangle. Night is the darkest beat in the film ("Into the dark",
   chapter III) so a denser slate here is on-concept, not a regression to a
   flat slab — the film still moves through it via the 14px blur. Scoped to
   .v10-room.v7-night (two classes) so it beats the shared single-class rule
   above without touching principles, and placed BEFORE the pointer:coarse
   fallback so touch still gets its own 0.82 flat fill. */
.v10-room.v7-night {
  background:
    linear-gradient(160deg, rgba(246, 243, 236, 0.08), rgba(246, 243, 236, 0.02)),
    rgba(13, 20, 33, 0.68);
}

/* ================= v15: performance fallback tier =================
   backdrop-filter compositing a blur over a *playing video* stage is
   measurably expensive on touch/mobile GPUs. Gated on `pointer: coarse`
   (touch hardware) rather than `@supports`, because every shipping engine
   here supports backdrop-filter — the problem is cost, not capability.
   Frame-time measurement (rAF sampling across a scripted full-page scroll,
   iPad emulation 1366x1024/isMobile/hasTouch/dsf2) decided this tier ships;
   see docs/v15-frosted-rooms-spec.md for the numbers. Swapping to a flat
   higher-opacity paper (no blur) keeps the continuity win — the stage's
   tint still bleeds through and still shifts room to room — while dropping
   the per-frame blur/compositing cost.
   Deliberately placed last in the cascade AND written with the two-class
   compound selectors below (.v10-room.v7-range, not just .v7-range) so this
   override wins on specificity too, not just source order — a rule this
   important shouldn't depend on staying at the bottom of the file forever. */
@media (pointer: coarse) {
  .landing-v7 .v10-room {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }

  /* v27: founder brief — "lower flat alpha to ~0.82 (from 0.93) so mobile
     also reads translucent." Applied the same directional fix across every
     touch-fallback surface this tier governs (rooms, dark rooms, cards) so
     touch/mobile isn't left on the old, more-opaque recipe while desktop
     moves to the new one. Re-measured against both the tunnel and
     golden-burst frames with blur off (the real touch condition — no blur
     means the flat alpha is compositing directly over an UNBLURRED frame,
     unlike desktop) — see docs/v27-true-glass-spec.md. */
  .v10-room.v7-range,
  .v10-room.v7-ventures {
    background: rgba(246, 243, 236, 0.82);
  }

  /* v21: night + principles carry a real blur now too (see the unconditional
     dark-glass rule further down this file) — same "blur over playing
     video" touch cost, same fallback shape: drop the blur (already handled
     by the unqualified .v10-room rule above), raise the flat alpha. This
     compound selector has the same specificity as that unconditional rule,
     so it only wins here because it's physically LATER in the file — the
     unconditional rule is placed before this media block specifically so
     this override, not that one, has the last word for touch devices.
     v27: alpha lowered 0.94 -> 0.82 (same fix as the light rooms above) and
     switched to the ink-toned (13,20,33) base to match the base-tint
     change above. */
  .v10-room.v7-night,
  .v10-room.v7-principles {
    background: rgba(13, 20, 33, 0.82);
  }

  /* v20: the range cards and the ventures stage panel are new backdrop-
     filter consumers this pass — same "blur over playing video" cost the
     tier above exists to avoid, just moved one layer down (card/panel
     instead of room). Same fallback shape: drop the blur, raise the flat
     alpha so the card/panel still reads as one solid, legible surface.
     v27: card alpha lowered 0.94 -> 0.82, same fix as the rooms. */
  .v7-lane {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    background:
      radial-gradient(circle at var(--mx) var(--my), rgba(255, 255, 255, var(--v7-glow-a)), transparent 42%),
      rgba(246, 243, 236, 0.82);
  }

  .v10-stage-panel-part {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    background: rgba(246, 243, 236, 0.06);
  }
}

/* ================= v19: universal focus-visible (a11y) =================
   Before this pass, only .v10-stage-cta / .v10-stage-row-part (above) had
   an explicit gold ring — every other interactive element (buttons, links,
   any future role="button"/tabindex custom control) fell back to the
   browser default outline. This rule is deliberately unqualified (no
   class/id prefix) so its specificity stays LOWER than the bespoke rule
   above; where the two overlap the bespoke rule still wins (same visual
   result either way, since both use var(--v7-gold)). Covers everything
   else in one shot. See docs/v19-fit-finish-spec.md. */
a:focus-visible,
button:focus-visible,
[role="button"]:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--v7-gold);
  outline-offset: 2px;
}

/* ================= Living hero ambient (flag-gated: body.v-living) =================
   Only active when the URL carries ?living=1 (or ?living=demo). The JS adds
   body.v-living and injects the .v-living-canvas elements; with neither the
   class nor those elements present by default, every rule below is inert, so
   the default page renders identically. These are ENVIRONMENTAL motions
   (weather, wildlife) on 40-84s cycles — deliberately exempt from the ≤300ms
   micro-motion law, exactly like the existing fog sway and hero breathe.
   Transform/opacity only. See src/landing-v10.js "Living hero ambient". */

/* CONTINUOUS tier: strengthen the existing fog + cloud-shadow drift slightly
   over the resting site — larger sway amplitude and a touch faster, still calm.
   New keyframes (amplitude lives in the keyframe, not the element). */
body.v-living .v7-hero-fog-back {
  animation: v-living-fog-back 66s ease-in-out infinite alternate;
}
body.v-living .v7-hero-fog-front {
  animation: v-living-fog-front 50s ease-in-out infinite alternate;
}
body.v-living .v8-cloudshadow {
  animation: v-living-shadow-drift 66s ease-in-out infinite alternate;
}
@keyframes v-living-fog-back {
  from { transform: translateX(-5.4%); }
  to { transform: translateX(5.4%); }
}
@keyframes v-living-fog-front {
  from { transform: translateX(4.4%); }
  to { transform: translateX(-4.4%); }
}
@keyframes v-living-shadow-drift {
  from { transform: translateX(-6.5%) translateY(0.8%); }
  to { transform: translateX(7.5%) translateY(-1.2%); }
}

/* Injected ambient canvases. .v-living-far (z -5) carries the distant
   seaplane/balloon: only the back fog shares z -5, so appending this canvas
   last paints it just above the far fog while the front fog (z -4) still veils
   it — depth-correct "far". .v-living-near (z 4) carries the periodic flock,
   co-planar with the resting birds, in front of the towers. */
.v-living-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
.v-living-far {
  z-index: -5;
}
.v-living-near {
  z-index: 4;
}

/* Reduced motion: the JS already never starts the ambient system, but belt-and
   suspenders — kill the boosted drift too if the class is ever present. */
@media (prefers-reduced-motion: reduce) {
  body.v-living .v7-hero-fog-back,
  body.v-living .v7-hero-fog-front,
  body.v-living .v8-cloudshadow {
    animation: none;
  }
}
