/* ==========================================================================
   UNI-X — Design system
   Source of truth: UNI-X_WEBSITE_HANDOFF_v1.0/02_machine_readable/design_tokens.json
   Fonts: Geist Sans / Geist Mono, self-hosted (SIL OFL 1.1 — fonts/geist/LICENSE.txt)
   ========================================================================== */

@font-face {
  font-family: 'Geist Sans';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/geist/Geist-Regular.woff2') format('woff2');
}
@font-face {
  font-family: 'Geist Sans';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/geist/Geist-Medium.woff2') format('woff2');
}
@font-face {
  font-family: 'Geist Sans';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('fonts/geist/Geist-SemiBold.woff2') format('woff2');
}
@font-face {
  font-family: 'Geist Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/geist/GeistMono-Regular.woff2') format('woff2');
}
@font-face {
  font-family: 'Geist Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('fonts/geist/GeistMono-Medium.woff2') format('woff2');
}

:root {
  /* Living Black */
  --bg: #050505;
  --bg-deep: #0A0B0D;
  --bg-graphite: #111317;
  --bg-smoke: #171A1F;

  /* Warm White */
  --white: #F5F3EE;
  --white-2: #F2F2EF;
  --white-structural: #E5E2DA;
  --white-faint: #CBC7BD;

  /* Burning Orange — life. Selection / collect / valuation / MemeKick only. */
  --orange: #F36A21;
  --orange-bright: #FF7A2F;
  --orange-deep: #D95416;

  --muted: rgba(245, 243, 238, 0.72);
  --tertiary: rgba(245, 243, 238, 0.45);
  --system: rgba(245, 243, 238, 0.22);
  --ghost: rgba(245, 243, 238, 0.08);

  --font-sans: 'Geist Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --font-mono: 'Geist Mono', ui-monospace, 'SF Mono', Menlo, monospace;

  --pad: clamp(20px, 5.55vw, 80px);
  --content-max: 1280px;

  --t-normal: 700ms;
  --t-structural: 1300ms;
  --ease: cubic-bezier(0.16, 1, 0.3, 1);

  /* Current viewport height in px, kept in sync with window.innerHeight by
     main.js (on load AND on resize) — see its comment for why the sticky
     scenes need this instead of the `vh` unit. 100vh here is only a
     pre-JS/no-JS fallback. */
  --vh1: 100vh;
}

*, *::before, *::after { box-sizing: border-box; }

html {
  background: var(--bg);
  color: var(--white);
  scroll-behavior: smooth;
  scroll-snap-type: y proximity;
}

body {
  margin: 0;
  background: var(--bg);
  font-family: var(--font-sans);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

a { color: inherit; text-decoration: none; }

/* Visible keyboard focus everywhere — see REVISION v1.1 P1-03. Warm White by
   default; Orange only where an interactive accent is already appropriate
   (the mobile menu toggle, which is otherwise a quiet mono-label button). */
a:focus-visible, button:focus-visible {
  outline: 1px solid var(--white);
  outline-offset: 4px;
}
.nav-menu-toggle:focus-visible { outline-color: var(--orange); }

h1, h2, h3, p, ol, ul { margin-top: 0; }

h1 {
  font-size: clamp(54px, 6.1vw, 104px);
  line-height: 0.98;
  letter-spacing: -0.035em;
  font-weight: 500;
  margin-bottom: 34px;
}
h2 {
  font-size: clamp(46px, 5vw, 76px);
  line-height: 1;
  letter-spacing: -0.03em;
  font-weight: 500;
}
h3 {
  font-size: clamp(26px, 3vw, 46px);
  line-height: 1.08;
  font-weight: 500;
}
p {
  font-size: clamp(17px, 1.45vw, 21px);
  line-height: 1.5;
  color: var(--muted);
}
p.large { font-size: clamp(22px, 2vw, 30px); }

.eyebrow {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  color: var(--tertiary);
}

/* ---------- Progressive phases (Scene 02/03) ----------
   A .phase is hidden until scroll progress through its owning sticky-scene
   crosses data-phase-from (and, if set, leaves once past data-phase-until).
   See main.js setupPhases(). Content stays in the DOM throughout (a11y/SEO);
   only its visibility is progressive. */
.phase { opacity: 0; transform: translateY(14px); transition: opacity var(--t-normal) var(--ease), transform var(--t-normal) var(--ease); }
.phase.is-active { opacity: 1; transform: translateY(0); }
/* THAT IS NOT THE PROBLEM.: a beat of stillness, not a slide-up like the rest. */
.phase-pivot { transform: none; }

/* ---------- The cultural object ----------
   One real MemeKick meme frame (images/product/object.webp) stands for the
   selected object in Scene 03 and the archive's centre in Scene 04 — never
   AI-generated stock/SF imagery (ASSET_REQUIREMENTS.md). */
.object-shot { display: block; width: 100%; height: 100%; object-fit: cover; }

/* ---------- Header ---------- */
.site-header {
  position: fixed;
  z-index: 50;
  top: 0; left: 0; right: 0;
  height: 80px;
  padding: 0 var(--pad);
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* Fully opaque, not a fade-to-transparent gradient and not translucent:
     a sticky-scene's content can end up right behind the header at any
     point during its pinned scroll (not just at page load), and anything
     less than full opacity let large headline text bleed/ghost through
     (even blurred) and visually collide with the header's own text. */
  background: var(--bg);
  pointer-events: none;
}
.site-header > * { pointer-events: auto; }
/* The scroll "recede" effect (see main.js) fades the header's own CONTENT
   only, via this class — never the header element's opacity, which would
   fade its solid background back out and reopen the bleed-through bug
   above. */
.site-header > * { transition: opacity var(--t-normal) var(--ease); }
.site-header.is-receded > * { opacity: 0.78; }
.wordmark {
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.site-header nav { display: flex; align-items: center; gap: 32px; font-size: 14px; color: var(--muted); }
.site-header nav a { transition: color var(--t-normal) var(--ease); }
.site-header nav a:hover { color: var(--white); }
/* Real button, not inert text — see REVISION v1.1 P0-07. Hidden on desktop
   (nav links are already visible there); shown only under the 800px
   breakpoint, where it opens/closes #mobileNav (see main.js setupMobileNav()). */
.nav-menu-toggle {
  display: none;
  font: 10px var(--font-mono);
  letter-spacing: 0.08em;
  color: var(--muted);
  background: none;
  border: 1px solid var(--system);
  border-radius: 4px;
  padding: 8px 12px;
  cursor: pointer;
}
.nav-menu-toggle[aria-expanded="true"] { color: var(--white); border-color: var(--muted); }

/* ---------- Mobile nav panel ---------- */
.mobile-nav {
  position: fixed;
  z-index: 60;
  top: 68px; right: 12px;
  height: auto;
  width: min(56vw, 220px);
  background:
    linear-gradient(135deg, rgba(255, 255, 255, 0.16) 0%, rgba(255, 255, 255, 0.05) 45%, rgba(255, 255, 255, 0.08) 100%);
  -webkit-backdrop-filter: blur(24px) saturate(180%) brightness(1.15);
  backdrop-filter: blur(24px) saturate(180%) brightness(1.15);
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: 14px;
  box-shadow:
    0 16px 40px rgba(0, 0, 0, 0.55),
    inset 0 1px 0 rgba(255, 255, 255, 0.35),
    inset 0 -1px 0 rgba(255, 255, 255, 0.06);
  display: flex;
  flex-direction: column;
  padding: 6px 20px;
  transform: translateX(calc(100% + 12px));
  transition: transform var(--t-normal) var(--ease);
}
.mobile-nav[data-open="true"] { transform: translateX(0); }
.mobile-nav a {
  font-size: 16px;
  letter-spacing: 0.02em;
  color: var(--white);
  padding: 14px 0;
  opacity: 0;
  transform: translateX(8px);
  transition: opacity var(--t-normal) var(--ease), transform var(--t-normal) var(--ease), color 0.15s;
}
.mobile-nav a + a { border-top: 1px solid rgba(255, 255, 255, 0.14); }
.mobile-nav[data-open="true"] a { opacity: 1; transform: none; }
.mobile-nav[data-open="true"] a:nth-child(1) { transition-delay: 60ms; }
.mobile-nav[data-open="true"] a:nth-child(2) { transition-delay: 100ms; }
.mobile-nav[data-open="true"] a:nth-child(3) { transition-delay: 140ms; }
.mobile-nav[data-open="true"] a:nth-child(4) { transition-delay: 180ms; }
.mobile-nav a:active { color: var(--orange); }
.mobile-nav a:focus-visible { outline: 1px solid var(--white); outline-offset: 4px; }

/* ---------- Scene shell ---------- */
/* overflow: clip, not hidden — hidden makes .scene a scroll container, and a
   position:sticky descendant then sticks to that (never-scrolling) box
   instead of the viewport, so .sticky-stage never pinned at all. The hidden
   line is only a fallback for engines without clip. */
.scene { position: relative; min-height: var(--vh1); padding: 120px var(--pad) 80px; overflow: hidden; overflow: clip; }
/* No block padding on pinned scenes: .sticky-stage carries its own, and
   section padding would start the pin 120px after main.js's stageProgress()
   starts counting from the section top. */
.sticky-scene { padding-top: 0; padding-bottom: 0; }
.snap { scroll-snap-align: start; }
/* calc(var(--vh1) * N), not `Nvh` — see the --vh1 definition above: both
   sides of the sticky-pin math (this and .sticky-stage's height) must track
   the SAME live viewport height, or the pin releases at the wrong scroll
   position and leaves a blank gap. */
.tall { min-height: calc(var(--vh1) * 1.8); }
.extra-tall { min-height: calc(var(--vh1) * 2.2); }
.sticky-stage {
  position: sticky;
  top: 0;
  /* Fixed, not min-height: a box taller than one viewport breaks the sticky
     pin's own math (it releases before the section's scroll range ends),
     leaving a dead blank gap in the scroll. overflow:hidden clips the rare
     child that would otherwise force this box taller instead of reopening
     that gap; the min()-clamped children below (.signal-field,
     .archive-diagram, .product-frame) keep clipping from actually
     happening at normal sizes. */
  height: var(--vh1);
  overflow: hidden;
  padding: 120px 0 80px;
  display: grid;
  /* `safe center`, not `center`: .copy can hold several .phase/.feed-phase/
     .archive-copy blocks at once (all stay in the DOM per the a11y comment
     below, only their opacity is progressive — see main.js), so its full
     height is often taller than this box's own content area. Plain `center`
     overflows equally on both edges, pushing the top (e.g. Scene 03's "NEXT"
     h2) up past the 120px padding and behind the fixed .site-header. `safe`
     falls back to start-alignment whenever centering would overflow, so the
     top always respects the padding instead of disappearing behind the
     header. */
  align-content: safe center;
}
/* Set by main.js updatePinning() when a scene's content is taller than one
   screen: the scene scrolls normally instead of pinning and clipping. */
.sticky-scene.is-unpinned { min-height: auto; }
.sticky-scene.is-unpinned .sticky-stage { position: relative; height: auto; overflow: visible; }
.copy { position: relative; z-index: 4; max-width: 820px; }
.copy.wide { max-width: var(--content-max); margin: auto; }

/* Reveal-on-scroll: content is always present in the DOM (a11y/SEO), motion is progressive enhancement */
.reveal { opacity: 0; transform: translateY(28px); transition: opacity var(--t-normal) var(--ease), transform var(--t-normal) var(--ease); }
.reveal.is-visible { opacity: 1; transform: translateY(0); }

/* ---------- Scene 01 — Hero ---------- */
.hero { display: flex; align-items: center; }
.hero-copy { margin-left: 2.8vw; }
.explore {
  display: inline-block;
  margin-top: 28px;
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.08em;
}
.hero-signals i {
  position: absolute;
  width: 3px; height: 3px;
  border-radius: 50%;
  background: var(--white);
  opacity: 0.4;
}
.hero-signals i:nth-child(1) { right: 18%; top: 30%; }
.hero-signals i:nth-child(2) { right: 28%; bottom: 24%; }
.hero-signals i:nth-child(3) { right: 10%; bottom: 42%; }

/* ---------- Scene 02 — Creation is becoming abundant ---------- */
.creation-stage { grid-template-columns: minmax(0, 1fr) minmax(360px, 0.8fr); gap: 7vw; }
.sequence { margin: 38px 0; }
.sequence p { margin: 4px 0; }
.creation-stage h3 { margin-top: 8vh; }
.mono-row {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  display: flex;
  gap: 34px;
  margin-top: 32px;
  color: var(--muted);
  flex-wrap: wrap;
}
/* Abundance: a signal field that multiplies as the section pins — see main.js */
.signal-field { min-height: min(520px, calc(var(--vh1) * 0.52)); position: relative; opacity: 0.85; }
.signal-field i {
  position: absolute;
  border-radius: 50%;
  background: var(--white);
  transition: opacity var(--t-structural) var(--ease), transform var(--t-structural) var(--ease);
  opacity: 0;
  transform: scale(0.4);
}
.signal-field i.is-active { opacity: var(--dot-opacity, 0.5); transform: scale(1); }
.signal-field i.is-orange { background: var(--orange); }

/* ---------- Scene 03 — Feed to Living Archive ---------- */
.split { grid-template-columns: minmax(0, 0.8fr) minmax(380px, 1.2fr); gap: 8vw; }
.flow { font-family: var(--font-mono); font-size: 14px; line-height: 1.8; }
/* --vh1-scaled gaps (were 10vh / 9vh): the feed copy and the archive copy
   share one pinned stage, and the larger gaps overflowed it on every
   viewport height, clipping the archive copy's last line. */
.question { font-size: clamp(34px, 3.5vw, 54px); line-height: 1.05; color: var(--white); margin-top: calc(var(--vh1) * 0.04); }
/* Recedes (dims, doesn't vanish) once the Living Archive copy takes over —
   see REVISION v1.1 P0-03: the two economies must not read with equal
   strength at the same time. */
.feed-phase { transition: opacity var(--t-structural) var(--ease); }
.feed-phase.is-receded { opacity: 0.22; }
.archive-copy { margin-top: calc(var(--vh1) * 0.04); }
.archive-line { margin-top: 18px; }

/* Feed: items pass (translateY) then attention metadata flashes and disappears.
   .feed-column stacks the passing feed above the object that stays selected. */
.feed-column {
  display: flex;
  flex-direction: column;
  gap: 20px;
  align-self: center;
  justify-self: center;
  /* Capped by vh too: feed-stage + culture-object stack vertically here, so
     an uncapped width (-> uncapped culture-object height, since it's a
     square) can make the column taller than the sticky-stage's budget —
     see the .sticky-stage comment on why that must not happen. */
  width: min(34vw, 430px, calc(var(--vh1) * 0.34));
}
.feed-stage {
  position: relative;
  width: 100%;
  min-height: 160px;
}
.feed-item {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  border: 1px solid var(--system);
  font-family: var(--font-mono);
  color: var(--muted);
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 900ms var(--ease), transform 900ms var(--ease);
}
.feed-item.is-passing { opacity: 1; transform: translateY(0); }
.feed-item .attention {
  position: absolute;
  top: 14px;
  right: 14px;
  font-size: 10px;
  color: var(--orange-bright);
  opacity: 0;
  transition: opacity 400ms var(--ease);
}
.feed-item.is-passing .attention { opacity: 1; animation: attention-flash 900ms var(--ease) forwards; }
@keyframes attention-flash {
  0% { opacity: 0; }
  40% { opacity: 1; }
  100% { opacity: 0; }
}
/* Selection: one object stops and gains the orange ring; feed items above stop being generated */
.culture-object {
  position: relative;
  width: 100%;
  aspect-ratio: 1;
  border: 1px solid var(--system);
  display: grid;
  place-items: center;
  color: var(--muted);
  font-family: var(--font-mono);
  opacity: 0;
  transition: opacity var(--t-structural) var(--ease);
}
.culture-object.is-selected { opacity: 1; }
.selected-ring {
  position: absolute;
  inset: -42px;
  border: 1px solid rgba(243, 106, 33, 0.28);
  border-radius: 50%;
  box-shadow: 0 0 50px rgba(243, 106, 33, 0.06);
  opacity: 0;
  transition: opacity var(--t-structural) var(--ease);
}
.culture-object.is-selected .selected-ring { opacity: 1; }

/* ---------- Scene 04 — The Living Archive ---------- */
.archive-stage { grid-template-columns: minmax(0, 0.75fr) minmax(480px, 1.25fr); gap: 7vw; }
.archive-diagram {
  position: relative;
  width: min(48vw, 620px, calc(var(--vh1) * 0.52));
  aspect-ratio: 1;
  justify-self: center;
  border: 1px solid rgba(245, 243, 238, 0.06);
  border-radius: 50%;
}
.archive-diagram::before, .archive-diagram::after {
  content: "";
  position: absolute;
  inset: 16%;
  border: 1px solid rgba(245, 243, 238, 0.1);
  border-radius: 50%;
}
.archive-diagram::after { inset: 32%; }
/* Draws one line per principle from the object to its label — see REVISION
   v1.1 P0-05: each principle is a distinct visual action, not just a label
   fading in. Lines are drawn (stroke-dashoffset -> 0) via main.js
   setupScene04(), which reads each path's own data-from threshold — the
   same threshold its .principle-label uses, so the line and the words for
   a principle appear together. */
.archive-lines { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.archive-lines .line {
  fill: none;
  stroke: rgba(245, 243, 238, 0.3);
  stroke-width: 0.4;
  transition: stroke-dashoffset var(--t-structural) var(--ease);
}
.archive-lines .line-valuation { stroke: rgba(243, 106, 33, 0.4); }
.archive-lines .line-discovery { stroke-width: 0.5; }
.archive-lines .line-continuity { stroke-dasharray: 1.2 1.6; }
.node {
  position: absolute;
  left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  width: 150px;
  aspect-ratio: 1;
  border: 1px solid rgba(245, 243, 238, 0.35);
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-family: var(--font-mono);
  text-align: center;
  z-index: 1;
  overflow: hidden;
}
.principle-label {
  position: absolute;
  font: 11px/1.3 var(--font-mono);
  letter-spacing: 0.06em;
  color: var(--muted);
  max-width: 140px;
  opacity: 0;
  transition: opacity var(--t-normal) var(--ease);
}
.principle-label.is-visible { opacity: 1; }
.principle-label small { display: block; color: var(--tertiary); font-size: 10px; margin-top: 2px; }
.p-origin { left: 1%; top: 45%; }
.p-context { left: 16%; bottom: 8%; }
.p-selection { left: 42%; bottom: -2%; }
.p-valuation { right: 14%; bottom: 8%; }
.p-lineage { right: 0%; top: 44%; }
.p-discovery { left: 42%; top: -2%; }
.p-continuity { right: 6%; top: 70%; }
.p-valuation b { color: var(--orange); font-size: 18px; font-weight: 500; display: block; }

/* ---------- Scene 05 — Create / Curate / Collect ---------- */
.roles { display: flex; align-items: center; }
.role-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 28px; margin-top: 56px; }
.role-grid article > span { font: 11px var(--font-mono); color: var(--muted); letter-spacing: 0.06em; }
.state-box {
  height: 230px;
  margin: 18px 0 22px;
  border: 1px solid rgba(245, 243, 238, 0.12);
  display: grid;
  place-items: center;
  font: 12px var(--font-mono);
  color: var(--muted);
  letter-spacing: 0.06em;
}
.state-box.active { border-color: rgba(243, 106, 33, 0.38); box-shadow: inset 0 0 80px rgba(243, 106, 33, 0.04); }
.role-grid h3 { font-size: 28px; }
.role-transition { margin-top: 28px; font: 12px var(--font-mono); letter-spacing: 0.08em; color: var(--muted); }
.valuation {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: end;
  gap: 60px;
  margin-top: 10vh;
  padding-top: 54px;
  border-top: 1px solid rgba(245, 243, 238, 0.12);
}
.value strong { display: block; font-size: clamp(86px, 9vw, 130px); line-height: 0.8; font-weight: 500; }
.value small { display: block; margin-top: 22px; font: 11px/1.6 var(--font-mono); color: var(--muted); letter-spacing: 0.04em; }

/* ---------- Scene 06 — MemeKick ---------- */
/* min-content, not a fixed 300px floor: the MEMEKICK headline is a single
   unbreakable word, and when it was wider than the column it spilled over
   the product frame beside it. The column now grows to fit the word. */
.memekick-stage { grid-template-columns: minmax(min-content, 0.55fr) minmax(0, 1.45fr); gap: 6vw; }
.memekick-stage h2 { font-size: clamp(44px, 4.6vw, 84px); }
.product-steps {
  list-style: none;
  padding: 0;
  margin: 58px 0 0;
  display: flex;
  gap: 20px;
  font: 11px var(--font-mono);
  color: var(--muted);
  letter-spacing: 0.06em;
  flex-wrap: wrap;
}
.product-steps li.is-active { color: var(--orange); }
.memekick-body { margin-top: 20px; max-width: 46ch; }
/* Neutral product-proof stage — no stock/SF video or imagery (REVISION v1.1
   P0-01). Each .frame-state is one step of MEME -> TITLE -> COLLECT ->
   ARCHIVE -> BRANCH, cross-fading in sync with .product-steps (main.js). */
.product-frame {
  position: relative;
  align-self: center;
  min-height: min(620px, calc(var(--vh1) * 0.52));
  border: 1px solid rgba(245, 243, 238, 0.1);
  background: linear-gradient(145deg, rgba(243, 106, 33, 0.035), transparent 35%);
  overflow: hidden;
}
.frame-state {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  padding: 24px 40px;
  text-align: center;
  opacity: 0;
  transition: opacity var(--t-structural) var(--ease);
}
.frame-state.is-active { opacity: 1; }
/* Real app screenshot for the step; height-led so it always leaves room
   for the caption inside the frame. */
.frame-shot { flex: 0 1 auto; min-height: 0; height: calc(100% - 40px); width: auto; max-width: 100%; object-fit: contain; border-radius: 16px; }
.frame-caption { font: 11px var(--font-mono); letter-spacing: 0.05em; color: var(--white); }

/* BRANCH step: the origin and its plays, revealed one by one as the scroll
   passes each data-lineage-from threshold (main.js) — the lineage grows
   outward instead of appearing as a single screen. */
.lineage-chain {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  gap: 22px;
  width: 100%;
  height: calc(100% - 40px);
  justify-content: center;
}
.lineage-shot {
  position: relative;
  flex: 0 1 auto;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  opacity: 0;
  transform: translateX(-14px);
  transition: opacity var(--t-normal) var(--ease), transform var(--t-normal) var(--ease);
}
.lineage-shot.is-shown { opacity: 1; transform: none; }
.lineage-shot img { flex: 1 1 auto; min-height: 0; max-width: 100%; width: auto; object-fit: contain; }
.lineage-shot span { font: 10px var(--font-mono); letter-spacing: 0.06em; color: var(--muted); }
.lineage-shot:first-child span { color: var(--orange); }
/* The link back to the previous meme in the chain. */
.lineage-shot + .lineage-shot::before {
  content: "→";
  position: absolute;
  left: -19px;
  top: 45%;
  font: 12px var(--font-mono);
  color: var(--orange);
}

/* ---------- Scene 07 — The Loop ----------
   A spatial, open-ended loop (REVISION v1.1 P0-06), not a text row: the
   arc draws itself as the user scrolls, each node lights up as the arc
   reaches it, and a new branch forms off CREATE AGAIN — the same visual
   grammar Scene 04 uses for LINEAGE. Driven by main.js setupScene07(). */
.loop-stage { text-align: center; }
.loop-stage .copy { margin: auto; max-width: 1100px; }
.loop-diagram { position: relative; width: min(46vw, 420px, calc(var(--vh1) * 0.3)); aspect-ratio: 1; margin: calc(var(--vh1) * 0.025) auto; }
.loop-diagram svg { width: 100%; height: 100%; overflow: visible; }
/* No transition on stroke-dashoffset: unlike .archive-lines .line (a
   threshold-triggered two-state draw), main.js scrubs this arc continuously
   1:1 with scroll progress every frame — an eased transition can never
   catch up to a target that keeps moving every ~16ms, so while scrolling
   forward the arc appears stuck near its start and only visibly "connects"
   once scrolling stops or reverses long enough (1300ms) for it to settle. */
.loop-arc { fill: none; stroke: rgba(245, 243, 238, 0.3); stroke-width: 1; }
.loop-center { fill: none; stroke: rgba(245, 243, 238, 0.35); stroke-width: 1; }
.loop-node circle { fill: var(--bg); stroke: rgba(245, 243, 238, 0.4); stroke-width: 1; }
.loop-node { opacity: 0; transition: opacity var(--t-normal) var(--ease); }
.loop-node.is-active { opacity: 1; }
.loop-node.is-active circle { fill: var(--orange); stroke: none; }
.loop-branch, .loop-branch-node { opacity: 0; transition: opacity var(--t-structural) var(--ease); }
.loop-branch { fill: none; stroke: var(--orange); stroke-width: 1; }
.loop-branch-node { fill: var(--orange); }
.loop-branch.is-active, .loop-branch-node.is-active { opacity: 1; }
.loop-label {
  position: absolute;
  font: 11px var(--font-mono);
  letter-spacing: 0.06em;
  color: var(--muted);
  opacity: 0;
  transition: opacity var(--t-normal) var(--ease);
  transform: translate(-50%, -50%);
}
.loop-label.is-active { opacity: 1; color: var(--white); }
.loop-label-archive { left: 50%; top: 2%; }
.loop-label-discover { left: 108%; top: 50%; }
.loop-label-reinterpret { left: 50%; top: 98%; }
.loop-label-create { left: 2%; top: 86%; }
.loop-copy { margin-top: 2vh; }
/* Every vertical size in this scene scales with --vh1: the whole .copy has to
   fit inside one pinned .sticky-stage, and with width-only sizing it ran
   ~940px tall — everything below the diagram was clipped off the bottom. */
.living {
  font-size: min(clamp(86px, 10vw, 150px), calc(var(--vh1) * 0.1));
  line-height: 1;
  color: var(--white);
  text-shadow: 0 0 50px rgba(243, 106, 33, 0.11);
  margin: calc(var(--vh1) * 0.025) 0 0;
}
.final-line { margin: calc(var(--vh1) * 0.03) 0 0; }

/* ---------- Scene 08 — Uni-X returns ---------- */
.company { display: flex; align-items: center; }
.lead { font-size: clamp(25px, 2.7vw, 38px); color: var(--white); }
.final-question { max-width: 1050px; margin: 9vh 0 6vh; }
.company-closing { margin-top: 6vh; color: var(--white); }

/* ---------- Signals ----------
   Non-interactive editorial rows, not placeholder <a href="#"> links — see
   REVISION v1.1 P1-02. Becomes a real <a> once each Signal has a real URL. */
.signals-index { padding: 80px var(--pad) 120px; }
.signals-index > .signals-row {
  max-width: var(--content-max);
  margin: auto;
  min-height: 82px;
  border-top: 1px solid rgba(245, 243, 238, 0.13);
  display: grid;
  grid-template-columns: 50px 1fr;
  align-items: center;
  font: 12px var(--font-mono);
  color: var(--muted);
  letter-spacing: 0.04em;
}
.signals-index > .signals-row:last-child { border-bottom: 1px solid rgba(245, 243, 238, 0.13); }
.signals-index b { font: 500 17px var(--font-sans); color: var(--white); }
.signals-index p { grid-column: 2; margin-top: 4px; font-size: 13px; color: var(--tertiary); }

/* ---------- Footer ---------- */
/* container-type: the giant wordmark sizes itself from the footer's content
   width (cqw), so it always fills it. */
footer { padding: 90px var(--pad) 0; overflow: hidden; container-type: inline-size; }
.footer-top { display: grid; grid-template-columns: 1fr 1fr; gap: 80px; padding-bottom: 80px; }
.footer-top p { max-width: 360px; margin-top: 24px; }
.footer-links { display: grid; grid-template-columns: repeat(2, minmax(100px, 160px)); gap: 16px 48px; align-content: start; font-size: 14px; }
.footer-links a:hover { color: var(--white); }
.footer-meta {
  padding: 24px 0;
  border-top: 1px solid rgba(245, 243, 238, 0.12);
  display: flex;
  justify-content: space-between;
  color: var(--muted);
  font: 11px var(--font-mono);
  gap: 12px;
  flex-wrap: wrap;
}
/* Centered in the footer. "UNI-X" is 2.462em of ink at this weight and
   tracking, so 100cqw / 2.8 makes the ink ~88% of the content width
   (deliberately a bit smaller than full-bleed). No translateY and a little
   bottom padding, so the letters sit fully inside the footer instead of
   being clipped by its overflow. */
.giant-wordmark {
  font-size: calc(100cqw / 2.8);
  text-align: center;
  line-height: 0.8;
  padding-bottom: 0.06em;
  font-weight: 500;
  letter-spacing: -0.055em;
  white-space: nowrap;
  color: var(--white);
  margin-top: 7vh;
}

/* ==========================================================================
   Mobile — 390px reference (see RESPONSIVE_SPEC.md). Cinematic but efficient.
   ========================================================================== */
@media (max-width: 800px) {
  :root { --pad: 20px; }
  html { scroll-snap-type: y proximity; }

  .site-header { height: 68px; }
  .site-header nav { gap: 0; }
  .site-header nav a { display: none; }
  .nav-menu-toggle { display: block; }

  .scene { padding-top: 96px; min-height: auto; }
  .hero { min-height: 100svh; }
  .hero-copy { margin-left: 0; }
  h1 { font-size: clamp(44px, 12vw, 58px); }

  .tall, .extra-tall { min-height: auto; }
  .sticky-stage { position: relative; height: auto; overflow: visible; padding: 90px 0; }
  .creation-stage, .split, .archive-stage, .memekick-stage { grid-template-columns: 1fr; }

  .signal-field { min-height: 280px; order: -1; }
  .feed-column { width: 78vw; margin: 0 auto; }
  .feed-stage { min-height: 180px; }
  .archive-diagram { width: 88vw; margin: 60px auto; }

  .role-grid { grid-template-columns: 1fr; }
  .state-box { height: 160px; }
  .valuation { grid-template-columns: 1fr; }

  .product-frame { min-height: 56vh; margin-top: 48px; }
  .product-steps { flex-wrap: wrap; }
  .frame-state { padding: 24px; }

  .loop-diagram { width: 78vw; }

  .signals-index > .signals-row { grid-template-columns: 34px 1fr; gap: 8px; }

  .footer-top { grid-template-columns: 1fr; }
  .footer-meta { flex-direction: column; gap: 10px; }

  .mono-row { flex-wrap: wrap; }
  .question { margin-top: 50px; }
}

@media (max-width: 400px) {
  h1 { font-size: 42px; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; scroll-snap-type: none; }
  * , *::before, *::after { animation: none !important; transition: none !important; }
  .reveal, .phase { opacity: 1 !important; transform: none !important; }
  .signal-field i, .culture-object, .selected-ring, .principle-label, .feed-item,
  .feed-phase, .archive-lines .line, .frame-state, .lineage-shot, .loop-node, .loop-branch, .loop-branch-node, .loop-label {
    opacity: 1 !important;
    transform: none !important;
  }
  /* Every step's static state at once is more useful without motion than
     a cross-fade frozen mid-transition would be. */
  .archive-lines .line { stroke-dashoffset: 0 !important; }
  .mobile-nav, .mobile-nav a { transition: none !important; }
}
