/* ════════════════════════════════════════════════════════════════════════
   Loryn Command · v3 chamber chrome — FLOW LAYOUT
   ────────────────────────────────────────────────────────────────────────
   Replaces the absolute-overlay approach. Now the chrome is REORDERED
   into the chamber's existing flex flow:
       atrium-orb-zone (existing)
       ↓
       cc-date-eyebrow            (new chrome — appears after first conv)
       cc-hero  (label, number, verdict)   (new chrome)
       cc-offers                   (new chrome)
       cc-stage  (default vault OR Loryn's staged surface)   (new chrome)
       ↓
       atrium-line  (existing)
       ↓
       cc-base   (new chrome — base hairline)

   Chrome that lives in flow can't collide with chamber elements because
   it's IN THE SAME PARENT and grows the parent's height.
   Two chrome elements remain absolute by design — they live in the
   margin: the room rail (right edge) and the "currently in" indicator
   (top-left). Neither overlaps any flow content.

   atrium-convo and chamber-workstation are HIDDEN. Their content is
   mirrored to cc-hero-verdict and cc-stage respectively via JS observer.
   ════════════════════════════════════════════════════════════════════════ */

/* ════════════════════════════════════════════════════════════════════════
   LIVING STAGE — timing tokens (single source of truth for motion).
   Every duration/easing in the chamber comes from here so timing is
   consistent and tunable in one place. The JS sequencer mirrors these.
   ?stage_slowmo=N adds body.cc-slowmo to step through animations frame by
   frame during verification.
   ════════════════════════════════════════════════════════════════════════ */
:root {
  --t-instant: 120ms;
  --t-quick:   220ms;
  --t-base:    320ms;
  --t-settle:  480ms;
  --t-slow:    720ms;
  --t-cine:   1100ms;
  --e-soft: cubic-bezier(0.22, 1, 0.36, 1);   /* enters — overshoot-free settle */
  --e-cine: cubic-bezier(0.4, 0, 0.2, 1);     /* moves — material standard */
  --e-rise: cubic-bezier(0.32, 0.5, 0.32, 1); /* reveals — gentle rise */
}
/* Verification slow-motion: 4× every token so an agent can inspect each
   frame of a transition. Toggled by ?stage_slowmo (JS adds the class). */
body.cc-slowmo {
  --t-instant: 480ms;
  --t-quick:   880ms;
  --t-base:   1280ms;
  --t-settle: 1920ms;
  --t-slow:   2880ms;
  --t-cine:   4400ms;
}

/* ─── Atrium-stage layout — already flex via journal.css. ────────────── */
body.in-atrium .atrium-stage {
  gap: 0;
}

/* The canonical stage is position:fixed sized to var(--chamber-h) (an iOS
   visualViewport keyboard-tracking value). When that resolves shorter than
   the window (e.g. a controlled browser, or transient iOS states), a strip
   of the body shows below the fixed stage — and the body was pure #000 while
   the chamber's void bottom is #020205, so the seam read as a "black bar."
   Paint the body the chamber's deepest void so any gap is seamless. */
body.in-atrium {
  background-color: #020205 !important;
}

/* ═══ SPATIAL RHYTHM — the v9 model ═══════════════════════════════════
   STANDBY (no room open): the orb + grounding line + greeting + offers
   float in the VERTICAL CENTER of the chamber. Calm, premium, present —
   not a dashboard, not an empty page. The living orb IS the centerpiece.

   IN A ROOM: the cluster rises to the top and .cc-stage (flex: 1) absorbs
   the freed vertical space, so the tableau fills the chamber down to just
   above the input. Nothing is ever "smashed at the top with a void below."
   ═══════════════════════════════════════════════════════════════════════ */
/* Bound the chamber to the viewport so the inner stage (flex:1) can be
   constrained and SCROLL internally instead of overflowing past the input.
   The real journal.css uses min-height:100vh (unbounded) which let tall
   vault rows run off the bottom and collide with the input/base — exactly
   the "text bar gets jammed when info goes low" bug. Scoped to chrome
   states only (NOT voice mode) so canonical modes are untouched. */
body.in-atrium.chamber-neutral .atrium-stage,
body.in-atrium.chamber-in-room .atrium-stage {
  height: 100vh;
  max-height: 100vh;
  overflow: hidden;
}
body.in-atrium.chamber-neutral .atrium-stage {
  justify-content: center;       /* center the standby cluster vertically */
  padding-bottom: 0;             /* offers are pinned to the bottom now, not in flow */
}
body.in-atrium.chamber-in-room .atrium-stage {
  justify-content: flex-start;   /* cluster rides the top; stage fills below */
  /* Reserve the bottom strip for the absolute input bar (~88px) + base
     hairline (~30px). The flex content box ends ABOVE this, so the stage's
     box never reaches the input/base — no overlap, the panel scrolls within
     its own (now shorter) box. This is the real fix for the row/base
     collision: reserve on the CONTAINER, not as inner padding.
     Dynamic (was a static 122px): the input bar's own bottom padding tracks
     max(24px, safe-area-inset-bottom), so the reserve must too — otherwise a
     larger safe-area shrinks the stage and the last rows clip. */
  padding-bottom: calc(88px + max(24px, env(safe-area-inset-bottom)) + 30px);
}
/* In standby, lift the cluster slightly above true-center so the orb has
   headroom and the offers sit on the optical midline, not the math one. */
body.in-atrium.chamber-neutral .atrium-stage .atrium-orb-zone {
  padding-top: 0 !important;
}
/* SLEEK MINIMAL IDLE REDESIGN
   The center is reduced to a single focal cluster: orb + net + one verdict
   line. All date/time/weather context lives ONLY in the corners. The offers
   drop to a quiet, evenly-spaced command row pinned near the bottom edge — so
   the center floats in deliberate negative space (Jarvis, not a card). */
body.in-atrium.chamber-neutral .cc-date-eyebrow { display: none; }  /* date moved to the corner */
/* Tight cluster: ~32px between the orb and the greeting (was a 96px hole). */
body.in-atrium.chamber-neutral .cc-hero { gap: 0; padding-top: 32px; }
/* LEFT TRAY — the standby starter offers are a slim vertical column on the left
   edge, OFF the orb. They slide in from the left in standby and slide out (left)
   when you enter a room or talk to her. Jake (2026-06-03): hated the 3 options
   "following the orb", and they glitched on tableau exit — being out of the orb's
   flow kills both. (Conversation offers still appear UNDER her answer via
   .cc-lane-actions — these are only the standby starters.) */
/* TRANSITION-based, NOT an animation. The old ccTrayIn animation used `both`
   fill, which HELD the offers visible (opacity 1) even after you entered a room —
   that's why "the options stay when you go into a table" (Jake). A transition
   can't stick: the tray is slid OUT by default and slid IN only in clean standby,
   so any other state cleanly reverts. */
body.in-atrium .cc-offers {
  position: fixed;
  left: 46px; top: 50%; right: auto; bottom: auto;
  margin: 0; padding: 0;
  flex-direction: column; flex-wrap: nowrap;
  align-items: flex-start; justify-content: center;
  gap: 5px; width: auto; max-width: 280px; z-index: 6;
  opacity: 0;
  /* Slide its FULL width + the 46px left offset off-screen so a wide tray
     (long labels) clears completely — a fixed -180px left up to ~146px of a
     280px tray still showing ("doesn't fully disappear"). */
  transform: translate(calc(-100% - 60px), -50%);
  pointer-events: none;
  /* ONE transition for every entry/exit path so the slide always eases the
     same (the in-room/returning block below uses the identical value).
     650ms opacity to MATCH the hero / stage / input bar exactly (was 460ms — the
     tray "popped in fast as fuck" while the other three eased in at 650ms, Jake
     2026-06-04). Transform keeps its slide easing but the same 650ms span, so the
     four quadrants finish on one beat. */
  transition: opacity 1000ms cubic-bezier(0.32, 0.7, 0.32, 1),
              transform 1000ms cubic-bezier(0.22, 1, 0.36, 1);
}
/* Slid IN — only in clean standby (once the awakening reveal tagged .is-ready). In
   a room / returning / thinking / working / voice → none of these match, so the
   tray transitions back to its slid-out default and fades off to the left. */
body.in-atrium.chamber-neutral:not(.chamber-returning):not(.loryn-thinking):not(.chamber-working):not(.in-voice-mode) .cc-offers.is-ready {
  opacity: 1;
  transform: translate(0, -50%);
  pointer-events: auto;
  /* On the OPENING, the offers reveal in lock-step with the hero + tableau (all
     four quadrants land together — Jake: "options, text bar, hero, tableaus
     should happen at the same time"). Same 1000ms opacity as the hero (line
     ~698), no extra delay. The room-exit slide keeps its own snappier base
     timing (this rule only applies transitioning INTO standby). */
  transition: opacity 1000ms cubic-bezier(0.22, 1, 0.36, 1),
              transform 1000ms cubic-bezier(0.22, 1, 0.36, 1);
}

/* IN A ROOM — strip the standby hero to almost nothing so the TABLEAU is
   the surface, not crushed under a giant number + label + offers. Jake:
   "the screen is being crushed by useless shit... the giant number I hate,
   the booknet today, the options." The book net already lives in the panel
   footer, so it is NOT repeated big up top. What remains in-room: a small
   orb (her quiet presence) + one slim verdict line of context. Everything
   else gives way to the data. */
body.in-atrium.chamber-in-room .atrium-stage .atrium-orb-zone {
  padding: 12px 0 0 !important;
}
body.in-atrium.chamber-in-room .atrium-stage .atrium-orb {
  width: 48px !important; height: 48px !important;
}
/* ORB ENTER/EXIT MOTION is now done with a JS FLIP in setActiveRoom (command-
   chamber-chrome.js _flipOrbZone). The old fixed-distance keyframes (orbGlideUp
   from +30vh, orbGlideDown from -30vh) GUESSED the start position, so when the
   guess didn't match where the orb actually sat (top in-room vs centered in
   standby) the orb JUMPED then glided — the "glitches like crazy on exit" Jake
   hit. FLIP measures the orb's real before/after rects and tweens the exact
   delta, so it can never jump. Don't reintroduce a transition on .atrium-orb-zone
   here — the FLIP sets it inline for the duration only. */
body.in-atrium.chamber-in-room .cc-hero-label,
body.in-atrium.chamber-in-room .cc-hero-number,
body.in-atrium.chamber-in-room .cc-date-eyebrow {
  display: none !important;
}
/* In a room (or returning to standby), the left tray slides OUT to the left edge
   + fades — fixed & transitioned so it eases away instead of cutting, and never
   sits under the orb to glitch on exit. */
body.in-atrium.chamber-in-room .cc-offers,
body.in-atrium.chamber-neutral.chamber-returning .cc-offers {
  position: fixed;
  left: 46px; top: 50%; right: auto; bottom: auto;
  margin: 0; padding: 0;
  flex-direction: column; align-items: flex-start;
  opacity: 0;
  transform: translate(calc(-100% - 60px), -50%);   /* full slide off-screen left */
  pointer-events: none;
  animation: none !important;
  /* SAME timing as the default block above — so exiting into a room eases
     identically to returning to standby (was 380/460 vs 460/540 → the
     "different animation on different entry/exit" Jake hit). */
  transition: opacity 460ms cubic-bezier(0.22, 1, 0.36, 1),
              transform 540ms cubic-bezier(0.22, 1, 0.36, 1);
  z-index: 6;
}
body.in-atrium.chamber-in-room .cc-hero {
  gap: 0; padding: 4px 60px 8px;
}
body.in-atrium.chamber-in-room .cc-hero-verdict {
  font-size: 12.5px; font-weight: 300;
  color: rgba(232, 240, 255, 0.5);
  letter-spacing: 0.04em;
}

/* ─── Push the orb UP and shrink it to match prototype v9-cinematic. ─── */
/* Canonical .atrium-orb-zone has 120px top padding + 200px orb. Prototype
   spec (.orb-wrap in v9-cinematic) is 92×92 with 22px top padding.
   Override only when chamber chrome is active. */
body.in-atrium .atrium-stage .atrium-orb-zone {
  padding: 22px 0 8px !important;
}
body.in-atrium .atrium-stage .atrium-orb {
  width: 92px !important;
  height: 92px !important;
  --orb-base-scale: 1 !important;
}
/* journal.css applies `transform: scale(0.4)` on body.atrium-active .atrium-orb
   to collapse the canonical 200px orb to 80px once a conversation starts.
   With chamber chrome mounted, the wrap is already 92px (prototype spec) so
   the 0.4 shrink would collapse the visible orb to ~37px. Lock the scale to 1
   on ANY state where chamber chrome is mounted, not just atrium-active. */
body.in-atrium .atrium-stage .atrium-orb,
body.in-atrium.atrium-active .atrium-stage .atrium-orb,
body.in-atrium.chamber-neutral .atrium-stage .atrium-orb,
body.in-atrium.loryn-greeting-only .atrium-stage .atrium-orb {
  transform: scale(1) !important;
}
/* The standby greeting ("Good afternoon, sir…") belongs to standby only — once a
   room is open it was lingering pinned under the orb on top of the room header.
   Hide it in any room. (sweep bug #4) */
body.in-atrium.chamber-in-room .atrium-orb-greeting { display: none !important; }

/* ─── Premium atmosphere — KEEP the canonical glow alive. ─────────────
   Earlier this was dimmed to 0.22 which killed the radiant feel Jake
   wants. The canonical chamber's wisps + orb breathing ARE the premium
   atmosphere; let them shine. Standby leans brighter (the orb is the
   whole show); in a room we ease it back so the tableau reads cleanly. */
/* The canonical chamber's wisps ARE the ambiance. 0.85 over-revealed them so
   individual wisps read as discrete oval blobs (Jake's "obvious problem").
   A moderate level keeps them as soft diffuse haze — present, blue, premium,
   but never a distinct shape. */
/* Calmed (was 0.55 / 0.4): the wisps still drift — cinematics + depth intact —
   but faint enough to read as dark atmosphere, not blue pulsing lights. The
   journal is near-black with blue only as accent; this keeps the chamber in
   that family while preserving the living motion. */
/* Field wisps OFF — they were the blue haze over the floor. Black like the
   journal = no drifting blue clouds. The orb (its own glow) is the only light;
   the entrance/orb/room cinematics don't depend on these field wisps. */
body.in-atrium .atrium-stage .atrium-ambient {
  display: none !important;
}

/* NO custom orb bloom pseudo-element. The canonical orb SVG already carries
   its own glow (orb-outer / orb-halo / orb-mist-glow) and constructs them
   during the awakening. A separate ::after bloom (a) appeared BEFORE the orb
   finished constructing and (b) sat at the orb-zone center, not the orb's
   optical center — so it read as a detached glow. Removed. Radiance now comes
   from the v9-style blue-hue ambient (below), which is depth, not a halo
   bolted onto the orb. */

/* ─── v9 blue-hue void + ambient pools ────────────────────────────────
   Jake: "the v9 looks better in the ambient glow, the blue hue." v9 layers
   a void gradient (near→mid→far) plus slow-drifting blue radial POOLS behind
   everything. We paint that onto the chamber so the room has the same depth
   and blue cast — without touching the rest of the journal (scoped to
   body.in-atrium .atrium-stage). */
/* JUST the v9 void gradient — a smooth near→mid→far vertical fade for the
   blue-black depth. No centered radial and NO custom pools: both read as
   discrete oval blobs (Jake's eyesore). The blue cast + drift come entirely
   from the canonical .atrium-wisp elements, now at a moderate opacity so
   they're soft haze, not shapes. */
body.in-atrium .atrium-stage {
  /* Flat near-black like the journal. This was a #0a0c14→#020205 blue-grey
     GRADIENT with !important — the real, hidden source of "still blue": it
     out-specificity'd every token/override I tried. The field is now black;
     the orb is the only light. Uses the journal's own --vv-void-far token so
     the chamber field and journal field are byte-identical (no seam). */
  background: var(--vv-void-far) !important;
}

/* ─── Splash handoff guard. ───────────────────────────────────────────
   The entrance splash (#splash, with "You're up early, sir." + ENTER) is
   the pre-chamber gate. Once we're actually IN the chamber (body.in-atrium,
   set only AFTER the user clicks ENTER), the splash MUST be gone — otherwise
   its greeting text lingers and overlaps the hero number (the collision Jake
   saw: "You're up early, sir." printed on top of "+$12,292"). This fires
   only post-entry, so the splash gate + the orb's self-construction are
   completely untouched. Fade for grace, then fully remove from interaction. */
body.in-atrium #splash,
body.in-atrium .splash {
  opacity: 0 !important;
  visibility: hidden !important;
  pointer-events: none !important;
  transition: opacity 500ms cubic-bezier(0.32, 0.7, 0.32, 1),
              visibility 0s linear 500ms;
}

/* ─── Hide canonical convo + workstation; they bleed visually. Content
   gets mirrored into our slots via JS. ──────────────────────────────── */
body.in-atrium #atriumConvo { display: none !important; }
body.in-atrium #chamberWorkstation:not(.cc-stage-host) {
  /* If workstation hasn't been adopted into cc-stage yet, hide it. */
  display: none !important;
}

/* ─── Currently In · top-left absolute (margin, doesn't collide) ────── */
/* Positioned below + right of the back arrow so they don't overlap. */
body.in-atrium .atrium-stage .cc-currently-in {
  position: absolute;
  top: 30px; left: 68px;
  z-index: 5;
  font-size: 9px; font-weight: 700;
  letter-spacing: 0.28em; text-transform: uppercase;
  color: rgba(232, 240, 255, 0.24);
  display: inline-flex; gap: 8px; align-items: baseline;
  font-family: -apple-system, BlinkMacSystemFont, 'Inter', system-ui, sans-serif;
  opacity: 0;
  transition: opacity 1000ms cubic-bezier(0.32, 0.7, 0.32, 1);
  pointer-events: none;
}
body.in-atrium .atrium-stage .cc-currently-in.is-ready { opacity: 1; }
body.in-atrium .atrium-stage .cc-currently-in .cc-name {
  color: #7ab8f5; opacity: 0.85;
}

/* ─── Room rail · right-edge absolute (margin, doesn't collide) ─────── */
body.in-atrium .atrium-stage .cc-room-rail {
  position: absolute;
  right: 22px; top: 50%;
  transform: translateY(-50%);
  z-index: 6;
  display: flex; flex-direction: column;
  gap: 6px;
  padding: 14px 6px;
  border-radius: 24px;
  background: rgba(255, 255, 255, 0.014);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.03);
  opacity: 0;
  transition: opacity 1000ms cubic-bezier(0.32, 0.7, 0.32, 1);
}
body.in-atrium .atrium-stage .cc-room-rail.is-ready { opacity: 1; }
.cc-rail-btn {
  position: relative;
  width: 36px; height: 36px;
  border: 0; background: transparent;
  display: flex; align-items: center; justify-content: center;
  border-radius: 12px;
  cursor: pointer;
  color: rgba(232, 240, 255, 0.42);
  transition: color 280ms cubic-bezier(0.32, 0.7, 0.32, 1), background 280ms;
}
/* Standby/home glyph: the discoverable door back to standby. Only meaningful
   inside a room, so hidden in standby. Orb-blue tint signals "back to her". */
.cc-rail-home { display: none !important; }
body.chamber-in-room .cc-rail-home {
  display: flex !important;
  color: rgba(122, 184, 245, 0.80);
  margin-bottom: 10px;   /* set apart from the room glyphs below */
}
body.chamber-in-room .cc-rail-home:hover { color: rgba(170, 210, 255, 0.98); background: rgba(122, 184, 245, 0.08); }
.cc-rail-btn:hover { color: rgba(232, 240, 255, 0.96); background: rgba(255, 255, 255, 0.02); }
.cc-rail-btn.is-active { color: #7ab8f5; background: rgba(122, 184, 245, 0.06); }
.cc-rail-btn svg { width: 18px; height: 18px; display: block; }
.cc-rail-btn .cc-rail-dot {
  position: absolute; right: -2px; top: 50%;
  transform: translateY(-50%);
  width: 3px; height: 3px; border-radius: 50%;
  background: #7ab8f5;
  box-shadow: 0 0 6px rgba(122, 184, 245, 0.42);
  opacity: 0;
  transition: opacity 280ms;
}
.cc-rail-btn.is-active .cc-rail-dot { opacity: 1; }
.cc-rail-btn .cc-rail-label {
  position: absolute; right: 46px; top: 50%;
  transform: translateY(-50%) translateX(8px);
  padding: 7px 13px;
  background: rgba(10, 12, 20, 0.92);
  backdrop-filter: blur(10px);
  border: 1px solid rgba(255, 255, 255, 0.06);
  border-left: 1px solid rgba(122, 184, 245, 0.30);
  border-radius: 9px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.40);
  font-size: 12.5px; font-weight: 400;
  letter-spacing: 0.012em; text-transform: none;
  color: rgba(232, 240, 255, 0.94);
  white-space: nowrap;
  opacity: 0; pointer-events: none;
  transition: opacity 320ms, transform 320ms cubic-bezier(0.32, 0.7, 0.32, 1), color 320ms cubic-bezier(0.32, 0.7, 0.32, 1);
  font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Inter', system-ui, sans-serif;
}
/* (Rail numbers removed — they read out of order and added nothing; the room
   name alone is cleaner.) */
.cc-rail-btn:hover .cc-rail-label,
.cc-rail-btn:focus-visible .cc-rail-label {
  opacity: 1; transform: translateY(-50%) translateX(0);
}
/* When a rail button is the active room, suppress the tooltip — the
   "Currently In · The Vault" indicator top-left already says where we are,
   and the tooltip otherwise hangs over the vault panel content. */
.cc-rail-btn.is-active .cc-rail-label,
.cc-rail-btn.is-active:hover .cc-rail-label,
.cc-rail-btn.is-active:focus-visible .cc-rail-label {
  opacity: 0 !important; pointer-events: none;
}

/* ─── In-flow chrome elements — date, hero, offers, stage, base ──── */
/* These get spliced into atrium-stage's flex children AFTER orb-zone. */
.cc-date-eyebrow {
  font-family: -apple-system, BlinkMacSystemFont, 'Inter', system-ui, sans-serif;
  font-size: 10px; font-weight: 700;
  letter-spacing: 0.28em; text-transform: uppercase;
  color: rgba(232, 240, 255, 0.42);
  font-variant-numeric: tabular-nums;
  text-align: center;
  padding: 4px 0 0;
  /* Hug the text, centered — NOT a full-width block. A full-width centered
     element's box spans to the edges and "overlaps" corner elements (e.g.
     the rail hover tooltip) by the box-based linter, even though the text
     doesn't visually touch. Content-width + self-center is correct and keeps
     the visual sweep honest. */
  align-self: center;
  width: max-content; max-width: 92vw;
  opacity: 0;
  transition: opacity 1000ms cubic-bezier(0.32, 0.7, 0.32, 1);
}
.cc-hero {
  font-family: -apple-system, BlinkMacSystemFont, 'Inter', system-ui, sans-serif;
  display: flex; flex-direction: column; align-items: center;
  gap: 6px;
  /* No top padding — the neutral-state override owns the orb→hero gap
     (padding-top:32px). A 4px top here used to sneak past that override. */
  padding: 0 60px;
  opacity: 0;
  transition: opacity 1000ms cubic-bezier(0.32, 0.7, 0.32, 1);
}
.cc-hero-label {
  font-size: 9px; font-weight: 600;
  letter-spacing: 0.34em; text-transform: uppercase;
  color: rgba(232, 240, 255, 0.26);
  line-height: 1;
}
/* Jarvis-minimal: the net is a quiet, refined readout — a calm mono figure,
   NOT a shouting 48px slab. Clean, organized, restrained glow. */
.cc-hero-number {
  /* Journal font (San Francisco), NOT monospace — the --ct-mono fallback
     rendered the number blocky/"Minecraft". tabular-nums keeps digits aligned. */
  font-family: -apple-system, BlinkMacSystemFont, 'Inter', system-ui, sans-serif;
  font-size: 27px; font-weight: 300;
  letter-spacing: 0.005em;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
.cc-hero-number.is-win {
  color: rgba(208, 224, 245, 0.92);
  text-shadow: 0 0 12px rgba(122, 184, 245, 0.18);
}
.cc-hero-number.is-loss {
  /* Ash, NEVER red */
  color: rgba(180, 188, 200, 0.82);
  text-shadow: none;
}
.cc-hero-number.is-flat { color: rgba(232, 240, 255, 0.58); }
.cc-hero-verdict {
  font-size: 14px; font-weight: 300;
  letter-spacing: 0.012em;
  color: rgba(232, 240, 255, 0.68);
  max-width: 600px;
  text-align: center;
  line-height: 1.55;
  min-height: 1.55em;
  /* Hard safety net: the verdict is a single spoken line. Even if something
     sets a long string here, clamp to 2 lines so it can NEVER overflow and
     crush the orb / offers / stage below it. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.cc-offers {
  font-family: -apple-system, BlinkMacSystemFont, 'Inter', system-ui, sans-serif;
  /* gap is owned by the neutral-state override (single source of truth: 48px).
     Offers only render in neutral; in-room they are display:none. */
  display: flex; flex-wrap: wrap;
  justify-content: center;
  padding: 4px 60px 0;
  max-width: 760px;
  margin: 0 auto;
  opacity: 0;
  transition: opacity 1000ms cubic-bezier(0.32, 0.7, 0.32, 1);
}
.cc-offer {
  flex: 0 1 auto;
  border: 0; background: transparent;
  color: rgba(232, 240, 255, 0.62);
  padding: 10px 0 10px 22px;
  font-size: 13px;
  letter-spacing: 0.022em;
  font-weight: 300;
  font-family: inherit;
  cursor: pointer;
  position: relative;
  transition: color 320ms cubic-bezier(0.32, 0.7, 0.32, 1);
}
.cc-offer::before {
  content: '›';
  position: absolute;
  left: 4px; top: 50%;
  transform: translateY(-52%);
  color: rgba(122, 184, 245, 0.65);
  font-size: 15px; line-height: 1;
  transition: color 220ms cubic-bezier(0.32, 0.7, 0.32, 1), transform 280ms;
}
.cc-offer::after {
  content: '';
  position: absolute;
  left: 22px; right: 0; bottom: -1px;
  height: 1px;
  background: rgba(122, 184, 245, 0.45);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 320ms cubic-bezier(0.32, 0.7, 0.32, 1);
}
.cc-offer:hover { color: rgba(232, 240, 255, 0.96); }
.cc-offer:hover::before {
  color: rgba(170, 210, 255, 0.95);
  transform: translateY(-52%) translateX(2px);
}
.cc-offer:hover::after { transform: scaleX(1); }

/* Swap offer — "Open the …" jumps you to the room the answer belongs to (she
   never moves you unasked). Reads as the lead action: a touch brighter, with a
   trailing arrow that eases right on hover to say "this takes you there." */
.cc-swap-offer { color: rgba(232, 240, 255, 0.78); }
.cc-swap-arrow {
  display: inline-block; margin-left: 8px;
  color: rgba(122, 184, 245, 0.72);
  transform: translateX(0);
  transition: transform 280ms cubic-bezier(0.32, 0.7, 0.32, 1), color 220ms ease;
}
.cc-swap-offer:hover .cc-swap-arrow {
  transform: translateX(4px);
  color: rgba(170, 210, 255, 0.96);
}

/* Press + keyboard-focus feedback (sweep P2: controls had hover but no :active
   press or :focus-visible ring — felt dead + inaccessible). */
.cc-offer, .cc-rail-btn { transition: color .2s ease, background .2s ease, transform .12s cubic-bezier(0.32,0.7,0.32,1); }
.cc-offer:active, .cc-rail-btn:active { transform: translateY(0.5px) scale(0.98); }
.cc-offer:focus-visible, .cc-rail-btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 1px rgba(122, 184, 245, 0.55);
  border-radius: 8px;
}

/* ─── Stage — where the active room's primary tableau lives ─────────── */
/* No artificial max-height / overflow-y. The prototypes use natural flow:
   the panel grows, the chamber follows. Jake called out the scrollable-inside-
   stage pattern as wrong — replaced with flow layout. The chamber as a whole
   may scroll the viewport (if content is taller than the screen) but the
   panel itself never has its own inner scrollbar. */
.cc-stage {
  width: 100%;
  max-width: 1400px;
  margin: 12px auto 0;
  /* Positioning context so an OUTGOING room (tagged .cc-room-exit + absolute
     during a room→room navigation) floats over the stage and dissolves without
     shoving the incoming holder mounted beneath it. */
  position: relative;
  padding: 0 40px 0 40px;
  /* v9 model: the stage GROWS to absorb the free vertical space between
     the offers and the input bar, so the tableau fills the chamber rather
     than leaving a void. flex:1 with min-height:0 lets it both grow and
     shrink (so the inner panel scrolls instead of pushing the input). */
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  opacity: 0;
  /* SYNC: the tableau reveals at the SAME instant as the hero/offers/input bar
     (all gated by is-ready at ~3200ms), 650ms happy-medium fade. The old 3700ms
     delay is exactly why the tableau + hero faded in ~1s AFTER the options/text
     bar — Jake: "the four quadrants need to come in at the same time." */
  transition: opacity 1000ms cubic-bezier(0.32, 0.7, 0.32, 1);
  /* The STAGE itself doesn't scroll — the panel fills it and the panel's
     inner row-list scrolls (see .ct-panel / .ct-view in command-tableaux.css).
     That keeps the panel head + mode switcher + footer PINNED while only the
     rows move — the premium v9 pattern. */
  overflow: hidden;
}
/* Holder div (wraps the panel HTML) must fill the stage height so the
   panel's internal flex layout has a definite height to scroll against. */
.cc-stage > div { width: 100%; height: 100%; display: flex; flex-direction: column; }
/* CENTER the tableau vertically in the stage when viewing a room — a short panel
   (Brief, Day, Risk) sat jammed at the TOP with dead space below it (Jake: "tables
   are jammed upward, not nicely in the middle"). justify-content:center floats it
   in the middle; a tall panel (Vault audit) still fills + scrolls internally
   because its view is height-bounded, so nothing clips. NOT applied while working
   (orb docked at top for her answer) — that posture wants the panel high. */
body.in-atrium.chamber-in-room .cc-stage > div { justify-content: center; }
/* max-width caps the wide span AND reserves room for the right rail at narrower
   desktop widths. At 1600px this stays 1280; at 1280/900px laptop widths it
   shrinks so the panel's right edge never runs under the rail (QA P1 — Day's
   STATUS column clipped under the rail at 1280×800). Done on the panel, NOT via
   stage padding, which previously broke the room-switch alignment. */
.cc-stage .ct-panel { max-width: min(1280px, calc(100vw - 170px)); width: 100%; }
/* Dense rooms (Audit = all 20+ accounts, long session logs) overflowed the
   height-bounded cc-stage and got CLIPPED by overflow:hidden — the bottom rows
   were invisible and ran under the input bar. The internal flex-scroll chain
   wasn't bounding the active view, so bound it explicitly: it now scrolls
   inside the chamber instead of clipping. Verified live (20-row audit: was
   clipped past the stage; now scrollable, every row reachable). */
body.chamber-in-room .cc-stage .ct-view.is-active {
  max-height: calc(100vh - 210px);
  overflow-y: auto;
}

/* ─── Cinematic room entry (v9 surfaceIn). ────────────────────────────
   Every time a room's panel renders, it rises + unblurs + fades in instead
   of snapping. Jake: rooms "just appear immediately... no beautiful
   cinematic transitions." The panel is a fresh element on each render, so
   the animation runs once per room entry. backwards fill = starts hidden. */
.cc-stage .ct-panel,
.cc-stage .cc-placeholder {
  animation: ccSurfaceIn 720ms cubic-bezier(0.32, 0.7, 0.32, 1) backwards;
}
@keyframes ccSurfaceIn {
  from { opacity: 0; transform: translateY(20px); filter: blur(6px); }
  to   { opacity: 1; transform: translateY(0);    filter: blur(0); }
}
/* The cyan left border draws downward as the panel arrives — the v9 detail. */
.cc-stage .ct-panel { position: relative; }
.cc-stage .ct-panel::before {
  content: '';
  position: absolute; left: -2px; top: 0; bottom: 0; width: 2px;
  background: #7ab8f5;
  transform-origin: top; transform: scaleY(0);
  animation: ccBorderDraw 820ms cubic-bezier(0.32, 0.7, 0.32, 1) 120ms forwards;
  pointer-events: none;
}
@keyframes ccBorderDraw { to { transform: scaleY(1); } }

/* ─── v9 ROW STAGGER + BAR GROW — the cinematic tableau reveal. ───────
   Each row rises + unblurs + fades, cascading 70ms apart (--row-idx set in
   JS after render). Magnitude bars then grow from zero. This is the "cool
   stuff" from v9/v3 — the tableau assembles itself instead of snapping in. */
.cc-stage .ct-row,
.cc-stage .ct-audit-row,
.cc-stage .ct-tile,
.cc-stage .ct-day-row,
.cc-stage .ct-week-row,
.cc-stage .ct-log-row,
.cc-stage .ct-strat-row,
.cc-stage .ct-mem-row {
  /* v9 cinematic feel (Jake) — slower + smoother than the old 640ms snap.
     Longer settle (940ms), gentler ease, a touch more rise + blur so rows
     emerge "from the void" rather than pop in. Stagger eased to 80ms. */
  animation: ccRowStage 940ms cubic-bezier(0.32, 0.5, 0.32, 1) backwards;
  animation-delay: calc(420ms + var(--row-idx, 0) * 80ms);
}
@keyframes ccRowStage {
  from { opacity: 0; transform: translateY(13px); filter: blur(4px); }
  to   { opacity: 1; transform: translateY(0);     filter: blur(0); }
}
/* Magnitude bars grow from the left after their row has staged in. The fill
   keeps its computed width; we only animate scaleX (loss bars from the
   center-right). All bars are horizontal now (week included). */
.cc-stage .ct-bar-fill,
.cc-stage .ct-w-bar-fill {
  transform-origin: left;
  animation: ccBarGrow 1040ms cubic-bezier(0.32, 0.5, 0.32, 1) backwards;
  animation-delay: calc(1000ms + var(--row-idx, 0) * 80ms);
}
.cc-stage .ct-bar-fill.is-loss,
.cc-stage .ct-w-bar-fill.is-loss { transform-origin: right; }
@keyframes ccBarGrow {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}
/* In-place data refresh (same room, new numbers) repaints under .cc-no-entrance
   — suppress the entrance cascade so the room updates quietly instead of
   re-playing the whole staggered reveal every 15s. */
.cc-no-entrance .ct-row,
.cc-no-entrance .ct-audit-row,
.cc-no-entrance .ct-tile,
.cc-no-entrance .ct-day-row,
.cc-no-entrance .ct-week-row,
.cc-no-entrance .ct-log-row,
.cc-no-entrance .ct-strat-row,
.cc-no-entrance .ct-mem-row,
.cc-no-entrance .ct-bar-fill,
.cc-no-entrance .ct-w-bar-fill {
  animation: none !important;
}

/* When chamber is neutral (no room selected), hide the stage entirely so
   the orb + hero + offers sit centered with no empty panel underneath. */
/* Opening sequence: while the phase-1 "Good afternoon, sir." greeting holds
   the center, keep the hero number/verdict + offers fully transparent so they
   don't collide with it. They fade in (1s) once the greeting starts leaving. */
.cc-hero, .cc-offers { transition: opacity 1000ms cubic-bezier(0.32, 0.7, 0.32, 1); }
body.loryn-phase1-greeting .cc-hero,
body.loryn-phase1-greeting .cc-offers { opacity: 0 !important; }

/* ─── SEND CHOREOGRAPHY (Jake: orb must shrink + dock toward the top on send,
   and the options must clear) ───────────────────────────────────────────────
   While a message is in flight (body.loryn-thinking) the offers fade out
   immediately — the question supersedes them — and the orb shrinks + lifts so
   the focal area opens for her answer, then eases back when she settles. The
   orb-zone moves with transform ONLY (translate + scale) so it never reflows
   the layout beneath it. First step of the §3 orb phase machine. */
body.in-atrium .atrium-stage .atrium-orb-zone {
  /* Both padding (the dock travel) and transform ease, so the orb GLIDES
     between its centered idle posture and the top dock — never a snap. */
  transition: transform 560ms cubic-bezier(0.22, 1, 0.36, 1),
              padding 600ms cubic-bezier(0.32, 0.5, 0.32, 1);
}

/* ── CONDUCTOR — the WORKING POSTURE (one rule, chat AND voice) ──────────────
   When Loryn is WORKING she docks SMALL at the TOP so the stage below is clear
   for the tableau; when IDLE she is large + centered. This unifies chat-with-
   tableau, prose answers, AND voice under ONE posture, and it PERSISTS through
   the whole answer (cleared only on return to standby). It mirrors the proven
   in-room dock geometry, so with the eased padding+size transitions the orb
   TRAVELS to the top rather than snapping. Replaces the old transient
   loryn-thinking dock, which popped the orb back to centre the instant a reply
   arrived (Jake: "loryn still does not become small and move to the top"). */
body.in-atrium.chamber-working .atrium-stage .atrium-orb-zone {
  padding: 12px 0 0 !important;
}
body.in-atrium.chamber-working .atrium-stage .atrium-orb {
  width: 48px !important;
  height: 48px !important;
}
/* The standby cluster steps aside while working so the orb truly sits at the
   top and the stage owns the space below. Her verdict caption (.cc-hero-verdict)
   is intentionally NOT hidden — that's her answer line over the tableau. */
body.in-atrium.chamber-working .cc-hero-label,
body.in-atrium.chamber-working .cc-hero-number,
body.in-atrium.chamber-working .cc-date-eyebrow,
body.in-atrium.chamber-working .cc-offers { display: none !important; }
/* Top-align the column when working so the orb rides the TOP even when there's
   no tableau yet (a prose answer, or voice still listening) — chamber-neutral
   otherwise centers the cluster, which is why voice "put the orb in the middle." */
body.in-atrium.chamber-working .atrium-stage {
  justify-content: flex-start !important;
}
/* CONVERSATION — a prose reply (greeting, chat, a spoken answer with no tableau)
   should sit CENTERED in the space, not jammed to the top edge with a void
   below. The holder fills the stage; centering its content vertically gives her
   words room to breathe near the orb instead of clinging to the ceiling. */
body.in-atrium.chamber-prose .cc-stage-response {
  justify-content: center;
  min-height: 100%;
}

/* ── CONDUCTOR — SPEAK beat: her words REVEAL, never pop ─────────────────────
   Jake: "the prose just shows up on screen immediately in the tableaux." Every
   new answer's verdict line + prose document now fade+rise in (soft mask), so
   her words arrive like she's speaking them — before the tableau cascades
   below. Triggered once per NEW answer (presentResponse), re-armed via reflow,
   so the 15s data refresh never replays it. */
@keyframes ccProseReveal {
  from { opacity: 0; transform: translateY(9px); filter: blur(1.5px); }
  to   { opacity: 1; transform: none;            filter: none; }
}
.cc-stage .cc-prose-in,
[data-cc="hero-verdict"].cc-prose-in {
  animation: ccProseReveal 640ms cubic-bezier(0.32, 0.5, 0.32, 1) both;
}
/* When the answer is a prose document, reveal its paragraphs in a gentle
   cascade (each a beat after the last) so it reads as composed, not dumped. */
.cc-stage .cc-prose-in.strategy-prose > * {
  animation: ccProseReveal 700ms cubic-bezier(0.32, 0.5, 0.32, 1) both;
  animation-delay: calc(120ms + var(--p-idx, 0) * 90ms);
}
body.in-atrium.loryn-thinking .cc-offers {
  opacity: 0 !important;
  pointer-events: none;
  transition: opacity 220ms cubic-bezier(0.32, 0.7, 0.32, 1) !important;
}

/* ── POINTING (P3) — she lights up the row she's talking about, in the room
   you're already looking at. Additive only: the target brightens + takes a cyan
   left edge; a slow one-shot glow draws the eye, then settles to a held marker.
   Nothing else dims — the data never dims. This is the Jarvis gesture: she
   points at the thing in your view instead of re-printing it elsewhere. */
@keyframes ccAnchorArrive {
  0%   { box-shadow: inset 3px 0 0 rgba(122,184,245,0),    0 0 0 1px rgba(122,184,245,0);    background: rgba(122,184,245,0); }
  35%  { box-shadow: inset 3px 0 0 rgba(122,184,245,0.95), 0 0 22px 2px rgba(122,184,245,0.22); background: rgba(122,184,245,0.10); }
  100% { box-shadow: inset 3px 0 0 rgba(122,184,245,0.7),  0 0 0 1px rgba(122,184,245,0.16); background: rgba(122,184,245,0.05); }
}
.cc-stage .cc-anchor-hot {
  border-radius: 5px;
  position: relative; z-index: 1;
  animation: ccAnchorArrive 1200ms cubic-bezier(0.32, 0.5, 0.32, 1) both;
}

/* ── THINKING CUE — the moment you ask, she's visibly on it. Three dots breathe
   beneath the orb through the THINKING beat, then vanish the instant she speaks.
   No more dead silence while she works (Jake: "sitting in silence drives users
   nuts"). */
.cc-thinking-cue {
  display: none;
  justify-content: center; gap: 6px;
  margin-top: 9px; height: 6px;
}
body.in-atrium.cc-state-thinking .cc-thinking-cue { display: flex; }
.cc-thinking-cue span {
  width: 5px; height: 5px; border-radius: 50%;
  background: rgba(122, 184, 245, 0.7);
  animation: ccThinkPulse 1400ms ease-in-out infinite;
}
.cc-thinking-cue span:nth-child(2) { animation-delay: 220ms; }
.cc-thinking-cue span:nth-child(3) { animation-delay: 440ms; }
@keyframes ccThinkPulse {
  0%, 100% { opacity: 0.18; transform: translateY(0); }
  50%      { opacity: 0.9;  transform: translateY(-2px); }
}

/* ════════════════════════════════════════════════════════════════════════
   DIALOGUE LANE — the one home for Loryn's voice when you're in a room. It
   slides in on the right; the tableau eases left and STAYS (never destroyed,
   never dimmed). Her words live here; the rows she names light up in the
   tableau. At standby she answers centered instead — no lane. (Jake, 2026-06-03)
   ════════════════════════════════════════════════════════════════════════ */
.cc-dialogue {
  position: absolute;
  top: 150px; right: 74px; bottom: 78px;
  width: 372px;
  display: flex; flex-direction: column;
  padding: 2px 2px 2px 24px;
  border-left: 1px solid rgba(122, 184, 245, 0.14);
  background: linear-gradient(90deg, rgba(122, 184, 245, 0.03), rgba(0, 0, 0, 0) 62%);
  opacity: 0; transform: translateX(30px); pointer-events: none;
  transition: opacity 520ms cubic-bezier(0.22, 1, 0.36, 1),
              transform 560ms cubic-bezier(0.22, 1, 0.36, 1);
  z-index: 4;
}
body.cc-lane-open .cc-dialogue { opacity: 1; transform: none; pointer-events: auto; }
.cc-dialogue-body { height: 100%; overflow-y: auto; overflow-x: hidden; }
/* Her words use the existing from-the-void Spoken treatment, sized + left-set
   for the lane (the lane itself is clearly "her voice", so keep the channel and
   left alignment even for short replies). */
.cc-dialogue .cc-spoken,
.cc-dialogue .cc-walk { width: 100%; max-width: none; margin: 0; padding-left: 22px; text-align: left; }
.cc-dialogue .cc-spoken.is-plain { text-align: left; padding-left: 22px; }
.cc-dialogue .cc-spoken.is-plain .cc-sp-channel { display: block; }
.cc-dialogue .cc-sp-lead { font-size: 16px; }
.cc-dialogue .cc-sp-read { font-size: 13.5px; max-width: none; }
.cc-lane-actions { display: flex; flex-direction: column; gap: 2px; margin-top: 24px; padding-left: 22px; }
.cc-dialogue .cc-offer { font-size: 12.5px; padding: 8px 0 8px 20px; }

/* the tableau eases left to clear the lane — it shrinks/recenters, never hides */
body.cc-lane-open .cc-stage {
  padding-right: 430px;
  transition: padding-right 520ms cubic-bezier(0.22, 1, 0.36, 1);
}

/* ─── RETURN-TO-STANDBY DISSOLVE (Jake: "no smooth animation when clicking
   standby") ─────────────────────────────────────────────────────────────────
   setActiveRoom(null) tags the outgoing room content with .cc-room-exit and
   removes it after the transition, so the room sinks + fades as Loryn clears
   the workspace instead of cutting instantly. */
.cc-stage .cc-room-exit {
  opacity: 0 !important;
  transform: translateY(22px) scale(0.98);
  /* v9 cinematic feel — slower, smoother dissolve to match the entrance. */
  transition: opacity 520ms cubic-bezier(0.32, 0.5, 0.32, 1),
              transform 640ms cubic-bezier(0.32, 0.5, 0.32, 1);
  pointer-events: none;
}

/* MESSAGE TRANSITION — the outgoing answer RECEDES into the void (sinks down +
   fades + blurs, the reverse of the from-the-void surfacing) as the next answer
   rises. message→message reads as one going back into the dark and the next
   coming up, never a hard cut. */
.cc-stage .cc-void-exit {
  animation: ccVoidRecede 440ms cubic-bezier(0.4, 0, 0.7, 1) forwards;
  pointer-events: none;
}
@keyframes ccVoidRecede {
  from { opacity: 1; transform: none; filter: blur(0); }
  to   { opacity: 0; transform: translateY(26px) scale(0.97); filter: blur(7px); }
}

/* While RETURNING to standby, keep the stage VISIBLE so the room's dissolve
   actually shows. chamber-neutral alone display:none's the stage instantly
   (cutting the room); chamber-returning (set in JS for the dissolve duration)
   overrides that. The room sinks+fades here while the standby cluster rises in
   over the top; once the timer drops chamber-returning, the empty stage hides. */
body.chamber-neutral.chamber-returning .cc-stage {
  display: flex !important;
  opacity: 1 !important;
  pointer-events: none;
}

/* RETURN-TO-STANDBY cluster entrance (Jake: "standby has no animation"). When
   the workspace clears, the orb eases back to its idle size and the greeting +
   offers rise in — the room-entrance vocabulary, reversed. Keyed on
   chamber-neutral so it plays on entry to standby (and the first awakening),
   not on the 15s refresh (the class persists, so the animation never restarts). */
@keyframes ccStandbyRise {
  from { opacity: 0; transform: translateY(11px); }
  to   { opacity: 1; transform: translateY(0); }
}
/* During the return-to-standby DISSOLVE, keep the greeting/offers HIDDEN. The
   cc-stage is still held visible (chamber-returning) and occupies space, so if the
   text rose in now it would land in a non-final position and then JUMP up when the
   stage drops — the exit "text glitch" when the orb eases back to center (Jake,
   2026-06-03). They rise in fresh, into their settled spot, only once
   chamber-returning is gone. First awakening (never has chamber-returning) is
   unchanged. */
body.chamber-neutral.chamber-returning .cc-hero { opacity: 0; animation: none; }
/* Hold the hero hidden until `chamber-revealed` — the SINGLE mount+800 trigger that
   reveals offers / stage / input. Without this the hero's rise fired off
   `chamber-neutral` (set at first render, ~mount+0) on a different clock from the
   other three quadrants. Now it can't fire ahead of OR behind them. */
body.chamber-neutral:not(.chamber-returning):not(.chamber-revealed) .cc-hero { opacity: 0; animation: none; }
/* Hero rises in on the EXACT same instant AND the same 650ms tempo as offers / stage
   / input. Was 760ms + a 140ms head-start delay — that mismatch is what made the
   hero "fade in super slow, a second behind" (Jake, 2026-06-04). One trigger, one
   tempo = the four quadrants in unison. Return-to-standby is unchanged: chamber-revealed
   persists, so this still fires; the dissolve rule above holds it hidden meanwhile. */
body.chamber-revealed.chamber-neutral:not(.chamber-returning) .cc-hero {
  animation: ccStandbyRise 1000ms cubic-bezier(0.32, 0.7, 0.32, 1) both;
}
/* (.cc-offers no longer uses ccStandbyRise — it's the left tray now, with its own
   ccTrayIn slide-in / slide-out; a translateY rise would fight its translateY(-50%)
   vertical centering.) */
/* STANDBY = orb + tray ONLY, no floating text line. The standby read ("The
   accounts pressed today, sir…") was sitting ABOVE the orb and teleporting under
   it on exit (Jake's screenshot, the exit "text above the orb then spawns under").
   Killing it removes the glitchy text entirely. Her actual ANSWERS still headline
   here — they're rendered data-locked-by-convo="true", which this :not() spares.
   (The greeting is now ripped out at the source, so the standby verdict is the
   plain unlocked read — unlike the old boot greeting, which WAS locked and slipped
   this rule.) */
body.in-atrium.chamber-neutral .cc-hero-verdict:not([data-locked-by-convo="true"]) {
  display: none !important;
}
/* The orb eases between its in-room (48px) and standby (92px) sizes instead of
   snapping, so leaving a room visibly returns her to her resting presence. */
body.in-atrium .atrium-stage .atrium-orb {
  transition: width 620ms cubic-bezier(0.32, 0.5, 0.32, 1),
              height 620ms cubic-bezier(0.32, 0.5, 0.32, 1),
              /* Smooth MAGNETIZE on hover (Jake: "it should magnetize a little
                 bit as an indication you can click her for voice"). Without a
                 transform transition the hover scale snapped; now it eases. */
              transform 320ms cubic-bezier(0.22, 1, 0.36, 1),
              filter 320ms ease;
}

/* ─── THE BRIEF — a report reads as a research note, not a giant number with
   words crammed under it (Jake). In the chamber, the figure shrinks to a quiet
   dateline and the PROSE is the hero: a readable left-aligned column with
   generous leading and breathing room from the top. ─────────────────────── */
.cc-stage .strategy-deep {
  max-width: 620px;
  margin: 0 auto;
  padding-top: 5vh;
  gap: 18px;
}
.cc-stage .strategy-deep .strategy-hero {
  justify-self: start;
  text-align: left;
}
.cc-stage .strategy-deep .strategy-hero .strategy-stat { align-items: flex-start; text-align: left; }
.cc-stage .strategy-deep .strategy-hero .strategy-stat-label {
  font-size: 10px; letter-spacing: 0.34em; text-transform: uppercase;
  color: rgba(232, 240, 255, 0.36);
}
.cc-stage .strategy-deep .strategy-hero .strategy-stat.size-mega .strategy-stat-value,
.cc-stage .strategy-deep .strategy-hero .strategy-stat.size-hero .strategy-stat-value {
  font-size: 30px; font-weight: 300; letter-spacing: -0.01em;
}
.cc-stage .strategy-deep .strategy-prose {
  max-width: 600px; margin: 0;
  font-size: 16.5px; line-height: 1.76; font-weight: 300;
  color: rgba(232, 240, 255, 0.82);
  text-align: left;
}

/* Ambient corner telemetry — fills the dead top corners with live clock +
   market session (left) and weather + place (right). Quiet, mono, never
   competing with the orb. */
.cc-corner {
  position: absolute;
  top: 30px;
  z-index: 6;
  pointer-events: none;
  opacity: 0;
  /* Delay so the corners fade in alongside the focal cluster (hero/verdict
     reveal at ~3600ms), not before it. A 200ms delay made the supporting
     telemetry appear ~2s before the center, inverting the hierarchy. */
  transition: opacity 1400ms cubic-bezier(0.32, 0.7, 0.32, 1);
}
.cc-corner.is-ready { opacity: 1; }
/* Back button (.atrium-exit) sits at ~left:18px and is ~30px wide (right
   edge ~48px). 52px gives the corner clock 4px of breathing room off it —
   no dead gap, no overlap. Bump this if the back button is ever resized. */
.cc-corner-left  { left: 52px;  text-align: left;  max-width: 140px; }
.cc-corner-right { right: 40px; text-align: right; max-width: 140px; }
/* Premium, NOT a military watch (Jake). Monospace + heavy uppercase telemetry
   read like a Casio; light system-sans with air reads like a considered system.
   Sans, very light weight, dimmer, more letter-spacing — ambient, not a gauge. */
.cc-cn-lead {
  font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', system-ui, sans-serif;
  font-size: 15px; font-weight: 200;
  letter-spacing: 0.06em;
  color: rgba(232, 240, 255, 0.50);
  font-variant-numeric: tabular-nums;
  line-height: 1;
  /* Clip long strings gracefully instead of wrapping inside the overflow:hidden
     stage on narrow widths. */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cc-cn-sub {
  font-family: -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
  font-size: 8.5px; font-weight: 500;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: rgba(180, 188, 200, 0.28);
  margin-top: 8px;
  line-height: 1.45;
}
/* In a room the corners recede so they don't pull focus from the stage. */
body.chamber-in-room .cc-corner { opacity: 0.34; }
body.in-voice-mode .cc-corner { opacity: 0 !important; }

/* Net P&L corner readout — the day's net as quiet left-corner telemetry
   instead of a big number under the orb. Shown only in neutral/idle. */
.cc-cn-net { display: none; margin-top: 16px; line-height: 1.25; }
body.chamber-neutral .cc-cn-net { display: block; }
.cc-cn-net-label {
  display: block;
  font-size: 8.5px; font-weight: 600; letter-spacing: 0.24em;
  text-transform: uppercase;
  color: rgba(232, 240, 255, 0.32);
  line-height: 1;
}
.cc-cn-net-val {
  font-family: -apple-system, BlinkMacSystemFont, 'Inter', system-ui, sans-serif;
  /* In neutral state this corner net IS the primary focal number (the hero
     number is hidden), so it carries near-hero weight (27px) rather than the
     old subtitle-sized 18px. */
  font-size: 24px; font-weight: 300;
  letter-spacing: 0.01em;
  font-variant-numeric: tabular-nums;
  color: rgba(232, 240, 255, 0.66);
}
.cc-cn-net-val.is-win  { color: rgba(208, 224, 245, 0.92); }
.cc-cn-net-val.is-loss { color: rgba(180, 188, 200, 0.82); }
.cc-cn-net-val.is-flat { color: rgba(232, 240, 255, 0.5); }
/* Idle center = orb + greeting + offers as ONE tight cluster. The hero
   number/label are removed (display:none) — their value lives in the corner —
   and the gap is controlled explicitly below so the greeting sits a deliberate
   ~32px under the orb (tight, not the 96px hole, and no bunching/overlap). */
body.in-atrium.chamber-neutral .cc-hero-label,
body.in-atrium.chamber-neutral .cc-hero-number { display: none !important; }

/* Next-action pills — Loryn's follow-up offers, rendered below the stage
   (was invisible: they only lived in the hidden convo). Reuses .cc-offer. */
.cc-actions {
  display: flex; gap: 20px; flex-wrap: wrap;
  justify-content: center;
  margin: 26px auto 0;
  padding: 0 40px;
  max-width: 840px;
}
.cc-actions:empty { display: none; }

body.chamber-neutral .cc-stage { display: none !important; }
/* The top-left "Currently In <room>" indicator collided with the corner clock
   (both at top-left). It's redundant — the room is already labelled bottom-left
   in the base (e.g. "07 Memoir"). Kill it so the corner telemetry owns the
   top-left cleanly. */
.cc-currently-in { display: none !important; }
/* Prose answer: the full prose renders in the stage and is the answer itself,
   so the one-line verdict headline above it would just echo the prose's first
   sentence. Hide it. (Data answers keep the verdict as a headline.) */
body.chamber-prose .cc-hero-verdict { display: none !important; }
/* Workspace rooms (Notes/Objectives) own her voice in their right pane — hide
   the hero verdict so her answer never shows both top AND right. */
body.cc-ws-room .cc-hero-verdict { display: none !important; }
/* ONE rendering of her answer, NEVER two (the "SPEAKING overlap" defect). When
   her spoken/walk-through answer occupies the stage — standby conversation, or
   an in-place answer — the under-orb caption must NOT also print the same line.
   That doubled copy (dim + line-clamped above, bright + full below) collided
   into one cramped block under the orb. This is state-independent: it fires off
   the actual DOM, so it can't be defeated by a mis-set class. Data rooms render
   a tableau (no .cc-spoken in the stage), so their docked verdict line is
   untouched. */
body.in-atrium:has(.cc-stage .cc-spoken) .cc-hero-verdict,
body.in-atrium:has(.cc-stage .cc-walk)   .cc-hero-verdict { display: none !important; }
/* In prose mode the stage FILLS the space so her reply can sit centered in it
   (not jammed against the top edge with a void below — Jake, 2026-06-03). The
   holder (.cc-stage-response) centers its content vertically; next-action pills
   live in .cc-actions outside the stage, so centering the prose doesn't strand
   them. */
body.chamber-prose .cc-stage { flex: 1 1 auto !important; min-height: 0 !important; }
/* A long conversational answer in STANDBY overflowed the viewport with NO scroll
   — `.cc-stage` is overflow:hidden and nothing scrolled, so ~70% was lost (QA
   P1). Target the prose holder DIRECTLY via :has() — NOT body.chamber-prose,
   which setActiveRoom strips right after presentResponse sets it. Whenever the
   stage holds a .cc-stage-response (a spoken/walk answer), let it scroll, and
   top-align the prose so a tall answer can scroll to its top (centering clips
   the top in a scroll container). */
body.in-atrium .cc-stage:has(> .cc-stage-response) {
  overflow-y: auto !important;
  overscroll-behavior: contain;
}
.cc-stage > .cc-stage-response { justify-content: flex-start; }
/* A conversational answer is routed through the strategy stage only so the
   prose has somewhere to render — it is NOT "in the Strategy room". Hide the
   room label + base chrome so it doesn't mislabel the answer. */
body.chamber-prose .cc-currently-in,
body.chamber-prose .cc-base { display: none !important; }
body.chamber-prose .cc-rail-btn.is-active {
  color: var(--ct-text-3, rgba(180,188,200,0.5));
  background: transparent;
}
/* Likewise hide the room indicator + base room number when neutral. */
body.chamber-neutral .cc-currently-in.cc-empty { display: none !important; }
/* In standby there is no room, so the base telemetry strip (room number +
   hairline + LIVE) is just a lone grey line floating above the input —
   Jake's "weird grey line." Hide the whole base in neutral; it returns the
   moment a room opens. The breathing horizon line under the input is the
   only line in standby. */
body.chamber-neutral .cc-base { display: none !important; }
.cc-stage .cc-placeholder {
  border: 1px dashed rgba(255, 255, 255, 0.06);
  border-radius: 8px;
  padding: 28px 24px;
  text-align: center;
  font-size: 10.5px; font-weight: 700;
  letter-spacing: 0.28em; text-transform: uppercase;
  color: rgba(232, 240, 255, 0.24);
  background: rgba(255, 255, 255, 0.010);
  width: 100%; max-width: 1040px;
}

/* The canonical chamber-workstation, when adopted into our stage, just
   acts as a containing div for any surface loryn.js stages. */
.cc-stage-host {
  display: block !important;  /* override the chamber-empty hide */
  width: 100%;
  background: transparent !important;
  border: 0 !important;
  padding: 0 !important;
  margin: 0 !important;
  grid-template-columns: none !important;
  grid-template-rows: none !important;
}
.cc-stage-host.is-suppressed {
  display: none !important;
}
.cc-stage-host .chamber-zone {
  display: none !important;  /* hide the empty zone placeholders */
}
.cc-stage-host .chamber-zone:not(:empty) {
  display: block !important;  /* re-show zones that have actual content */
}

/* ─── Base hairline (bottom of chamber) ─────────────────────────────── */
/* IMPORTANT: .atrium-line is position:absolute bottom:0 in journal.css.
   That means if cc-base is in flow (default), it floats up to wherever
   the flow ends — far above the input bar. Pin it absolute too, just
   ABOVE the atrium-line so the hairline reads as the base of the chamber. */
body.in-atrium .atrium-stage .cc-base {
  position: absolute;
  left: 0; right: 0;
  bottom: 88px;  /* atrium-line is ~78-88px tall depending on safe-area; sits just above */
  z-index: 4;
}
.cc-base {
  font-family: -apple-system, BlinkMacSystemFont, 'Inter', system-ui, sans-serif;
  width: 100%; max-width: 1280px;
  /* The in-atrium rule pins this absolute with left:0/right:0; combined with
     max-width, `margin: 0 auto` is what centers the box. This is intentional,
     not dead — do not remove without replacing the centering. */
  margin: 0 auto;
  padding: 0 60px 0;
  opacity: 0;
  transition: opacity 1000ms cubic-bezier(0.32, 0.7, 0.32, 1);
}
/* No base hairline. v9's base is just the quiet room/LIVE text row — a
   separate gradient line floating under everything read as awkward (Jake).
   Removed; the base text stands on its own. */
.cc-base-hairline { display: none; }
.cc-base-stats {
  display: flex; justify-content: space-between; align-items: center;
  padding: 10px 0 0;
  font-size: 9.5px; font-weight: 700;
  letter-spacing: 0.28em; text-transform: uppercase;
  color: rgba(232, 240, 255, 0.24);
  font-variant-numeric: tabular-nums;
}
.cc-base-num {
  font-family: 'SF Mono', 'JetBrains Mono', Menlo, Consolas, monospace;
  letter-spacing: 0.10em;
  color: #7ab8f5; opacity: 0.7;
  margin-right: 6px;
}
.cc-live { display: inline-flex; align-items: center; gap: 8px; }
.cc-live-dot {
  width: 4px; height: 4px; border-radius: 50%;
  background: #7ab8f5;
  box-shadow: 0 0 8px rgba(122, 184, 245, 0.7);
  animation: ccLiveBreath 4s ease-in-out infinite;
}
@keyframes ccLiveBreath {
  0%, 100% { opacity: 0.6; }
  50%      { opacity: 1; }
}

/* ─── Reveal after canonical chamber awakening (~3.2s) ──────────────── */
body.in-atrium .atrium-stage .cc-date-eyebrow.is-ready,
body.in-atrium .atrium-stage .cc-hero.is-ready,
body.in-atrium .atrium-stage .cc-offers.is-ready,
body.in-atrium .atrium-stage .cc-stage.is-ready,
body.in-atrium .atrium-stage .cc-base.is-ready { opacity: 1; }

/* VOICE = a layer OVER the live workstation, NOT a takeover. Jake: "Voice mode
   makes no sense, it isolates the room with her orb when work is supposed to be
   getting done." §9 of the experience blueprint. The room's tableaux (.cc-stage),
   navigation (.cc-room-rail), room label (.cc-base / .cc-currently-in) and her
   verdict caption (.cc-hero) all STAY visible — you speak and watch her work;
   the workstation is always there and still operable. Only the standby
   suggestion chrome steps aside while talking (you're speaking, not tapping). */
body.in-voice-mode .cc-date-eyebrow,
body.in-voice-mode .cc-offers { display: none !important; }
/* VOICE MODE = voice only. The text bar deactivates — it dims, desaturates, and
   drops all interaction (a typed Enter is also swallowed in JS), with a smooth
   fade so it reads clearly as "off, I'm listening". Restores on exit. */
body.in-voice-mode .atrium-line {
  /* Dim the typing bar in voice mode so it reads as "off, I'm listening" — but
     DIM ONLY. Do NOT pointer-events:none the whole .atrium-line: a control that
     voice needs could live inside it, and killing the container is what broke
     voice mode (Jake, 2026-06-03). Disable only the textarea itself, below; the
     JS Enter-guard already stops a typed submit from double-responding. */
  opacity: 0.4 !important;
  filter: saturate(0.5);
  transition: opacity 520ms cubic-bezier(0.32, 0.7, 0.32, 1), filter 520ms ease;
}
body.in-voice-mode .atrium-line .atrium-input {
  pointer-events: none !important;   /* the textarea only — not the whole bar */
  transform: none !important;
}
/* Focus-pull, never blackout (§9: "dim/desaturate a touch when she speaks,
   restore when done — but DIM, never BLANK"). While SHE is speaking, the
   workspace recedes slightly so attention rides her voice; the instant she
   finishes (loryn-talking drops) it eases back to full presence. */
body.in-voice-mode .cc-stage {
  transition: opacity 460ms ease, filter 460ms ease;
}
body.in-voice-mode.loryn-talking .cc-stage {
  opacity: 0.46;
  filter: saturate(0.72);
}

/* Responsive — hide rail + currently-in on phone width */
@media (max-width: 720px) {
  .cc-room-rail, .cc-currently-in, .cc-corner { display: none !important; }
  .cc-hero-number { font-size: 44px; }
  .cc-offers { gap: 18px; padding: 0 16px; }
  .cc-stage, .cc-base { padding: 0 16px; }
}

/* ─── X-Ray boundary visualization (debug only) ─────────────────────────
   Add body.cc-xray to outline every chrome boundary. Used by the test
   harness to verify boundaries visually. Has no effect in production. */
body.cc-xray .atrium-stage > * { outline: 1px dashed rgba(255, 80, 80, 0.5) !important; outline-offset: -1px; }
body.cc-xray .cc-currently-in,
body.cc-xray .cc-room-rail,
body.cc-xray .cc-date-eyebrow,
body.cc-xray .cc-hero,
body.cc-xray .cc-offers,
body.cc-xray .cc-stage,
body.cc-xray .cc-base       { outline: 1px solid rgba(120, 220, 255, 0.7) !important; outline-offset: -1px; }
body.cc-xray .ct-panel      { outline: 1px solid rgba(255, 220, 80, 0.6) !important; outline-offset: -1px; }
body.cc-xray .ct-row,
body.cc-xray .ct-day-row,
body.cc-xray .ct-week-row,
body.cc-xray .ct-strat-row,
body.cc-xray .ct-mem-row   { outline: 1px dotted rgba(180, 255, 180, 0.4) !important; outline-offset: -2px; }

/* ════════════════════════════════════════════════════════════════════════
   LIVING STAGE — FOCUS MODES (P1)
   Attention takes turns. "Forward" always means FOCUSED — isolation, contrast,
   and space — NEVER bigger. cc-state-* is owned by the chamber state machine.

   VIEWING   = tableau full, her words rest (the existing in-room look).
   CONVERSING= you're typing → tableau dims-and-holds as context, her words +
               the input come forward so you can see her while you write.
   SPEAKING  = her words forward while she answers (full choreography in P2;
               here it just brings her line to full contrast).
   ════════════════════════════════════════════════════════════════════════ */

/* HARD RULE (learned the hard way): the tableau NEVER dims because the user is
   typing or because Loryn is mid-answer. A trader keeps the cursor in the input
   the whole time they work — dimming the data on focus dulled it constantly and
   made it unreadable. The DATA stays at full brightness, always. "Her words
   forward" is done by the reveal animation + a contrast lift on her line only —
   never by pulling the data back. */

/* Her line lifts to full contrast when she's answering or you're typing — this
   is the ONLY focus treatment, and it touches her text, not the data. Same
   size; just present. */
body.in-atrium.cc-state-conversing .cc-hero-verdict,
body.in-atrium.cc-state-speaking   .cc-hero-verdict {
  color: rgba(232, 240, 255, 0.94) !important;
  transition: color var(--t-base) var(--e-soft);
}

/* ════════════════════════════════════════════════════════════════════════
   LIVING STAGE — TURN LIFECYCLE (P2)
   The parked question + the speak→view hand-off. Her words own the moment
   (tableau held back), then the tableau lifts to full. Nothing gets big.
   ════════════════════════════════════════════════════════════════════════ */

/* The parked question — a small, dim echo of what you asked, sitting under the
   orb above her answer. Present through THINKING + SPEAKING, fades as the panel
   lifts to VIEWING. Quiet and italic so it reads as "you", not a heading. */
.cc-asked {
  font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Inter', system-ui, sans-serif;
  font-size: 11.5px; font-weight: 300; font-style: italic;
  letter-spacing: 0.01em; line-height: 1.4;
  color: rgba(232, 240, 255, 0.32);
  text-align: center; max-width: 540px; margin: 0 auto;
  opacity: 0; max-height: 0; overflow: hidden;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  transition: opacity var(--t-base) var(--e-soft),
              max-height var(--t-base) var(--e-soft);
}
.cc-asked.is-shown { opacity: 1; max-height: 3em; margin-bottom: 5px; }

/* NO stage dim during THINKING / SPEAKING. The data stays fully readable the
   whole turn — her answer arrives via its own reveal animation, the tableau is
   never pulled back. (Removed the opacity/scale hold that dulled the panel.) */
