:root {
  --black: #000000;
  --white: #ffffff;
  --magenta: #ff00ff;
  --serif: "Playfair Display SC", Georgia, "Times New Roman", serif;
  --sans: -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 100%;
  background: var(--white);
  color: var(--black);
  font-family: var(--sans);
  -webkit-font-smoothing: antialiased;
}

button { font-family: inherit; }

/* ---------- screens ---------- */

.screen {
  display: none;
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  /* dvh tracks the real visible viewport (keyboard, browser chrome);
     falls back to the line above on browsers that don't support it —
     invalid values are ignored, not applied, so this is additive only. */
  height: 100dvh;
}

.screen--active { display: block; }

.screen--chrome { background: var(--white); }

/* ---------- top / bottom bars ---------- */

.bar {
  position: fixed;
  left: 0;
  right: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 20px 28px;
  z-index: 5;
}

.bar--top { top: 0; }
.bar--bottom { bottom: 0; }

.mark {
  display: block;
  width: 28px;
  height: 28px;
  border-radius: 3px;
}

.mark--ghost {
  width: 30px;
  height: 30px;
  border-radius: 0;
}

.wordmark {
  font-family: var(--serif);
  color: var(--magenta);
  font-size: 14px;
  letter-spacing: 0.05em;
}

/* C9.5: a real Home link, but only where it's actually safe (Gate, Send,
   Mirror — never Compose or Release, where it could abandon an active
   composition or an unpaid resolution). Reset to look identical to the
   plain .wordmark span it replaces; only cursor/hover signal it's live. */
/* Real bug, found 2026-09-04: `font: inherit` is a shorthand that resets
   font-family/size/weight together, not just the button-chrome
   properties this rule actually wants to reset — it was silently
   overwriting .wordmark's own serif/14px with whatever the <header>
   inherits (a plain system sans-serif at 16px), so the button variant of
   this wordmark (Gate, Send, Mirror) rendered visibly different from the
   plain-<span> variant (Compose) despite sharing the same .wordmark
   class. Resetting only what needs resetting fixes it without fighting
   .wordmark's own declarations. */
button.wordmark--nav {
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  cursor: pointer;
  transition: opacity 0.18s ease;
}
button.wordmark--nav:hover,
button.wordmark--nav:focus-visible {
  opacity: 0.7;
}

.coords, .time {
  font-family: var(--sans);
  font-size: 12px;
  letter-spacing: 0.04em;
  color: var(--black);
  font-variant-numeric: tabular-nums;
}

/* dark chrome variant — gate screen */
.screen--dark { background: var(--black); }
.screen--dark .coords,
.screen--dark .time { color: var(--white); }

/* ---------- home (1-A port) — black instrument, scoped to #screen-home
   only so the Mirror screen (which shares .bar/.wordmark/.mark) is
   untouched ---------- */

#screen-home { background: var(--black); }
#screen-home .home-line { color: var(--white); }
#screen-home .home-main {
  gap: clamp(20px, 4vw, 56px);
  overflow-y: auto;
  /* Home-as-product pass: the old fixed upward shift (tuned for a
     wordmark/tagline/button-only layout) clipped the wordmark's own top
     edge off-screen the moment real copy made this column taller —
     there was no scroll to recover it. Dropped rather than re-tuned:
     with six lines of real content, true vertical centering (plus a
     scroll fallback for genuinely short viewports) is more honest than
     another fixed offset that will just break again at the next content
     change. */
}

#screen-home .home-line--wordmark {
  font-family: var(--serif);
  font-weight: 700;
  color: var(--magenta);
  /* Demoted 2026-09-04: this used to be the dominant spatial fact of the
     page (clamp up to 112px) with the actual line sized as an
     afterthought underneath it — backwards hierarchy for a page whose
     one job is the line, not the brand name. Now matches the same quiet
     register every other room's own header wordmark already uses
     (.wordmark, 14px) instead of a unique giant treatment only Home had. */
  font-size: 14px;
  letter-spacing: 0.05em;
  max-width: calc(100vw - 48px);
  overflow-wrap: normal;
}

/* Room 1 visual-weight tweak (2026-09-08): the CTA as the screen's own
   center of gravity, without competing with the headline above it —
   text size and border/padding nudged up slightly, and the rim goes
   magenta at rest (not just on hover, per the existing rule just below).
   Scoped to #screen-home only, same reasoning as that rule: Room 4's
   own Release button shares .btn-conduct and must render exactly as it
   already does. */
#screen-home .btn-conduct {
  font-size: 20px;
  border-width: 3px;
  border-color: var(--magenta);
  padding: 24px 44px;
}

/* 1-A final cut: center stays black on hover — only the rim and label
   go magenta. Scoped to Home only; Gate/Mirror buttons keep their
   existing (background-fills-magenta) hover untouched. */
#screen-home .btn-conduct:hover,
#screen-home .btn-conduct:focus-visible {
  background: var(--black);
  color: var(--magenta);
  border-color: var(--magenta);
}

/* ---------- home / mirror main ---------- */

.home-main {
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 28px;
  padding: 0 24px;
  text-align: center;
}

.home-line {
  margin: 0;
  color: var(--black);
}

.home-line--top {
  font-family: var(--serif);
  /* Promoted 2026-09-04 to the dominant spatial fact of the page — this
     is the one thing Home actually needs someone to read; the wordmark
     inherited its old clamp size instead of the other way around. Sized
     back down same day — dominant relative to the wordmark, not
     shouting. */
  font-size: clamp(22px, 4vw, 44px);
  line-height: 1.2;
  font-weight: 700;
  letter-spacing: 0.02em;
  max-width: calc(100vw - 48px);
}

.home-line--bottom {
  font-size: 12px;
  letter-spacing: 0.14em;
}

/* Plain, quiet, functional — sits between the tagline's poetry and the
   fine-print time caption. Deliberately not styled to compete with
   either; it's the one place actual mechanism/cost facts live on Home. */
.home-line--context {
  font-family: var(--sans);
  font-size: 14px;
  letter-spacing: 0.02em;
  opacity: 0.7;
  max-width: 320px;
}

/* Only present at all once an account is known to exist — anonymous
   entry stays exactly as quiet as it already was. */
/* Only ever set right before a genuine resolution-loss return to Home
   (see conduct()'s expired catch branch in app.js) — the [hidden]
   attribute is the only thing gating it, so an ordinary fresh arrival at
   Home never shows this. */
.home-status {
  font-family: var(--sans);
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--white);
  opacity: 0.6;
  max-width: 320px;
  margin: 0;
}

.home-vault-link {
  font-family: var(--sans);
  font-size: 12px;
  letter-spacing: 0.1em;
  color: var(--white);
  opacity: 0.6;
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: opacity 0.18s ease, color 0.18s ease, border-color 0.18s ease;
}
.home-vault-link:hover,
.home-vault-link:focus-visible {
  opacity: 1;
  color: var(--magenta);
  border-bottom-color: var(--magenta);
}

/* Continuity pass: the persistent way back to a saved Card, present in
   every room where leaving doesn't risk abandoning an active/unpaid
   composition (Gate, Card, Send, Mirror — never Compose or Release,
   same safety boundary the wordmark-nav link already uses). Positioned
   absolutely so it can't disturb any room's existing flex centering or
   space-between layout — it doesn't participate in that flow at all,
   just overlays the same top-left corner every room's .bar already
   reserves via its own padding. Always visible, not gated on auth state:
   /vault already handles both the signed-out and signed-in case
   honestly, so hiding the door until you're recognized was the actual
   discoverability problem, not a feature. */
.bar-vault-link {
  position: absolute;
  top: 50%;
  left: 28px;
  transform: translateY(-50%);
  font-family: var(--sans);
  font-size: 12px;
  letter-spacing: 0.08em;
  /* Black by default — Send (.screen--life) and Mirror (.screen--chrome)
     are both white-background rooms, same as Card. Only Gate is dark. */
  color: var(--black);
  opacity: 0.5;
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: opacity 0.18s ease, color 0.18s ease, border-color 0.18s ease;
  z-index: 6;
}
.bar-vault-link:hover,
.bar-vault-link:focus-visible {
  opacity: 1;
  color: var(--magenta);
  border-bottom-color: var(--magenta);
}
.screen--dark .bar-vault-link {
  color: var(--white);
  opacity: 0.6;
}

/* Legibility pass: one uniform header placement rule for every room that
   uses .bar--top (Gate, Compose, Release, Send, Mirror), instead of the
   room-by-room special-casing this used to be. The wordmark true-centers
   everywhere, regardless of whether a Vault link or ghost-mark sits
   beside it — previously Gate/Compose centered the wordmark+mark as a
   pair (off from true 50%) while Send/Mirror left it pinned to the flex-
   start edge, two different placements for the same element. Taking the
   wordmark out of flex flow entirely and centering it absolutely means
   every room's header reads identically: Vault (if present) at the far
   left, wordmark dead-center, mark/action at the far right. */
.bar--top {
  justify-content: flex-end;
}
.bar--top .wordmark {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}

/* Card has no .bar header at all (its own wordmark lives inside the SVG
   object, which stays untouched) — this stands alone, fixed, matching
   the same top/left offsets every other room's .bar already uses via its
   own padding, so it lines up with the rest of the product without
   requiring a header bar to be added to the Card screen. */
.card-vault-link {
  position: fixed;
  top: 20px;
  left: 28px;
  z-index: 6;
  font-family: var(--sans);
  font-size: 12px;
  letter-spacing: 0.08em;
  color: var(--black);
  opacity: 0.5;
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: opacity 0.18s ease, color 0.18s ease, border-color 0.18s ease;
}
.card-vault-link:hover,
.card-vault-link:focus-visible {
  opacity: 1;
  color: var(--magenta);
  border-bottom-color: var(--magenta);
}

.btn-conduct {
  font-family: var(--serif);
  font-size: 18px;
  letter-spacing: 0.04em;
  background: var(--black);
  color: var(--white);
  border: 2px solid var(--white);
  padding: 22px 40px;
  cursor: pointer;
  transition: background 0.18s ease, color 0.18s ease, border-color 0.18s ease, opacity 0.18s ease;
}
.btn-conduct:hover,
.btn-conduct:focus-visible {
  background: var(--magenta);
  color: var(--white);
  border-color: var(--magenta);
}
.btn-conduct:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}
.btn-conduct:disabled:hover {
  background: var(--black);
  border-color: var(--white);
}

/* Room 4 final surgical pass (2026-09-11): the music control is now a
   real, visible text action — discoverable without becoming visually
   dominant, per direct instruction that it must no longer hide behind
   the previous tiny-dot control. Same quiet register .release-terms-link
   already established (sans, small, dimmed-until-hover), slightly more
   present at rest (0.55 vs. terms' 0.4) so it reads as clickable without
   competing with #btn-release for the room's own center of gravity. */
.release-audio-mark {
  font-family: var(--sans);
  font-size: 13px;
  letter-spacing: 0.08em;
  color: var(--white);
  opacity: 0.55;
  background: none;
  border: none;
  cursor: pointer;
  padding: 4px;
  transition: opacity 0.18s ease, color 0.18s ease;
}
.release-audio-mark:hover,
.release-audio-mark:focus-visible {
  opacity: 1;
  color: var(--magenta);
}
.release-audio-mark.is-playing {
  opacity: 1;
  color: var(--magenta);
}

/* Quiet, non-interactive supporting line — same register as the music
   control at rest, but never brightens (nothing to hover; it's just
   text), so it reads as clearly subordinate to both #btn-release above
   it and the music control below it. */
.release-quiet-line {
  font-family: var(--sans);
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--white);
  opacity: 0.5;
  margin: 0;
}

/* Was .btn-conduct-sub, the button's own price sub-span — no longer
   needed now that Room 4's button copy is one flat "Release it · €4"
   label (cleanup pass, 2026-09-09); removed rather than left orphaned,
   since nothing else in the codebase used this class. */

/* The concrete thing that was actually committed — sits between the
   abstract reference mark and "Did it click?", quiet enough not to
   compete with either. Empty string collapses to nothing if state.card
   is ever unavailable, matching .gate-gps-status's own pattern. */
.mirror-committed {
  font-family: var(--serif);
  font-style: italic;
  font-size: 15px;
  color: var(--black);
  opacity: 0.6;
  max-width: 420px;
  margin: 0;
}
.mirror-committed:empty { display: none; }

/* Room 7 Final Truth Pass (2026-09-11): the specific, mechanism-derived
   fact that is now true because the Room 6 crossing was confirmed — the
   room's only content besides EXIT (see ROOM7_TRUTH/renderRoom7Truth()
   in app.js). Magenta on the room's own black field, sized as the
   dominant object in a mostly-empty screen, not a headline competing
   with anything else — there is nothing else here to compete with. */
.mirror-final-note {
  font-family: var(--serif);
  font-weight: 700;
  font-size: clamp(24px, 4.5vw, 40px);
  letter-spacing: 0.01em;
  color: var(--magenta);
  max-width: 560px;
  margin: 0;
  text-align: center;
}
.mirror-final-note:empty { display: none; }

.mirror-resolve {
  font-family: var(--serif);
  font-weight: 700;
  font-size: 20px;
  letter-spacing: 0.02em;
  color: var(--black);
  margin: 0;
}

.mirror-actions {
  display: flex;
  gap: 16px;
}

.mirror-choice {
  font-family: var(--sans);
  font-size: 12px;
  letter-spacing: 0.14em;
  color: var(--black);
  background: none;
  border: 1px solid var(--black);
  padding: 12px 28px;
  cursor: pointer;
  transition: border-color 0.18s ease, color 0.18s ease;
}
.mirror-choice:hover { border-color: var(--magenta); color: var(--magenta); }
.mirror-choice:disabled { opacity: 0.5; cursor: default; pointer-events: none; }

/* Room 7 Final Truth Pass (2026-09-11): a black field, per direct
   instruction — "the pending thing is no longer pending." Scoped to
   #screen-mirror specifically (same pattern #screen-home already uses
   for its own black instrument), so Room 6's crossing-check Yes button
   — which shares .mirror-choice — is completely untouched; it still
   renders black-on-white exactly as before. */
#screen-mirror { background: var(--black); }
#screen-mirror .mirror-choice { color: var(--white); border-color: var(--white); }
#screen-mirror .mirror-choice:hover { border-color: var(--magenta); color: var(--magenta); }

.know-more, .feel-more {
  font-size: 12px;
  letter-spacing: 0.1em;
  color: var(--black);
  text-decoration: none;
  border-bottom: 1px solid transparent;
}
.know-more:hover, .feel-more:hover {
  color: var(--magenta);
  border-bottom-color: var(--magenta);
}

/* ---------- gate ---------- */

.gate-main {
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* One composition across the whole available space, 2026-09-04 — five
     objects distributed from top to bottom rather than stacked and
     centered as a block. gap is a floor, not the driver: space-between
     spreads the objects to fill whatever room is actually available. */
  justify-content: space-between;
  gap: 20px;
  /* Same failure mode Home already had and got fixed 2026-09-02: a fixed
     negative translateY tuned for shorter content clips the top of the
     page off-screen the moment real content makes this column taller.
     Top padding clears the fixed header instead of fighting it with an
     offset that only works for one specific content length; overflow-y
     is the fallback for genuinely short viewports rather than relying on
     centering alone. */
  /* Bottom padding widened 2026-09-04 — the footer is position:fixed
     (doesn't reserve its own flow space), so 48px left object 5 sitting
     almost flush against it. 84px clears the footer's own ~50px height
     with real breathing room on top. */
  padding: 100px 24px 84px;
  overflow-y: auto;
  text-align: center;
  max-width: 480px;
  margin: 0 auto;
}

.gate-gps-status {
  font-size: 13px;
  letter-spacing: 0.06em;
  color: var(--white);
  margin: 0;
}
/* Only occupies space (and the flex gap around it) once there's actually
   something to say — the default Room Two state has nothing in it. */
.gate-gps-status:empty { display: none; }
.gate-gps-status.is-granted { color: var(--magenta); }
.gate-gps-status.is-blocked { color: var(--white); opacity: 0.6; }

/* Room 2 handshake pass (2026-09-10): object 1 ("Bring one unresolved
   thing.") is the primary statement, same weight .gate-obj--1/--4 already
   carried under the old route copy. Object 3's weight is shared with the
   Release screen's own single line ("The one thing you brought has
   resolved.") — left untouched here; the three-line example group gets
   its own modifier below instead of altering --3's shared rule. */
.gate-obj {
  font-family: var(--serif);
  color: var(--white);
  margin: 0;
  max-width: 420px;
}
.gate-obj--1,
.gate-obj--4 {
  font-size: clamp(22px, 3.4vw, 30px);
  font-weight: 700;
  line-height: 1.3;
}
.gate-obj--3 {
  font-size: clamp(15px, 2vw, 18px);
  font-weight: 400;
}
.gate-obj-sub {
  font-family: var(--sans);
  font-size: 13px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--magenta);
  margin: 0;
}

/* Room 2 final product/boundary pass (2026-09-11): groups "Put the mess
   here." / "Composium turns it into one clear move." as one
   transformation unit — a tight internal gap, distinct from .gate-main's
   own wider space-between rhythm between top-level objects. */
.gate-transform {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* Quiet product-boundary statement — same register .gate-consent-text
   already established (sans, small, 0.55 opacity, readable sentence
   case), as its own object rather than nested in the consent label. */
.gate-boundary {
  font-family: var(--sans);
  font-size: 13px;
  line-height: 1.5;
  color: var(--white);
  opacity: 0.55;
  max-width: 380px;
  margin: 0;
}

/* The deliberate acknowledgment gate — Compose stays disabled until this
   is checked (app.js), so this is the actual "applicable confirmation"
   Terms/Conditions describe, not just ambient copy. */
.gate-consent {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  text-align: left;
  cursor: pointer;
  max-width: 420px;
}
/* Room 2 handshake pass (2026-09-10): the checkbox is still the real
   control app.js binds to (#gate-consent-checkbox, unchanged onchange
   wiring) — only hidden from view, not removed, so "Enter" (the pill
   below) reads as the single visible action instead of sitting next to a
   second, literal checkbox. Standard clip-based hide, not display:none,
   so it stays keyboard-focusable and toggleable via the label. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
.gate-consent-body {
  display: flex;
  flex-direction: column;
  gap: 16px;
}
/* Room 2 legibility pass (2026-09-11): quieter, not reworded — the copy
   itself is unchanged ("What you write — and your location — will be
   processed, including by AI."), only its visual weight drops relative
   to the CTA pill below it, per direct instruction to keep this line
   but make it clearly secondary. */
.gate-consent-text {
  font-family: var(--sans);
  font-size: 12px;
  line-height: 1.5;
  color: var(--white);
  opacity: 0.55;
}
/* The destination of the page — price and the crossing itself, inside
   the same object as the disclosure it depends on, not floating above
   it as a separate visual region. */
/* Styled as a full pill button, but it's still just a child of the same
   <label> as the checkbox above it — clicking it toggles that one input
   via native label behavior, not a second control or a second handler. */
.gate-entry-action {
  display: block;
  font-family: var(--sans);
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.12em;
  color: var(--black);
  background: var(--magenta);
  /* Squared off 2026-09-10 — matches the hard-edged geometry every other
     primary action on the site already uses (.btn-conduct has none). */
  padding: 16px 24px;
  margin-top: 6px;
  text-align: center;
  transition: opacity 0.18s ease;
}

/* The connecting thread between objects 1-4 — one continuous field, not
   four paragraphs stacked with equal empty space between them. */
.gate-divider {
  display: block;
  width: 1px;
  height: 20px;
  background: rgba(255, 255, 255, 0.25);
}

/* Room 2 handshake pass (2026-09-10): dropped the bordered-box treatment
   this modifier used to carry — six flat statements plus a boxed form
   module read like two different registers stacked on one screen. The
   pill (.gate-entry-action) is still the primary affordance; this class
   now only exists as the .is-entering hook app.js already toggles. */
.gate-consent--entry {
  transition: opacity 0.18s ease;
}
.gate-consent--entry:hover .gate-entry-action,
.gate-consent--entry:focus-within .gate-entry-action {
  opacity: 0.85;
}
.gate-consent--entry.is-entering {
  opacity: 0.45;
  pointer-events: none;
}


/* ---------- composition room ---------- */

#screen-composition { background: var(--black); }

/* Room 3 final surgical pass (2026-09-11): the phase word now lives on
   the white writing surface itself, directly above the prompt and timer
   — per direct instruction, and per direct feedback that its old
   position (in the black header, apart from the prompt/timer) wasn't
   landing. Recolored for its new context (was white-on-black in the
   header; needs black-on-white here) — same quiet whisper-register
   intent as before, just inverted. Uppercase per the exact phase copy
   (LISTEN/COMPOSE/CONDUCT) — a CSS transform, not a data change, so
   app.js's setRoomCue() (and its lowercase ROOM_PHASES values) stays
   untouched. */
.room-phase {
  display: block;
  text-align: center;
  font-family: var(--sans);
  font-size: 12px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--black);
  opacity: 0.35;
  margin: 0 0 6px;
}
.room-phase:empty { display: none; }

/* Anchored to the screen's own edges, not centered by a vh-height +
   transform. That combination is what destabilized under the iOS
   keyboard: vh doesn't track the keyboard-shrunk visual viewport, and a
   transform-centered box has no way to respond when its reference
   height changes — it just recenters around a new, wrong midpoint. Edge
   insets on a position:fixed/dvh-height parent track the real viewport
   directly, so the writing surface holds still instead. */
/* Inset enough to clear the fixed header above it (see .room-bar), and
   bounded with a visible edge so the writing surface reads as a
   contained app surface rather than the page itself — real-device
   feedback was that an edge-to-edge white-on-white box with no border
   didn't visually resolve as a surface at all. The thin border still
   does that job; the drop shadow (C5) stays dropped — that's what made
   this read as a floating UI panel/card rather than the room itself. A
   plane with an edge, not a card on a page. Padding widened for more
   breathing room around the material, and the whole surface fades in on
   arrival (see room-arrive below) instead of hard-cutting into view, so
   entering already reads as "something is happening" rather than a page
   load.
   Room 3 handshake pass (2026-09-10): corners squared back off (was
   border-radius:14px, added 2026-09-04) per this pass's explicit
   hard/square geometry requirement — the border alone already does the
   "this is a contained surface" job the rounding was added for. */
.room {
  position: absolute;
  top: 92px;
  right: 24px;
  /* .composium-footer is fixed, ~50px tall, white text built for a black
     surface — real-device finding: the room's white box was extending
     close enough to the bottom edge that the footer overlapped onto it,
     where white-on-white is invisible rather than merely close. This
     clears it outright rather than tightening the margin further. */
  bottom: 64px;
  left: 24px;
  max-width: 640px;
  margin: 0 auto;
  background: var(--white);
  border: 1px solid rgba(0, 0, 0, 0.08);
  display: flex;
  flex-direction: column;
  min-height: 0; /* lets .room-field (flex:1) actually shrink instead of overflowing when space is tight */
  padding: 32px 36px;
  animation: room-arrive 0.4s ease both;
}

/* Widened on desktop 2026-09-04 — 640px read cramped on larger screens;
   mobile (the max-width: 640px block further down) is untouched. */
@media (min-width: 900px) {
  .room {
    max-width: 800px;
  }
}
@media (prefers-reduced-motion: reduce) {
  .room { animation: none; }
}
@keyframes room-arrive {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* Tightened (C5): this header zone previously claimed as much vertical
   weight as the writing surface itself — a real "section" with its own
   border. Quieter border, less padding, so it recedes into the room
   instead of framing it.
   Room 3 final surgical pass (2026-09-11): now the last of the three
   stacked objects above the field (phase word, prompt, timer, in that
   order) rather than the first — the dashed border still does the same
   job, separating the header stack from the writing surface below it,
   just from underneath the timer now instead of underneath itself. */
.room-top {
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: 10px;
  padding-bottom: 8px;
  border-bottom: 1px dashed rgba(0, 0, 0, 0.08);
}

/* Room 3 cue (2026-09-09) — one live cue at a time, aimed at surfacing
   more real Matter, never at asking for the answer. Replaces the dead
   Listen/Compose/Conduct stage indicator (same slot, same quiet
   black-at-low-opacity convention .room-placeholder already uses);
   deliberately not magenta — that color reads as an active control/CTA
   everywhere else in the product, and this is ambient aim, not a CTA.
   Progressed by setRoomCue() in app.js from state.secondsLeft alone. */
.room-cue {
  display: block;
  text-align: center;
  font-family: var(--sans);
  font-size: 12px;
  letter-spacing: 0.1em;
  color: var(--black);
  opacity: 0.45;
  margin: 8px 0 0;
}
.room-cue:empty { display: none; }

/* Only occupies space once resolution has actually failed and there's
   something to say — matches .gate-gps-status's collapse-when-empty. */
.composition-error {
  font-size: 12px;
  letter-spacing: 0.03em;
  color: var(--magenta);
  margin: 0 0 16px;
}
.composition-error:empty { display: none; }

/* Room 3 final surgical pass (2026-09-11): magenta again, per direct
   instruction ("Timer is magenta") — supersedes C4.1's quiet-black
   treatment above. Full opacity throughout, not just during the final-20s
   urgency state below, which still layers its own pulse animation on top
   of this same color. */
.room-timer {
  font-size: 16px;
  color: var(--magenta);
  opacity: 1;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.08em;
}

/* Final 20 seconds: the same number starts carrying the weight of a live
   set actually ending — no new element, just the truth of what's left. */
.room-timer.is-ending {
  opacity: 1;
  animation: room-timer-pulse 1s ease-in-out infinite;
}
@keyframes room-timer-pulse {
  0%, 100% { opacity: 1; color: var(--magenta); }
  50% { opacity: 0.55; color: #ff33ff; }
}

.room-field {
  position: relative;
  flex: 1;
  margin: 24px 0;
}

/* Font-size raised 17px -> 19px (C5, matches .room-input below) — more
   presence for the one thing the room is actually for, a small but real
   part of "more breathing room / stronger relationship between cursor
   and space." */
.room-placeholder {
  position: absolute;
  top: 0;
  left: 0;
  font-size: 19px;
  color: var(--black);
  opacity: 0.35;
  pointer-events: none;
  /* Short enough that the placeholder is gone before the caret and the
     first typed character settle in — a longer fade here was leaving a
     visible overlap between the placeholder and real input. */
  transition: opacity 0.08s ease;
}
.room-placeholder.is-hidden { opacity: 0; }

/* C4.1 — no copy added, just a slow, quiet breath on the one thing
   already occupying the empty room, so the surface reads as already
   present/attentive rather than a static, inert form field. Scoped to
   :not(.is-hidden) so it cleanly hands off to the instant fade above the
   moment real typing begins, instead of fighting it (a running CSS
   animation otherwise keeps winning over a plain opacity rule regardless
   of selector specificity). */
@media (prefers-reduced-motion: no-preference) {
  .room-placeholder:not(.is-hidden) {
    animation: room-placeholder-breathe 3.6s ease-in-out infinite;
  }
}
@keyframes room-placeholder-breathe {
  0%, 100% { opacity: 0.28; }
  50% { opacity: 0.42; }
}

.room-input {
  width: 100%;
  height: 100%;
  resize: none;
  border: none;
  outline: none;
  background: transparent;
  color: var(--black);
  font-family: var(--sans);
  font-size: 19px;
  line-height: 1.65;
  caret-color: var(--magenta);
}
/* Resolution has begun — the same dimming language the room already uses
   for its placeholder/support text, not a new visual idea. */
.room-input:disabled {
  opacity: 0.5;
}

.room-bottom {
  display: flex;
  align-items: center;
  justify-content: flex-end;
}

/* C5: was a solid black filled button — the one element in the room that
   read as a conventional "Submit" control, competing with the fact that
   resolution is already automatic (early Conduct). Recast as a quiet
   text affordance, same register as the Card's own "Save" — present and
   fully functional (same id, same click handler, same mechanics), but no
   longer shaped like the thing you're supposed to press to make
   something happen. */
.btn-conduct-room {
  font-family: var(--sans);
  font-size: 12px;
  letter-spacing: 0.1em;
  background: none;
  color: var(--black);
  opacity: 0.45;
  border: none;
  padding: 6px 2px;
  cursor: pointer;
  transition: opacity 0.18s ease, color 0.18s ease;
}
.btn-conduct-room:hover { opacity: 1; color: var(--magenta); }

/* ---------- release (the crossing) ----------
   Restores .stripe-main/.stripe-eyebrow, removed in be3cd06 alongside
   screen-stripe itself, under names matching the restored room's new
   id — same rules, unchanged. */

.release-main {
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 24px;
  padding: 0 24px;
  text-align: center;
  max-width: 480px;
  margin: 0 auto;
}

/* Quiet, clearly interactive — same dimmed/hover-to-magenta convention
   .card-vault-link already uses, just on white (Room 4 is
   .screen--dark) instead of black. Own line, own element: not a
   parenthetical inside the sentence above, so it never renders with
   browser-default blue/underline (no rule existed for the old inline
   <a>, which is exactly what this replaces). */
/* Room 4 final surgical pass (2026-09-11): opacity dropped 0.5 -> 0.4 —
   with the music control now a real, visible text action of its own
   (.release-audio-mark, resting at 0.55), Terms needs to read as
   clearly the quietest of the two, per direct instruction that it stay
   "the smallest/subtlest text action" on the page. */
.release-terms-link {
  font-family: var(--sans);
  font-size: 12px;
  letter-spacing: 0.08em;
  color: var(--white);
  opacity: 0.4;
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: opacity 0.18s ease, color 0.18s ease, border-color 0.18s ease;
}
.release-terms-link:hover,
.release-terms-link:focus-visible {
  opacity: 1;
  color: var(--magenta);
  border-bottom-color: var(--magenta);
}

/* ---------- loop / processing ---------- */

#screen-loop { background: var(--black); }

.loop-wrap {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  gap: 14px;
}

.loop-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--magenta);
  animation: loop-pulse 1s ease-in-out infinite;
}
.loop-dot:nth-child(2) { animation-delay: 0.15s; }
.loop-dot:nth-child(3) { animation-delay: 0.3s; }

/* The landing beat ("LAND") — the instant resolution is confirmed, the
   three pulsing dots settle into one steady white point before the Card
   appears. Same markup, no new elements: the other two dots fade out,
   the first stops pulsing and turns white. The point has landed. */
.loop-wrap.is-landed .loop-dot {
  animation: none;
  opacity: 0;
  transition: opacity 0.2s ease;
}
.loop-wrap.is-landed .loop-dot:first-child {
  opacity: 1;
  background: #ffffff;
  transform: scale(1.4);
  transition: transform 0.25s ease, background 0.15s ease;
}

@keyframes loop-pulse {
  0%, 100% { opacity: 0.25; transform: scale(0.8); }
  50% { opacity: 1; transform: scale(1.15); }
}

/* ---------- card screen ---------- */

#screen-card { background: var(--white); }

.card-main {
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 22px;
  padding: 24px 16px;
  /* Extra bottom clearance: .card-move-prompt is fixed at bottom:36px —
     without this, an expanded .card-save-block (the inline sign-in form
     appearing) can grow tall enough to collide with it. */
  padding-bottom: 100px;
}

.composium-card {
  width: min(70vw, calc(72vh * 5 / 7));
  max-width: 520px;
  height: auto;
  aspect-ratio: 5 / 7;
}

/* Source Mark — quietest text on the Card, deliberately: subordinate to
   the composition above it. Display-only reference, not the full
   click_id, not a digest, not provider/model — see routes/card.js for
   what the mark can resolve to. The separate footer wordmark this used
   to sit below (.card-wordmark) was removed 2026-09-10 as a duplicate of
   the top brand text, which now reads COMPOSIUM.APP itself. */
.card-source-mark {
  fill: #ffffff;
  opacity: 0.35;
  font-family: var(--sans);
  font-size: 9px;
  letter-spacing: 0.08em;
}

/* Same mark as .card-source-mark, carried onto Life and Mirror (both
   white screens, hence black instead of white) — one visible thread
   proving this is the same sealed object moving through the rest of the
   transaction, not a fresh screen with no memory of the Card that made it. */
.object-mark {
  color: #000000;
  opacity: 0.3;
  font-family: var(--sans);
  font-size: 10px;
  letter-spacing: 0.08em;
  margin: 0;
}

/* Demoted 2026-09-04 (27px -> 16px): Card identity should be clear but
   subordinate to the actual resolved content — Tone now owns the first
   recognition moment, instead of the brand header. */
.card-brand {
  fill: var(--magenta);
  font-family: var(--serif);
  font-size: 16px;
}

/* Room 5 final surgical pass (2026-09-11): three intentional chunks, not
   a taper from one giant line to one tiny leftover line, per direct
   instruction. Tone (recognition) and Conduct (the forward charge —
   "go now") both bold and full-opacity, bookending Composition (the
   concrete movement now in front of the user) as the measured, regular-
   weight middle. Conduct previously ended the Card at 15px/400/0.75
   opacity — a fading coda; it now lands with as much real weight as
   Tone, deliberately, since "go now" is not the quietest thing on the
   Card. Sizes kept close together (24/20/22) rather than a steep decay,
   so all three read as deliberate objects, not a hierarchy of
   diminishing importance. See .card-label just below for the small
   TONE/COMPOSITION/CONDUCT structural markers each chunk now carries. */
.card-label {
  font-family: var(--sans);
  color: #ff00ff;
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  opacity: 0.55;
  margin: 0 0 4px;
}

/* All three: overflow-wrap so a single long word can't push past the
   Card's own border. font-size here is the base/normal size (scale 1.0)
   — card-layout.js sets an inline font-size that can compress this down
   for genuinely dense Cards, measuring real rendered height at each
   trial rather than trusting a fixed per-section ceiling. max-height
   here is a generous backstop only — large enough to never constrain a
   real, successfully-fitted layout, there only to contain a pathological
   string if the JS layout pass fails to run at all. */
.card-tone {
  font-family: var(--serif);
  color: #ffffff;
  font-weight: 700;
  font-size: 24px;
  line-height: 1.35;
  margin: 0;
  overflow-wrap: break-word;
  max-height: 400px;
  overflow: hidden;
}

.card-composition {
  font-family: var(--serif);
  color: #ffffff;
  font-size: 20px;
  line-height: 1.5;
  margin: 0;
  overflow-wrap: break-word;
  max-height: 400px;
  overflow: hidden;
}

.card-conduct {
  font-family: var(--serif);
  color: #ffffff;
  font-weight: 700;
  font-size: 22px;
  line-height: 1.4;
  margin: 0;
  overflow-wrap: break-word;
  max-height: 400px;
  overflow: hidden;
}

/* Save, relocated onto the Card screen itself (room 5) — the composer has
   just received the object, that's where "keep it" belongs, not a menu
   screen one crossing later. Deliberately quiet: a text action, not a
   boxed button, so it never competes with the Card or the Send prompt
   below it for attention. Sized down slightly 2026-09-04 (was 11px,
   same as the move prompt below despite the comment already saying it
   shouldn't compete) — custody is a secondary decision, movement toward
   Room 6 is primary; the two now actually read as different weight, not
   just intend to. */
.card-save-block {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}
.card-save-action {
  font-family: var(--sans);
  font-size: 10px;
  letter-spacing: 0.14em;
  color: var(--magenta);
  opacity: 0.6;
  background: none;
  border: none;
  padding: 6px;
  cursor: pointer;
  transition: opacity 0.18s ease;
}
.card-save-action:hover { opacity: 1; text-decoration: underline; }
.card-save-action:disabled { opacity: 0.4; cursor: default; }

/* Sized up 2026-09-04 (was 11px, matching Store it above) — this is the
   primary continuation out of Room 5, not an equal-weight alternative
   to custody. Wording unchanged ("Send it") — Room 6 itself is being
   locked separately; this is hierarchy only. */
.card-move-prompt {
  position: fixed;
  left: 50%;
  bottom: 36px;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  font-family: var(--sans);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.14em;
  color: var(--magenta);
  background: none;
  border: none;
  margin: 0;
  padding: 8px;
  cursor: pointer;
  opacity: 0;
  transition: opacity 1s ease, transform 0.18s ease;
  z-index: 5;
}
.card-move-prompt.is-revealed { opacity: 1; }
.card-move-prompt:hover { transform: translateX(-50%) translateY(4px); }
.card-move-arrow { display: block; }

/* ---------- send (room 6 — where the Card leaves) ----------
   White, like the Card screen this follows — one continuation, not a cut
   to an unrelated black menu. One question, one set of real destinations
   — no recap of the Card itself, the composer already has it. */

#screen-life { background: var(--white); }

/* Room 6 Conduct Payload pass (2026-09-11): conduct_payload is now the
   dominant object in the room — the resolved commit itself, not a
   quiet preview of something else shown above it. Bold serif, centered,
   the same weight register the old .life-composition (removed) used to
   carry. white-space:pre-wrap preserves the payload's own real line
   breaks (a composium.app or substituted Card-URL line Carmelita may
   have written). */
.life-payload {
  font-family: var(--serif);
  font-weight: 700;
  font-size: clamp(19px, 3.2vw, 27px);
  line-height: 1.4;
  letter-spacing: 0.01em;
  color: var(--black);
  white-space: pre-wrap;
  max-width: 480px;
  margin: 0;
  text-align: center;
}
.life-payload[hidden] { display: none; }

/* Corrective UI pass (2026-09-11): no commit hash / technical
   provenance in the main room anymore — Edit and View Card are the
   room's only two secondary actions now, same quiet weight as each
   other, neither competing with the payload above. (Provenance still
   lives inside the View Card overlay, just not out here.) */
.life-commit-line {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin: 0;
  font-family: var(--sans);
  font-size: 12px;
  color: var(--black);
  opacity: 0.5;
}
.life-commit-sep { opacity: 0.7; }

/* Shared small text-link register — Edit, View Card, and the Edit
   block's own Save/Cancel all use this one class rather than four
   near-duplicate rules. */
.life-inline-link {
  background: none;
  border: none;
  font-family: var(--sans);
  font-size: 12px;
  letter-spacing: 0.03em;
  color: var(--black);
  opacity: 0.75;
  cursor: pointer;
  padding: 2px 4px;
  text-decoration: underline;
}
.life-inline-link:hover { opacity: 1; color: var(--magenta); }

/* Edit (Part D) — small, inline, optional. Same visual register as the
   room's other text inputs (.life-auth-email), not a new writing surface. */
#life-edit-block {
  width: 100%;
  max-width: 420px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
/* Recurring bug in this file: an ID-selector's unconditional
   display:flex outranks the plain [hidden] UA rule at equal
   specificity by loading later, so hidden=true alone doesn't hide it.
   This explicit rule settles it (same fix as #life-crossing-check[hidden]
   and .life-card-overlay[hidden] below). */
#life-edit-block[hidden] { display: none; }
.life-edit-input {
  width: 100%;
  min-height: 70px;
  font-family: var(--sans);
  font-size: 14px;
  line-height: 1.5;
  color: var(--black);
  background: var(--white);
  border: 1px solid var(--black);
  padding: 10px 12px;
  resize: vertical;
  box-sizing: border-box;
}
.life-edit-input:focus { outline: none; border-color: var(--magenta); }
.life-edit-actions {
  display: flex;
  justify-content: center;
  gap: 16px;
}

/* Corrective UI pass (2026-09-11): destinations read as a quiet line of
   text, not an app/integration picker — no boxes, no equal-weight grid,
   same quiet register as .life-inline-link (Edit/View Card), just with
   a small mark ahead of each label. Wraps as plain inline content would. */
.life-route-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 4px 4px;
  max-width: 440px;
  margin: 4px 0;
}
.life-route-mark {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  background: none;
  border: none;
  padding: 2px 4px;
  cursor: pointer;
  font-family: var(--sans);
  font-size: 12px;
  letter-spacing: 0.03em;
  color: var(--black);
  opacity: 0.6;
  transition: opacity 0.18s ease, color 0.18s ease;
}
.life-route-mark-icon { display: flex; }
.life-route-mark-icon svg { display: block; width: 14px; height: 14px; }
.life-route-mark:hover,
.life-route-mark.is-selected {
  opacity: 1;
  color: var(--magenta);
}
/* Each mark is the whole action now — briefly disabled while its own
   run() is in flight, so a second tap can't fire a second crossing. */
.life-route-mark:disabled { opacity: 0.3; cursor: default; pointer-events: none; }
.life-route-sep {
  color: var(--black);
  opacity: 0.3;
  font-size: 12px;
}

/* Composium fallback — a full sentence, not a door in the row above, so
   it reads as "ask for help" rather than one more transport option. */
.life-composium-row {
  margin: 2px 0 0;
  font-family: var(--sans);
  font-size: 12px;
  letter-spacing: 0.03em;
  color: var(--black);
  opacity: 0.55;
}
.life-composium-link {
  background: none;
  border: none;
  font-family: var(--sans);
  font-size: 12px;
  letter-spacing: 0.03em;
  color: var(--magenta);
  cursor: pointer;
  padding: 2px 4px;
}
.life-composium-link:hover,
.life-composium-link.is-selected { text-decoration: underline; }
.life-composium-link:disabled { opacity: 0.4; cursor: default; pointer-events: none; }

.life-main {
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 20px;
  padding: 24px 16px;
  text-align: center;
}

/* Crossing confirmation (Part F) — "Did it cross?" plus the one real
   yes/no. Yes reuses .mirror-choice (the same bordered-choice weight
   Room 7 already uses for its own EXIT) since this is the room's actual
   forward door now; No is the room's quietest action, same
   .life-inline-link register as Edit/View Card. */
.life-section-label {
  font-family: var(--sans);
  color: var(--magenta);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  opacity: 0.6;
  margin: 0;
}
#life-crossing-check {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}
/* Recurring bug class in this file: an ID-selector's unconditional
   display:flex outranks the plain [hidden] UA rule at equal
   specificity by loading later, so hidden=true alone doesn't hide it.
   This explicit rule settles it (same fix as #life-edit-block[hidden]
   and .life-card-overlay[hidden] below). */
#life-crossing-check[hidden] { display: none; }
.life-crossing-actions {
  display: flex;
  align-items: center;
  gap: 18px;
}

/* View Card overlay (Part E) — a self-contained overlay, independent of
   #legal-overlay (see index.html's own comment). Fixed, dimmed backdrop;
   a plain white content panel, left-aligned like the Card's own detail
   register, not centered like the payload above it. */
.life-card-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  z-index: 20;
}
/* Same recurring bug class as #life-crossing-check[hidden] above: a
   class-selector's unconditional display:flex outranks the plain
   [hidden] UA rule at equal specificity by loading later. */
.life-card-overlay[hidden] { display: none; }
.life-card-overlay-frame {
  position: relative;
  background: var(--white);
  max-width: 420px;
  width: 100%;
  max-height: 85vh;
  overflow-y: auto;
  padding: 40px 24px 24px;
  text-align: left;
  box-sizing: border-box;
}
.life-card-overlay-field {
  font-family: var(--serif);
  font-size: 15px;
  line-height: 1.5;
  color: var(--black);
  margin: 0 0 20px;
}
.life-card-overlay-provenance {
  font-family: var(--sans);
  font-size: 11px;
  letter-spacing: 0.03em;
  color: var(--black);
  opacity: 0.5;
  margin: 0 0 16px;
}
.life-card-overlay-link {
  display: inline-block;
  font-family: var(--sans);
  font-size: 12px;
  letter-spacing: 0.05em;
  color: var(--magenta);
  text-decoration: none;
}
.life-card-overlay-link:hover { text-decoration: underline; }

.card-action-note {
  font-size: 12px;
  color: var(--magenta);
  margin: -10px 0 0;
}

/* Inline sign-in — appears only when Save needs an account it doesn't
   have yet. Same visual language as the Vault page's own login form
   (input#login-email/.btn-vault), not a new idiom. */
.life-auth {
  width: 100%;
  max-width: 440px;
}

.life-auth-form {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

.life-auth-email {
  flex: 1;
  min-width: 180px;
  font-family: var(--sans);
  font-size: 14px;
  padding: 12px 14px;
  border: 1px solid var(--black);
  background: var(--white);
  color: var(--black);
}
.life-auth-email:focus { outline: none; border-color: var(--magenta); }

.life-auth-submit {
  font-family: var(--serif);
  font-size: 14px;
  letter-spacing: 0.04em;
  background: var(--black);
  color: var(--white);
  border: 1px solid var(--black);
  padding: 12px 22px;
  cursor: pointer;
  transition: background 0.18s ease, border-color 0.18s ease;
}
.life-auth-submit:hover { background: var(--magenta); border-color: var(--magenta); }
.life-auth-submit:disabled { opacity: 0.5; cursor: default; }

.card-action-note-link {
  color: var(--black);
  text-decoration: underline;
}
.card-action-note-link:hover { color: var(--magenta); }

/* ---------- void ---------- */

.screen--void { background: var(--white); }

/* ---------- ownership footer ----------
   Hidden by default; enabled per-screen below. Allow-listed on purpose —
   only the black in-transaction screens that don't already carry a
   closing statement of their own (Card's embedded wordmark, Conducted's
   own line, Mirror's own time/coords footer). Room Three's own layout is
   untouched; this sits in the black margin around the room, not in it. */
.composium-footer {
  display: none;
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 4;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 18px 16px;
  pointer-events: none;
}

.composium-footer-line {
  font-size: 10px;
  letter-spacing: 0.05em;
  color: var(--white);
  opacity: 0.4;
}

.composium-footer-link {
  font-size: 10px;
  letter-spacing: 0.05em;
  color: var(--white);
  opacity: 0.4;
  text-decoration: none;
  border-bottom: 1px solid transparent;
  pointer-events: auto;
  transition: opacity 0.18s ease, color 0.18s ease, border-color 0.18s ease;
}
.composium-footer-link:hover,
.composium-footer-link:focus-visible {
  opacity: 1;
  color: var(--magenta);
  border-bottom-color: var(--magenta);
}

/* Home is excluded deliberately — pure pre-boundary arrival, before any
   crossing has happened, carries none of the operative-interior chrome. */
body:has(#screen-gate.screen--active) .composium-footer,
body:has(#screen-composition.screen--active) .composium-footer,
body:has(#screen-release.screen--active) .composium-footer,
body:has(#screen-card.screen--active) .composium-footer,
body:has(#screen-life.screen--active) .composium-footer {
  display: flex;
}

/* Life and Card are both white (they continue the Card, not the black
   in-transaction screens) — the footer's white-on-dark text needs the
   same inversion on both or it disappears against that background. */
body:has(#screen-life.screen--active) .composium-footer-line,
body:has(#screen-life.screen--active) .composium-footer-link,
body:has(#screen-card.screen--active) .composium-footer-line,
body:has(#screen-card.screen--active) .composium-footer-link {
  color: var(--black);
}
/* Restated at equal specificity so it still wins over the base rule above
   during hover/focus — the base selector's #screen-life/#screen-card id
   would otherwise outrank .composium-footer-link:hover's plain class rule
   and silently cancel the hover color. */
body:has(#screen-life.screen--active) .composium-footer-link:hover,
body:has(#screen-life.screen--active) .composium-footer-link:focus-visible,
body:has(#screen-card.screen--active) .composium-footer-link:hover,
body:has(#screen-card.screen--active) .composium-footer-link:focus-visible {
  color: var(--magenta);
}

/* ---------- legal overlay ---------- */

/* Product path pass (2026-09-10): OPEN -> READ -> X -> exact same place.
   Every .js-legal-link click opens the real legal page inside this fixed
   sheet (see app.js) instead of navigating away — active composition
   state is structurally unreachable from inside the iframe, so there is
   nothing to lose regardless of what's clicked inside it. */
.legal-overlay {
  position: fixed;
  inset: 0;
  z-index: 50;
  background: rgba(0, 0, 0, 0.6);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.legal-overlay[hidden] { display: none; }
.legal-overlay-frame {
  position: relative;
  width: 100%;
  max-width: 720px;
  height: 100%;
  max-height: 800px;
  background: var(--white);
  border: 1px solid rgba(0, 0, 0, 0.1);
}
.legal-overlay-close {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 1;
  width: 36px;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--white);
  color: var(--black);
  border: 1px solid var(--black);
  font-family: var(--sans);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  transition: border-color 0.18s ease, color 0.18s ease;
}
.legal-overlay-close:hover { border-color: var(--magenta); color: var(--magenta); }
.legal-overlay-iframe {
  width: 100%;
  height: 100%;
  border: none;
}

/* ---------- responsive ---------- */

@media (max-width: 640px) {
  .room { top: 78px; right: 14px; bottom: 60px; left: 14px; padding: 24px 20px; }
  .room-input, .room-placeholder { font-size: 15px; }
  .composium-card { width: min(88vw, calc(58vh * 5 / 7)); }
  .bar { padding: 16px 18px; }
  .btn-conduct { padding: 18px 28px; font-size: 16px; }
  .legal-overlay { padding: 0; }
  .legal-overlay-frame { max-width: none; max-height: none; border: none; }
}
