/* ============================================================
   SAGE DESK — Story 3
   Design language inherited from the FE Lobby (US2): void ground,
   halo cyan, Cabinet Grotesk display over Satoshi, JetBrains Mono
   for HUD micro-text. All interior sizing is container-query based
   so the whole interface scales as one unit on any screen.
   Target breakpoints: phone portrait and tablet portrait.
   ============================================================ */

:root {
  /* [ORION 2026-08-16] @DEE's AETHER SAGE sheet: the orb's colour is a
     function of her STATE, so it is a variable, not a constant. desk.js
     rewrites it in setMode(); this default is STATE 5 (calm bass), which is
     what the landing screen is before any session exists. */
  --orb-tint: 150, 232, 255;
  --void:  #04070b;
  --deep:  #071219;
  --halo:  #3FE0C8;   /* A-13 verdigris — was #4ee6ff halo cyan */
  --ice:   #dff7ff;
  --ember: #ffc98a;
  --alarm: #ff7d6b;

  --font-dis:  'Cabinet Grotesk', system-ui, sans-serif;
  --font-body: 'Satoshi', system-ui, sans-serif;
  --font-mono: 'JetBrains Mono', ui-monospace, monospace;

  --ease: cubic-bezier(.22, .61, .36, 1);
  --spring: cubic-bezier(.16, 1.3, .3, 1);
}

* { box-sizing: border-box; }
/* The stage centres inside the body, so sizing the stage alone was not
   enough -- a 759 stage inside a 956 body landed at 99..858, still 99px under
   the toolbar. The body has to BE the visible band. --vvh is published from
   visualViewport; 100% remains the fallback. */
html, body { height: 100%; height: var(--vvh, 100%); }

body {
  margin: 0;
  /* [ORION 2026-08-04] card 7 — transparent so the Wix video behind the
     iframe is the background. An iframe shows through only if the EMBEDDED
     document is transparent, so every opaque layer here has to go: body,
     .bleed and .stage all painted --void before this. */
  background: transparent;
  color: var(--ice);
  font-family: var(--font-body);
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
  display: grid;
  place-items: center;
}

/* blurred fill behind the letterbox on viewports wider than 9:16 */
.bleed {
  /* was: a blurred still of the plate, filling the letterbox on wide
     viewports. With no plate and a transparent stage there is nothing to
     blur and nothing to hide — the host's own background fills it. */
  display: none;
}

/* ---------- the stage: a 9:16 field, everything inside is cq-scaled ---------- */
.stage {
  position: relative;
  z-index: 1;
  width: 100vw;
  height: 100vh;
  height: 100dvh;
  /* DEE 2026-08-08, iPhone 16 Pro Max (440x956): the dock was clipped by the
     browser bar. Inside an iframe, dvh resolves against the IFRAME's viewport
     -- not the band the visitor can actually see. Wix sizes the component to
     the LARGE viewport, so 100dvh here is 956 while only ~759 is visible, and
     anything anchored to the stage's bottom lands under the toolbar.
     --vvh is set from visualViewport by the script in index.html and wins when
     present; the dvh values above remain the fallback where it is not. */
  height: var(--vvh, 100dvh);
  max-width: calc(var(--vvh, 100dvh) * 9 / 16);
  aspect-ratio: 9 / 16;
  overflow: hidden;
  container-type: size;
  background: transparent;
  box-shadow: none;
}
@media (min-aspect-ratio: 9/16) {
  .stage { width: auto; height: var(--vvh, 100dvh); }
}

/* ---------- video plate ----------
   @DEE, twice: do not darken the wallpaper. brightness() is off entirely now --
   the plate renders at its own exposure. saturate is raised for the vibrancy he
   asked for; contrast is left alone because pushing it would crush the shadow
   detail in the room and read as darkening by another route.
   This was affordable rather than a gamble: measured before touching it, the
   text sat at 7.75:1 (worst, the back seal) against a 3.0 requirement, so there
   was 2.5x of headroom to spend on the picture. */
.plate {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  filter: saturate(1.22) contrast(1.06);
}
/* Scrim keeps chrome legible without hiding the room. It is a legibility device,
   not a mood device, so it is cut back to the minimum the text actually needs:
   top and bottom bands hold the rail and the CTA, and the middle is now almost
   clear so the room reads at full strength through the centre where the orb sits.
   Every value here is backed by a contrast measurement -- see the commit. */
.plate__scrim {
  position: absolute; inset: 0;
  background:
    linear-gradient(180deg, rgba(2,6,11,.62) 0%, rgba(2,6,11,.26) 14%,
      rgba(2,6,11,.04) 30%, rgba(2,6,11,.04) 58%, rgba(2,6,11,.40) 78%,
      rgba(2,6,11,.72) 100%),
    radial-gradient(120% 60% at 50% 32%, rgba(6,26,38,.18), transparent 70%);
}
.dust { position: absolute; inset: 0; width: 100%; height: 100%; }

/* the plasma canvas that renders SAGE's filament shell. Sized to its own
   orb container; drawn with additive compositing on a transparent ground so
   it glows over the video without any blend mode on the element itself. */

/* the luminous body: milky interior, hard bright limb, blown-out centre.
   Pure CSS so it composites once per frame and leaves the entire script
   budget to the filament mesh drawn over it. */
/* [ORION 2026-07-31, US-003a] Orb rebuilt toward @DEE's reference frame, in
   @SAGE's verdigris.
   The structural change is an INVERSION. We had a hot white core fading to a
   cooler limb -- a lamp. The reference is the opposite: a dark, dense interior
   with the luminance concentrated in a broad ring AT the edge, so the thing
   reads as a shell of energy containing something rather than a bulb emitting.
   That is why the interior below is now darker than the ground it sits on at
   the centre, and why the bright band starts at 72% instead of 90%.
   Palette is #B8FFF2 bloom -> #3FE0C8 verdigris -> #0FA890 deep, per A-13. */
.lorb__body, .orb__body {
  position: absolute; inset: 0; border-radius: 50%;
  background:
    /* THE RIM -- a broad hot band, not a hairline. This is the subject of the
       image now; everything else supports it. */
    radial-gradient(circle,
      rgba(255, 255, 255, .00) 56%,
      rgba(63, 224, 200, .10) 72%,
      rgba(120, 240, 220, .34) 84%,
      rgba(184, 255, 242, .72) 92%,
      rgba(226, 255, 250, .97) 97%,
      rgba(240, 255, 253, 1) 99.3%,
      rgba(255, 255, 255, 0) 100%),
    /* THE INTERIOR -- deep and dense, so the rim has something to burn
       against. Deliberately darkest at the centre. */
    radial-gradient(circle,
      rgba(6, 42, 44, .58) 0%,
      rgba(9, 60, 62, .46) 34%,
      rgba(15, 110, 104, .34) 62%,
      rgba(30, 165, 150, .22) 84%,
      transparent 100%);
  animation: orbBreath 6.4s var(--ease) infinite;
}
.orb__body { animation-duration: 5.2s; }

/* breathing: the whole shell swells and settles, brightening as it fills.
   Slow enough to read as respiration rather than a throb. */
@keyframes orbBreath {
  0%, 100% { transform: scale(1);     opacity: .88; }
  46%      { transform: scale(1.055); opacity: 1; }
}

.pcv {
  position: absolute; inset: -30%; width: 160%; height: 160%;
  animation: orbBreath 6.4s var(--ease) infinite;
}
.orb .pcv { animation-duration: 5.2s; }
/* [ORION 2026-08-16] @DEE's new reference is a glass sphere, not a ball of
   strands. The painter is untouched — dropping the LAYER's opacity turns the
   swirls into interior texture and lets the shell, rings and core read as the
   subject. One value to put back if he wants the old density. */
.lorb .pcv { opacity: .46; }

/* ============================================================
   TOP RAIL
   ============================================================ */
.rail {
  position: absolute;
  top: calc(2.2cqh + env(safe-area-inset-top, 0px));
  left: 4cqw; right: 4cqw;
  display: flex; align-items: flex-start; gap: 2.4cqw;
  z-index: 6;
}

/* BACK / HOME — the Fields Enterprises, Global seal, matching the master.
   The control is a column: seal disc on top, a quiet HOME caption under it.
   Only the height grows, so the rail's horizontal budget is untouched. */
.back {
  display: inline-flex; flex-direction: column;
  align-items: center; justify-content: center; gap: .5cqh;
  flex: 0 0 auto;
  text-decoration: none; color: var(--ice);
  padding: 0;
  transition: transform .2s var(--spring);
}
.back__disc {
  /* the seal is its own badge, so the disc carries no chrome of its own --
     it exists to hold the 44px touch floor and centre the artwork */
  width: max(52px, 13cqw); height: max(52px, 13cqw);
  display: grid; place-items: center;
  border-radius: 50%;
  transition: box-shadow .2s var(--ease);
}
.back__cap {
  font: 500 max(9px, 2.05cqw)/1 var(--font-mono);
  letter-spacing: .2em; text-indent: .2em;
  color: rgba(200, 240, 252, .62);
  text-shadow: 0 1px 6px rgba(0, 0, 0, .9);
}
.back:active { transform: scale(.94); }
.back:active .back__disc { box-shadow: 0 0 3.4cqw -.5cqw rgba(63, 224, 200, .8); }
.back:active .dseal { filter: drop-shadow(0 0 8px rgba(120, 240, 220, .9)) brightness(1.12); }
/* desktop-only back affordance, hidden on the mobile seal layout */
.back__wide { display: none; }

/* the seal. A dark rim shadow separates the navy sunburst from the video
   behind it, plus a faint cyan halo so the mark belongs to the desk. */
.dseal {
  width: 100%; height: 100%;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .95))
          drop-shadow(0 0 5px rgba(78, 200, 255, .40));
}

/* live tag */
.live {
  display: inline-flex; align-items: center; justify-content: center; gap: 1.4cqw;
  flex: 1 1 auto; min-width: 0; overflow: hidden;
  height: max(46px, 11.6cqw);
  font: 500 max(10.5px, 2.32cqw)/1 var(--font-mono);
  letter-spacing: .16em;
  color: rgba(223, 247, 255, .74);
  white-space: nowrap;
}
.live b { overflow: hidden; text-overflow: ellipsis; }
.live i {
  flex: 0 0 auto;
  width: max(6px, 1.5cqw); height: max(6px, 1.5cqw); border-radius: 50%;
  background: #6bf29a;
  box-shadow: 0 0 8px rgba(107, 242, 154, .9);
  animation: blip 2.2s var(--ease) infinite;
}
@keyframes blip { 0%, 100% { opacity: 1; } 50% { opacity: .34; } }

/* utility chips — sound + language moved up here to free the bottom dash */
/* pinned right with an auto margin rather than relying on .live to fill the
   gap -- .live is hidden on the landing, which used to collapse the chips
   back against the seal and left the rail visibly lopsided */
.util { display: flex; align-items: center; gap: 1.6cqw; flex: 0 0 auto; margin-left: auto; }
.chip {
  position: relative;
  appearance: none; cursor: pointer;
  display: inline-flex; align-items: center; gap: 1.1cqw;
  height: max(46px, 11.6cqw);
  min-width: max(46px, 11.6cqw);
  padding: 0 max(10px, 2.2cqw);
  border: 1px solid rgba(120, 232, 255, .30);
  border-radius: 100px;
  background: rgba(4, 12, 19, .58);
  backdrop-filter: blur(7px);
  color: var(--ice);
  transition: transform .16s var(--spring), border-color .2s var(--ease), background .2s var(--ease);
}
.chip svg { width: max(18px, 4.4cqw); height: max(18px, 4.4cqw); }
.chip b { font: 500 max(11px, 2.4cqw)/1 var(--font-mono); letter-spacing: .1em; }
.chip:active { transform: scale(.9); border-color: rgba(200, 255, 246, .9); }
.chip[aria-pressed="false"] { color: rgba(223, 247, 255, .40); border-color: rgba(120, 232, 255, .16); }
.chip[aria-pressed="true"]  .snd__off { display: none; }
.chip[aria-pressed="false"] .snd__on  { display: none; }

/* the auto-detect indicator on the language chip */
.chip__auto {
  position: absolute; top: -.4cqw; right: -.4cqw;
  width: 2.2cqw; height: 2.2cqw; border-radius: 50%;
  background: var(--halo);
  box-shadow: 0 0 7px rgba(63, 224, 200, .95);
  border: 1px solid rgba(4, 12, 19, .9);
}
body.lang-manual .chip__auto { background: var(--ember); box-shadow: 0 0 7px rgba(255, 201, 138, .9); }

/* ============================================================
/* ============================================================
   LANDING STATE — SAGE DESK 1
   Zone map, as percentages of stage height, read off the supplied
   reference frame:
     rail        2.2 - 7.4
     plasma orb   24 - 40
     wordmark     42 - 51
     sub-line     53 - 57
     open plate   57 - 72   (holographic table, never covered)
     entry panel  72 - 82
     disclaimer     93
   ============================================================ */
.land { position: absolute; inset: 0; z-index: 5; }

/* ---------- PLASMA ORB ----------
   Deliberately a different organism from the entanglement sphere: an
   electrical discharge rather than a polished ball. Built from two
   counter-rotating conic ray fields, radially masked so the filaments
   dissolve before the rim, over a hot core. No mix-blend-mode. */
.lorb {
  position: absolute;
  top: 17.5cqh; left: 50%;
  transform: translateX(-50%);
  width: min(66cqw, 30cqh); aspect-ratio: 1;
  display: grid; place-items: center;
}
.lorb > span { position: absolute; border-radius: 50%; }
.lorb__glow {
  inset: -42%;
  /* Verdigris haze. The dark inner disc is what lets the rim read as the
     brightest thing on screen -- it holds the plate down immediately around
     the sphere without darkening the wallpaper globally, which @DEE has now
     ruled against twice. Local ground, not a global filter. */
  background:
    radial-gradient(circle, rgba(63, 224, 200, .34),
      rgba(15, 168, 144, .12) 42%, transparent 66%),
    radial-gradient(circle, rgba(2, 12, 14, .66) 32%,
      rgba(2, 12, 14, .30) 48%, transparent 64%);
  animation: lorbBreath 5.6s var(--ease) infinite;
}
.lorb__ring {
  inset: 0;
  /* the limb ring. The canvas draws its own rim glow; this keeps a crisp
     one-pixel silhouette on top of it so the sphere still has an edge. */
  border: 0;
  box-shadow: 0 0 6cqw -1.6cqw rgba(63, 224, 200, .60);
}

/* ---------- [ORION 2026-08-16] @DEE: new orb reference ----------------------
   The supplied image is a volumetric glass sphere with a WARM core and a
   concentric wireframe halo standing off the limb.

   ⚠️ DIRECTION REVERSAL, stated rather than slipped in: the previous reference
   had no bright centre, and .lorb__core below carries a comment saying exactly
   that — the core was deliberately dimmed to a verdigris ember so the rim
   stayed the subject. The new reference makes the centre the subject. I have
   flipped it, and left the old note in place so the change is legible.

   The rings live in CSS, not on the canvas, for the reason the painter already
   documents: that canvas is never cleared, only decayed, so anything redrawn
   each frame with additive compositing accumulates until it saturates. CSS
   composites once per frame. */
.lorb__halo {
  position: absolute;
  inset: -19%;
  border-radius: 50%;
  pointer-events: none;
  /* concentric hairlines standing off the limb, as in the reference */
  /* Each hairline is stroked TWICE — a deep casing under a bright core. A
     single .16 line vanished over the wallpaper: contrast against a moving
     plate is luminance-only, so a thin bright line with no ground behind it
     has nothing to hold it. The casing travels with the ring. */
  background:
    repeating-radial-gradient(circle at 50% 50%,
      rgba(3, 12, 17, 0)      0     2.86%,
      rgba(3, 12, 17, .46)    2.86% 3.16%,
      rgba(var(--orb-tint), .58) 3.16% 3.46%,
      rgba(3, 12, 17, .46)    3.46% 3.76%,
      rgba(3, 12, 17, 0)      3.76% 6.4%);
  /* fade the halo out toward the centre so it never veils the sphere */
  -webkit-mask-image: radial-gradient(circle, transparent 0 38%, #000 47%, #000 76%, transparent 88%);
          mask-image: radial-gradient(circle, transparent 0 38%, #000 47%, #000 76%, transparent 88%);
  opacity: .95;
  animation: lorbSpin 68s linear infinite;
}
/* two meridians, so the halo reads as a wireframe cage rather than flat rings */
.lorb__halo::before, .lorb__halo::after {
  content: ''; position: absolute; inset: 12%;
  border-radius: 50%;
  border: 1px solid rgba(var(--orb-tint), .34);
  box-shadow: 0 0 0 1px rgba(3, 12, 17, .40);   /* the meridian's own ground */
}
.lorb__halo::before { transform: rotateX(74deg); }
.lorb__halo::after  { transform: rotateY(74deg); }
/* upper-left sheen — the reference's giveaway that it is a glass shell */
.lorb__body::after {
  content: ''; position: absolute; inset: 0; border-radius: 50%;
  background: radial-gradient(circle at 33% 26%,
    rgba(226, 252, 255, .30) 0%, rgba(226, 252, 255, .10) 16%, transparent 42%);
  pointer-events: none;
}
.lorb__core {
  /* Was a hard white disc -- the old lamp reading. The reference has NO bright
     centre, so this is now only a faint verdigris ember: enough to suggest the
     interior is charged, far too dim to compete with the rim. Kept rather than
     deleted so the breathing animation still has something to act on. */
  /* @DEE 2026-08-16 reference: a hot amber core inside the cool shell. The
     warm/cool split is what makes it read as lit from within rather than as a
     tinted marble — the shell stays verdigris, only the centre is warm. */
  /* ⚠️ SUPERSEDES the fixed amber core from earlier today. That came from the
     glass-sphere reference; this one comes from the state sheet, where the
     centre carries the CURRENT STATE's colour. Warm is now what STATE 3/4 look
     like rather than what the orb always looks like. Pinning the landing back
     to amber is one line — say so if both were meant to stand. */
  width: 52%; aspect-ratio: 1;
  background: radial-gradient(circle,
    rgba(255, 255, 255, .92) 0%, rgba(var(--orb-tint), .80) 10%,
    rgba(var(--orb-tint), .46) 22%, rgba(var(--orb-tint), .20) 36%,
    rgba(var(--orb-tint), .07) 54%, transparent 74%);
  box-shadow: 0 0 5cqw -1cqw rgba(var(--orb-tint), .55);
  transition: background .9s var(--ease), box-shadow .9s var(--ease);
  animation: lorbCore 2.6s var(--ease) infinite;
}
@keyframes lorbBreath { 0%, 100% { transform: scale(1); opacity: .8; } 50% { transform: scale(1.1); opacity: 1; } }
@keyframes lorbSpin   { to { transform: rotate(360deg); } }
@keyframes lorbCore   { 0%, 100% { transform: scale(1); opacity: .86; } 50% { transform: scale(1.13); opacity: 1; } }

/* ---------- WORDMARK ---------- */
.land__type {
  position: absolute;
  top: 50cqh; left: 7cqw; right: 7cqw;
  text-align: center;
}
.land__h {
  margin: 0;
  font: 800 11cqw/1.02 var(--font-dis);
  letter-spacing: -.024em;
  text-shadow: 0 0 34px rgba(63, 224, 200, .30), 0 2px 20px rgba(0, 0, 0, .82);
}
.land__h span { display: block; color: #ffffff; }
.land__h em {
  display: block; font-style: normal;
  color: #7fe4f8;
  text-shadow: 0 0 30px rgba(63, 224, 200, .5), 0 2px 20px rgba(0, 0, 0, .8);
}
.land__sub {
  margin: 1.9cqh auto 0; max-width: 78cqw;
  font: 400 4.4cqw/1.42 var(--font-body);
  color: rgba(240, 252, 255, .95);
  text-wrap: balance;
  text-shadow: 0 1px 12px rgba(2, 8, 13, .95), 0 0 2.6cqw rgba(2, 8, 13, .85);
}

/* ---------- ENTRY PANEL ---------- */
.lpanel {
  position: absolute;
  top: 72cqh; left: 9cqw; right: 9cqw;
  appearance: none; cursor: pointer;
  overflow: hidden;
  display: grid; justify-items: center; gap: .5cqh;
  padding: 2.2cqh 5cqw 2cqh;
  border: 0;
  border-radius: 6px;
  /* deliberately no backdrop-filter: the reference panel is a bracketed
     frame the holographic table reads straight through, and blurring the
     plate here turned it into an opaque slab across the table. */
  background: linear-gradient(180deg, rgba(4, 14, 22, .30), rgba(4, 14, 22, .06));
  color: #eafcff;
  transition: transform .16s var(--spring), background .2s var(--ease);
}
.lpanel__brk {
  position: absolute;
  width: 6.4cqw; height: 6.4cqw;
  border: 1.5px solid rgba(170, 245, 255, .85);
}
.lpanel__brk--tl { top: 0; left: 0;  border-right: 0; border-bottom: 0; }
.lpanel__brk--tr { top: 0; right: 0; border-left: 0;  border-bottom: 0; }
.lpanel__brk--bl { bottom: 0; left: 0;  border-right: 0; border-top: 0; }
.lpanel__brk--br { bottom: 0; right: 0; border-left: 0;  border-top: 0; }
.lpanel__mic {
  width: 5.4cqw; height: 5.4cqw;
  color: rgba(200, 248, 255, .95);
  filter: drop-shadow(0 0 4px rgba(63, 224, 200, .8));
}
.lpanel__h {
  font: 800 6.6cqw/1.04 var(--font-dis);
  letter-spacing: .012em;
  text-align: center;
  text-shadow: 0 0 26px rgba(63, 224, 200, .34), 0 2px 14px rgba(0, 0, 0, .8);
}
.lpanel__sub {
  font: 500 max(10px, 2.36cqw)/1 var(--font-mono);
  font-style: normal;
  letter-spacing: .24em; text-indent: .24em;
  color: rgba(200, 244, 255, .90);
}
.lpanel__ripple {
  position: absolute; inset: 0;
  background: radial-gradient(58% 150% at 50% 50%, rgba(120, 236, 255, .40), transparent 72%);
  opacity: 0; transform: scale(.4);
}
.lpanel:active { transform: scale(.972); background: linear-gradient(180deg, rgba(10, 30, 44, .58), rgba(10, 30, 44, .26)); }
.lpanel.is-fire .lpanel__ripple { animation: ripple .62s var(--ease) 1; }
.lpanel.is-fire .lpanel__brk { border-color: #ffffff; }

.land__sim {
  position: absolute;
  bottom: calc(3.2cqh + env(safe-area-inset-bottom, 0px));
  left: 7cqw; right: 7cqw;
  margin: 0; text-align: center;
  font: 500 italic max(10px, 2.5cqw)/1.3 var(--font-body);
  color: rgba(226, 248, 255, .82);
  text-shadow: 0 1px 10px rgba(2, 8, 13, .95);
}

/* ============================================================
   ENTANGLEMENT INTERFACE
   Zone map, as percentages of stage height, taken from the doodle:
     rail          2 - 9
     header        11 - 21    (green)
     orb           25 - 44    (blue)
     message box   52 - 68    (red)
     open plate    68 - 82    left clear, never covered
     tap to speak  82 - 88
     dash          89 - 98
   ============================================================ */
.ent { position: absolute; inset: 0; z-index: 5; }

/* ---------- GREEN: header, dropped away from the top edge and enlarged ---------- */
.ehead {
  position: absolute;
  top: 11cqh; left: 0; right: 0;
  text-align: center;
}
.ehead__word {
  margin: 0;
  font: 800 15.6cqw/.92 var(--font-dis);
  letter-spacing: .06em; text-indent: .06em;
  color: #f2feff;
  text-shadow: 0 0 6cqw rgba(63, 224, 200, .55), 0 0 1.6cqw rgba(63, 224, 200, .9),
               0 3px 20px rgba(0, 0, 0, .85);
}
/* [ORION 2026-08-13] @DEE: reverted the Michroma interim — SAGE header is back
   on the original Cabinet Grotesk (var(--font-dis) in the base rule above). */
/* [ORION 2026-08-13] @DEE: the entanglement indicator moved OFF the SAGE
   header — the live status now lives under the orb (STANDING BY ↔ ENTANGLEMENT
   ACTIVE). So the header is just the SAGE wordmark + its rule. */
.ehead__sub { display: none; }
.ehead__rule {
  display: block;
  width: 26cqw; height: 1px;
  margin: 1.7cqh auto 0;
  background: linear-gradient(90deg, transparent, rgba(63, 224, 200, .85), transparent);
}

/* ---------- BLUE: the orb, lifted into the upper field ---------- */
.orbz {
  position: absolute;
  top: 22cqh; left: 0; right: 0;
  display: grid; justify-items: center; gap: 1.2cqh;
}
.orb {
  position: relative;
  width: min(74cqw, 33cqh); height: min(74cqw, 33cqh);
  display: grid; place-items: center;
  color: rgba(184, 255, 242, .9);
}
.orb__glow {
  position: absolute; inset: -34%;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(var(--orb-tint), .30), rgba(var(--orb-tint), .07) 46%, transparent 70%);
  animation: orbBreath 5.2s var(--ease) infinite;
  transition: background .9s var(--ease);
}
.orb__rim {
  position: absolute; inset: 0;
  border-radius: 50%;
  border: 0;
  box-shadow: 0 0 5cqw -1.6cqw rgba(var(--orb-tint), .5);
  transition: box-shadow .9s var(--ease);
}
/* Same inversion as the landing orb: the second screen must not still be a
   lamp when the first screen is a shell. No white disc; a faint ember only. */
.orb__core {
  position: absolute;
  width: 52%; aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(circle,
    rgba(184, 255, 242, .30) 0%, rgba(120, 240, 220, .18) 14%,
    rgba(63, 224, 200, .10) 34%, rgba(15, 168, 144, .05) 58%, transparent 76%);
  animation: orbCore 2.8s var(--ease) infinite;
}
.orb__ring {
  position: absolute;
  border-radius: 50%;
  border: 1px solid rgba(184, 255, 242, .40);
}
.orb__ring--a { inset: -2%;  border-style: solid; animation: orbSpin 26s linear infinite reverse; border-top-color: rgba(230, 255, 252,.95); }
.orb__ring--b { inset: -14%; border-style: dashed; opacity: .5; animation: orbSpin 44s linear infinite; }
.orb__ring--c { inset: -24%; border-style: solid; opacity: .22; }
.orb__wave { position: absolute; inset: -24%; width: auto; height: auto; }
.orb__trace {
  stroke-dasharray: 6 94;
  stroke-linecap: round;
  animation: orbTrace 6s linear infinite;
  opacity: .85;
}
@keyframes orbBreath { 0%, 100% { transform: scale(1); opacity: .82; } 50% { transform: scale(1.11); opacity: 1; } }
@keyframes orbSpin   { to { transform: rotate(360deg); } }
@keyframes orbCore   { 0%, 100% { transform: scale(1); opacity: .84; } 50% { transform: scale(1.14); opacity: 1; } }
@keyframes orbTrace  { to { stroke-dashoffset: -100; } }

/* orb modes */
.orb[data-mode="listening"] { color: #9df3b9; }
.orb[data-mode="listening"] .orb__glow { animation-duration: 1.5s; background: radial-gradient(circle, rgba(107, 242, 154, .34), rgba(107, 242, 154, .08) 46%, transparent 70%); }
.orb[data-mode="listening"] .orb__ring--a { animation-duration: 7s; border-top-color: #9df3b9; }
.orb[data-mode="listening"] .orb__rim { border-color: rgba(157, 243, 185, .62); }
.orb[data-mode="listening"] .orb__core { animation-duration: .9s; }
.orb[data-mode="thinking"] .orb__ring--b { animation-duration: 8s; opacity: .85; }
.orb[data-mode="speaking"] .orb__core { animation-duration: .48s; }
.orb[data-mode="speaking"] .orb__glow { animation-duration: 1.1s; }

.orbz__mode {
  margin: 0;
  font: 500 max(11px, 2.5cqw)/1 var(--font-mono);
  letter-spacing: .34em; text-indent: .34em;
  color: rgba(178, 245, 255, .95);
  text-shadow: 0 0 1.6cqw rgba(4, 16, 24, .95), 0 1px 6px rgba(0, 0, 0, .9);
}
/* [ORION 2026-08-13] a green indicator dot leads the caption when SAGE is
   engaged — reads as "● ENTANGLEMENT ACTIVE"; absent while STANDING BY. */
.orb:not([data-mode="idle"]) ~ .orbz__mode { color: #cdfade; }
.orb:not([data-mode="idle"]) ~ .orbz__mode::before {
  content: ''; display: inline-block; vertical-align: middle;
  width: .72em; height: .72em; margin-right: .6em; margin-top: -.14em;
  border-radius: 50%; background: #6bf29a;
  box-shadow: 0 0 8px rgba(107, 242, 154, .95);
  animation: orbCore 2.2s var(--ease) infinite;
}

/* ---------- RED: the message box, dropped below the orb ---------- */
.msg {
  position: absolute;
  top: 60cqh; left: 7cqw; right: 7cqw;
  /* CARD 5 — the block had no height and no scroll container, so a long reply
     grew DOWNWARD through the consent notice, the tap target and the dash.
     Absolutely-positioned siblings cannot be pushed, so it drew over them, and
     with no page scroll nothing recovered it.

     Bounded in cqh rather than px on purpose: .stage is `container-type: size`,
     so cqh is proportional to the stage at every viewport — construction-bound,
     not a magic number that re-breaks at the next width. 16cqh keeps the block
     clear of the notice even at 320, where the notice wraps to two lines and
     therefore starts highest. Flex is scoped to this block only; the stage
     composition is untouched. */
  max-height: 16cqh;
  display: flex;
  flex-direction: column;
  padding: 2.4cqh 5cqw 1.8cqh;
  border-radius: 4px;
  background: linear-gradient(180deg, rgba(3, 11, 18, .82), rgba(3, 11, 18, .66));
  border: 1px solid rgba(120, 232, 255, .22);
  box-shadow: 0 0 5cqw -2cqw rgba(63, 224, 200, .40), 0 10px 34px rgba(0, 0, 0, .55);
}
.msg__brk {
  position: absolute; width: 3.4cqw; height: 3.4cqw;
  border: 1.6px solid rgba(160, 244, 255, .95);
}
.msg__brk--tl { top: -1px; left: -1px;  border-right: 0; border-bottom: 0; }
.msg__brk--tr { top: -1px; right: -1px; border-left: 0;  border-bottom: 0; }
.msg__brk--bl { bottom: -1px; left: -1px;  border-right: 0; border-top: 0; }
.msg__brk--br { bottom: -1px; right: -1px; border-left: 0;  border-top: 0; }
.msg__who {
  margin: 0 0 1cqh;
  font: 500 max(10.5px, 2.34cqw)/1 var(--font-mono);
  letter-spacing: .30em; text-indent: .30em;
  color: rgba(120, 232, 255, .9);
}
.msg__body {
  margin: 0;
  font: 400 3.72cqw/1.44 var(--font-body);
  color: #eafcff;
  min-height: 3em;
  text-wrap: pretty;
  /* the only part that scrolls — .msg__who and .msg__meta stay pinned, and the
     corner brackets are absolute children of .msg so they never move. */
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.msg__meta {
  margin: 1.4cqh 0 0;
  font: 400 max(10px, 2.2cqw)/1 var(--font-mono);
  letter-spacing: .14em;
  color: rgba(223, 247, 255, .48);
}

/* ---------- CONSENT NOTICE ----------
   Sits directly beneath the tap target, inside the same insets, so the notice
   and the act it governs are one visual unit. Higher contrast than .msg__meta
   on purpose: this is conspicuous notice, not chrome. Positioned off the tap
   button's own geometry (82cqh + 6.4cqh height) so the two move together. */
.consent {
  position: absolute;
  /* ABOVE the tap target, not below. Measured 2026-08-02: the gap between the
     button (ends 88.4cqh) and the control dash is only ~15px at 390x844 and
     ~15px at 1440x900, while this notice needs 24-28px. Placing it beneath
     overlapped the dash on BOTH viewports. Shrinking it to fit would trade
     away the conspicuousness that is the whole point, so it sits immediately
     above the button instead — same insets, still one visual unit with the
     act it governs. */
  bottom: calc(100cqh - 82cqh + 1.0cqh);
  left: 7cqw; right: 7cqw;
  margin: 0;
  text-align: center;
  font: 400 max(9px, 2.05cqw)/1.35 var(--font-mono);
  letter-spacing: .05em;
  color: rgba(223, 247, 255, .76);
  text-wrap: balance;
  pointer-events: none;
}

/* ---------- TAP TO SPEAK ---------- */
.tap {
  position: absolute;
  top: 82cqh; left: 7cqw; right: 7cqw;
  height: 6.4cqh;
  appearance: none; cursor: pointer;
  display: grid; place-items: center;
  overflow: hidden;
  border-radius: 100px;
  border: 1.5px solid rgba(150, 244, 255, .78);
  background: rgba(6, 22, 32, .66);
  backdrop-filter: blur(8px);
  color: #eafcff;
  box-shadow: 0 0 4cqw -1.4cqw rgba(63, 224, 200, .68);
  transition: transform .16s var(--spring), border-color .2s var(--ease);
}
.tap__cap {
  position: relative; z-index: 2;
  font: 700 3.4cqw/1 var(--font-body);
  letter-spacing: .28em; text-indent: .28em;
}
.tap__ripple {
  position: absolute; inset: 0;
  background: radial-gradient(60% 140% at 50% 50%, rgba(63, 224, 200, .34), transparent 70%);
  opacity: 0; transform: scale(.4);
}
.tap:active { transform: scale(.975); border-color: #eafcff; }
.tap.is-hot { border-color: #9df3b9; box-shadow: 0 0 5cqw -1cqw rgba(107, 242, 154, .8); }
.tap.is-hot .tap__ripple {
  background: radial-gradient(60% 140% at 50% 50%, rgba(107, 242, 154, .34), transparent 70%);
  animation: ripple 1.5s var(--ease) infinite;
}
@keyframes ripple {
  0%   { opacity: 0;   transform: scale(.5); }
  45%  { opacity: .95; transform: scale(1); }
  100% { opacity: 0;   transform: scale(1.5); }
}

/* ---------- CONTROL DASH — three wide targets ---------- */
.dash {
  position: absolute;
  bottom: calc(2.2cqh + env(safe-area-inset-bottom, 0px));
  left: 7cqw; right: 7cqw;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 2.4cqw;
}
.ctl {
  appearance: none; cursor: pointer;
  min-height: 7.6cqh;
  display: grid; place-items: center; gap: .5cqh;
  padding: 1cqh .6cqw;
  border-radius: 14px;
  border: 1px solid rgba(120, 232, 255, .26);
  background: rgba(4, 12, 19, .62);
  backdrop-filter: blur(8px);
  color: var(--ice);
  transition: transform .16s var(--spring), border-color .18s var(--ease), background .18s var(--ease);
}
.ctl svg { width: 6.4cqw; height: 6.4cqw; }
.ctl b {
  font: 500 max(11px, 2.44cqw)/1 var(--font-mono);
  letter-spacing: .13em;
  white-space: nowrap;
}
.ctl:active { transform: scale(.94); border-color: rgba(200, 255, 246, .95); background: rgba(10, 30, 42, .78); }
.ctl--mic[aria-pressed="true"]  { border-color: rgba(107, 242, 154, .5); color: #cdfade; }
.ctl--mic[aria-pressed="true"]  .mic__slash { display: none; }
.ctl--mic[aria-pressed="false"] { color: rgba(223, 247, 255, .42); }
.ctl--end { border-color: rgba(255, 125, 107, .40); color: #ffd2c9; }
.ctl--end:active { border-color: var(--alarm); background: rgba(48, 12, 8, .8); }

/* ============================================================
   LANGUAGE SHEET
   ============================================================ */
.sheet {
  position: absolute; inset: 0; z-index: 20;
  display: grid; align-items: end;
  background: rgba(2, 6, 11, .62);
  backdrop-filter: blur(3px);
  animation: fade .22s var(--ease) both;
}
.sheet[hidden] { display: none; }
@keyframes fade { from { opacity: 0; } }
.sheet__panel {
  background: rgba(4, 12, 19, .95);
  border-top: 1px solid rgba(120, 232, 255, .36);
  border-radius: 20px 20px 0 0;
  padding: 3cqh 6cqw calc(3.4cqh + env(safe-area-inset-bottom, 0px));
  animation: rise .3s var(--spring) both;
  max-height: 82cqh; overflow-y: auto;
}
@keyframes rise { from { transform: translateY(16%); opacity: .4; } }
.sheet__top { display: flex; align-items: center; gap: 3cqw; }
.sheet__top h2 {
  margin: 0;
  font: 700 4.2cqw/1 var(--font-dis);
  letter-spacing: .2em;
}
.sheet__x {
  margin-inline-start: auto;
  appearance: none; cursor: pointer;
  width: 9cqw; height: 9cqw;
  display: grid; place-items: center;
  border-radius: 50%;
  border: 1px solid rgba(120, 232, 255, .3);
  background: rgba(8, 22, 32, .8);
  color: var(--ice);
}
.sheet__x svg { width: 4.4cqw; height: 4.4cqw; }
.sheet__note {
  margin: 2cqh 0 0;
  font: 400 3cqw/1.5 var(--font-body);
  color: rgba(223, 247, 255, .72);
}
.sheet__auto { margin-top: 2.4cqh; }
.auto {
  width: 100%;
  appearance: none; cursor: pointer; text-align: start;
  display: grid; gap: .6cqh;
  padding: 1.9cqh 4cqw;
  border-radius: 12px;
  border: 1.5px solid rgba(120, 232, 255, .26);
  background: rgba(8, 22, 32, .7);
  color: var(--ice);
}
.auto b { font: 500 3cqw/1 var(--font-mono); letter-spacing: .14em; }
.auto span { font: 400 max(12px, 2.7cqw)/1.3 var(--font-body); color: rgba(223, 247, 255, .6); }
.auto[aria-pressed="true"] {
  border-color: rgba(150, 244, 255, .95);
  box-shadow: 0 0 3.4cqw -1cqw rgba(63, 224, 200, .6);
}
.langs {
  list-style: none; margin: 2.4cqh 0 0; padding: 0;
  display: grid; grid-template-columns: 1fr 1fr; gap: 1.6cqw;
}
.langs button {
  width: 100%;
  appearance: none; cursor: pointer; text-align: start;
  display: grid; gap: .3cqh;
  padding: 1.6cqh 3.4cqw;
  border-radius: 10px;
  border: 1px solid rgba(120, 232, 255, .2);
  background: rgba(8, 22, 32, .6);
  color: var(--ice);
}
.langs button b { font: 500 3cqw/1.2 var(--font-body); }
.langs button i {
  font: 400 max(10px, 2.24cqw)/1 var(--font-mono);
  letter-spacing: .12em; font-style: normal;
  color: rgba(223, 247, 255, .5);
}
.langs button[aria-current="true"] {
  border-color: rgba(150, 244, 255, .9);
  background: rgba(12, 40, 54, .85);
}
.sheet__src {
  margin: 2.4cqh 0 0;
  font: 400 max(10px, 2.2cqw)/1.5 var(--font-mono);
  letter-spacing: .1em;
  color: rgba(223, 247, 255, .42);
}

/* ============================================================
   TOAST
   ============================================================ */
.toast {
  position: absolute;
  left: 50%; bottom: 24cqh;
  transform: translate(-50%, 10px);
  z-index: 30;
  padding: 1.4cqh 4.6cqw;
  border-radius: 100px;
  border: 1px solid rgba(120, 232, 255, .4);
  background: rgba(4, 12, 19, .92);
  font: 500 2.6cqw/1 var(--font-mono);
  letter-spacing: .14em;
  white-space: nowrap;
  opacity: 0; pointer-events: none;
  transition: opacity .24s var(--ease), transform .24s var(--spring);
}
.toast.on { opacity: 1; transform: translate(-50%, 0); }

/* ============================================================
   STATE SWITCHING
   ============================================================ */
body.state-land  .ent { display: none; }
body.state-land  .live { display: none; }
/* [ORION 2026-08-13] @DEE: one entanglement indicator, and it lives UNDER THE
   ORB now — so the top rail's ENTANGLEMENT ACTIVE tag retires in the conversation
   state too (it was duplicating the orb caption). */
body.state-entangle .live { display: none; }
body.state-entangle .land { display: none; }

/* copy-slot inspector: highlights every string awaiting Dee's verbiage */
body.show-slots [data-copy] {
  outline: 1px dashed rgba(255, 201, 138, .9);
  outline-offset: 2px;
  background: rgba(255, 201, 138, .10);
}

/* ============================================================
   FOCUS + MOTION
   ============================================================ */
:where(a, button):focus-visible {
  outline: 2px solid #9ff0ff;
  outline-offset: 3px;
  border-radius: 8px;
}

@media (prefers-reduced-motion: reduce) {
  .orb__glow, .orb__core, .orb__ring, .orb__trace, .orb__body,
  .live i, .tap.is-hot .tap__ripple,
  .lorb__glow, .lorb__core, .lorb__body, .lorb__halo, .pcv,
  .lpanel.is-fire .lpanel__ripple {
    animation: none !important;
  }
  .sheet, .sheet__panel { animation: none !important; }
}

/* ---------- TEXT COMPOSER ----------------------------------------------
   [ORION 2026-08-04] Bolu: "we should have an actual chat input users can
   use to send message to sage if they don't want to talk." It occupies the
   TAP bar's exact box so switching modes is a swap, not a reflow -- the
   consent notice and dash below must not move (card 5 was about exactly
   that kind of drift). */
.composer {
  position: absolute;
  top: 82cqh; left: 7cqw; right: 7cqw;
  height: 6.4cqh;
  display: flex; align-items: center; gap: 2cqw;
  padding: 0 1.6cqw 0 4.5cqw;
  border-radius: 100px;
  border: 1.5px solid rgba(150, 244, 255, .78);
  background: rgba(6, 22, 32, .66);
  backdrop-filter: blur(8px);
  box-shadow: 0 0 4cqw -1.4cqw rgba(63, 224, 200, .68);
}
.composer[hidden] { display: none; }
.composer__in {
  flex: 1 1 auto; min-width: 0;
  appearance: none; background: none; border: 0; outline: none;
  color: #eafcff;
  font: 500 3.4cqw/1 var(--font-body);
  letter-spacing: .02em;
}
.composer__in::placeholder { color: rgba(234, 252, 255, .42); letter-spacing: .18em; }
.composer__go {
  flex: 0 0 auto;
  width: 4.6cqh; height: 4.6cqh;
  display: grid; place-items: center;
  appearance: none; cursor: pointer;
  border-radius: 100px;
  border: 1px solid rgba(150, 244, 255, .5);
  background: rgba(63, 224, 200, .16);
  color: #eafcff;
  transition: transform .16s var(--spring), background .2s var(--ease);
}
.composer__go:active { transform: scale(.94); background: rgba(63, 224, 200, .3); }
.composer__go:disabled { opacity: .4; cursor: default; }

/* system status ticker — desktop only (styled + shown in the min-width block) */
.deskstatus { display: none; }

/* ============================================================
   [ORION 2026-08-13] DESKTOP STAGE — Story-3 desk on the wide canvas
   @DEE: restore the desktop composition the mobile-first rebuild flattened.
   HALLWAY + TAKE TOUR are OUT by his call; a clean back button takes the
   top-left. This is the LANDING-STATE pass (backdrop + welcome + engagement
   bucket); the in-conversation (entanglement) desktop layout is a follow-up.
   Mobile DOM/JS untouched — additive desktop layer only. The stage un-boxes
   to full width so cqw/cqh rebase to the viewport; the panel internals are
   pinned in px so they don't balloon with the wider cqw. Overrides the
   min-aspect-ratio letterbox by source order + min-width specificity.
   ============================================================ */
@media (min-width: 768px) {
  /* un-letterbox: fill the frame, drop the 9:16 cage so the composition is wide */
  .stage {
    width: 100vw;
    height: var(--vvh, 100dvh);
    max-width: none;
    aspect-ratio: auto;
  }

  /* PLASMA ORB — high and centred, a touch smaller than the balloon default */
  .lorb { top: 11cqh; width: min(220px, 26cqh); }

  /* WELCOME wordmark — centred hero, capped so cqw doesn't over-scale it */
  .land__type { top: 39cqh; left: 0; right: 0; padding: 0 6cqw; }
  .land__h  { font-size: clamp(54px, 6.6cqw, 108px); line-height: 1.0; }
  .land__sub { font-size: clamp(17px, 1.5cqw, 24px); margin-top: 18px; max-width: 42ch; }

  /* ENGAGEMENT BUCKET — a contained centred panel, not a full-width bar.
     [ORION 2026-08-13] @DEE: dropped it lower (17cqh → 13cqh) so there's real
     margin between it and "Let's have a real conversation" — desktop was tight
     at ~23px; this matches the ~55px the mobile layout already has, so the
     spacing reads consistent on both. */
  .lpanel {
    /* [ORION 2026-08-13] @DEE: the CONVERSATIONAL AI button glitched right on
       click. Cause: it was centred with transform:translateX(-50%), and
       .lpanel:active{transform:scale(.972)} REPLACES that transform — so a
       press dropped the centring and snapped the button ~half its width to the
       right, taking it out from under the cursor (hence the missed/"unresponsive"
       clicks). Centre with auto margins instead so `transform` is free for the
       press scale and the button never moves. */
    top: auto; bottom: 13cqh; left: 0; right: 0; margin-inline: auto;
    transform: none;
    width: min(460px, 62cqw);
    padding: 22px 40px 20px; gap: 7px;
    border-radius: 8px;
  }
  .lpanel__brk { width: 20px; height: 20px; }
  .lpanel__mic { width: 26px; height: 26px; }
  .lpanel__h   { font-size: 30px; letter-spacing: .012em; }
  .lpanel__sub { font-size: 12px; }

  /* TOP RAIL / BACK — the seal steps aside for a clean back control that
     REUSES #btnBack, so the existing return-to-lobby wiring still fires. */
  .rail { top: 24px; left: 30px; right: 30px; gap: 16px; align-items: center; }
  /* [ORION 2026-08-13] @DEE: the Fields Enterprises SEAL is the back button —
     the logo itself is the control (still #btnBack, wiring unchanged). The
     "← BACK" pill is retired on desktop; the seal + a quiet HOME cap return. */
  .back {
    flex-direction: column; gap: 4px; padding: 0;
    background: none; border: 0;
    -webkit-backdrop-filter: none; backdrop-filter: none;
    transition: transform .2s var(--spring);
  }
  .back:hover { transform: translateY(-1px); }
  .back__wide { display: none; }
  .back__disc {
    display: grid; place-items: center; width: 54px; height: 54px;
    filter: drop-shadow(0 2px 10px rgba(0,0,0,.7));
    transition: filter .2s var(--ease);
  }
  .back:hover .back__disc { filter: drop-shadow(0 0 12px rgba(120,240,220,.65)); }
  .back__cap {
    display: block; font: 500 10px/1 var(--font-mono);
    letter-spacing: .24em; text-indent: .24em; color: rgba(200,240,252,.62);
  }

  /* util chips + live tag are cqw-sized (they'd balloon to ~167px here) — pin px */
  .util { gap: 10px; }
  .chip { height: 42px; min-width: 42px; padding: 0 14px; gap: 7px; }
  .chip svg { width: 18px; height: 18px; }
  .chip b { font-size: 12px; }
  .live { font-size: 11px; gap: 8px; height: 42px; }
  .live i { width: 8px; height: 8px; }
  .chip__auto { width: 9px; height: 9px; top: -3px; right: -3px; }

  /* ── ENTANGLEMENT (in-conversation) desktop layout ──────────────
     [ORION 2026-08-13] @DEE: the second screen was the mobile UI ballooned
     by cqw — giant SAGE, overlapping message box, TAP hidden behind an
     oversized dash. Same treatment as the landing: pin fonts/widths in px,
     centre the wide elements, keep the vertical rhythm in cqh. */
  .ehead { top: 6cqh; }
  .ehead__word { font-size: clamp(44px, 6cqw, 88px); letter-spacing: .05em; text-indent: .05em; }
  .ehead__sub  { font-size: 13px; letter-spacing: .4em; text-indent: .4em; margin-top: 10px; }
  .ehead__rule { width: 200px; margin-top: 14px; }

  .orbz { top: 23cqh; gap: 12px; }
  .orb  { width: 200px; height: 200px; }
  .orbz__mode { font-size: 13px; }

  /* message box — a centred card, not a full-width slab */
  .msg {
    top: 54cqh; bottom: auto; left: 50%; right: auto;
    transform: translateX(-50%); width: min(600px, 68cqw);
    max-height: 17cqh; padding: 18px 28px 15px; border-radius: 6px;
  }
  .msg__brk { width: 16px; height: 16px; }
  .msg__who  { font-size: 12px; margin-bottom: 8px; }
  .msg__body { font-size: 16px; }
  .msg__meta { font-size: 11px; margin-top: 10px; }

  /* consent sits just above the tap pill (raised to clear the HUD dash bar) */
  .consent {
    bottom: calc(22cqh + 62px); left: 50%; right: auto;
    transform: translateX(-50%); width: min(560px, 64cqw); font-size: 12px;
  }

  /* TAP TO SPEAK — a contained centred pill */
  .tap {
    /* [ORION 2026-08-13] Same fix as .lpanel: centre with auto margins, not a
       transform, so .tap:active{transform:scale(.975)} (with its spring
       transition) animates a clean press instead of springing the button to
       the right off the cursor. */
    top: auto; bottom: 22cqh; left: 0; right: 0; margin-inline: auto;
    transform: none; width: min(460px, 54cqw); height: 52px;
  }
  .tap__cap { font-size: 14px; letter-spacing: .28em; }

  /* typed composer occupies the tap's footprint */
  .composer {
    top: auto; bottom: 22cqh; left: 50%; right: auto;
    transform: translateX(-50%); width: min(460px, 54cqw); height: 52px;
  }
  /* [ORION 2026-08-17] @DEE, from a tester session: "the text appeared
     unformatted and outside the text box."

     MEASURED, and it is not subtle: .composer__in sets `font: 500 3.4cqw/1`,
     which is correct on a phone (13.3px at 390) and catastrophic on a desktop
     stage — 48.96px of type inside a 52px pill, with the line running 1246px
     through a 300px input. The glyphs were literally taller than their own box.

     The pill above it (.msg__body) already pins a px size for desktop; this
     control was simply missed when the desktop layout was added, so it kept
     scaling with the STAGE width. Same family as the cqw popup that ballooned
     on the lobby's full-bleed stage. */
  .composer__in { font-size: 16px; letter-spacing: .01em; }
  .composer__in::placeholder { letter-spacing: .12em; }

  /* ── CONTROL DASH — adopt @DEE's interactive-dashboard branding (photo 2):
     a framed holographic bar of ROUND icon buttons, the MIC the glowing hero
     in the centre. Labels sit under each disc. (SOUND + ENGLISH live in the
     top rail as chips, so the bar carries the desk's three acts: TYPE / MIC /
     END; the side telemetry widgets from the ref are left for a later pass.) */
  .dash {
    bottom: 4cqh; left: 50%; right: auto; transform: translateX(-50%);
    width: auto; grid-template-columns: none;
    display: flex; align-items: center; justify-content: center; gap: 36px;
    padding: 14px 42px;
    border-radius: 22px;
    background: linear-gradient(180deg, rgba(4,14,22,.44), rgba(4,14,22,.24));
    border: 1px solid rgba(120,210,235,.24);
    box-shadow: 0 0 40px -18px rgba(63,224,200,.4), inset 0 0 30px -18px rgba(120,210,235,.3);
    -webkit-backdrop-filter: blur(9px); backdrop-filter: blur(9px);
  }
  .ctl {
    min-height: 0; padding: 0; gap: 9px; border: 0; border-radius: 0;
    background: none; -webkit-backdrop-filter: none; backdrop-filter: none;
  }
  /* the round holographic disc holds each icon */
  .ctl svg {
    width: 22px; height: 22px; padding: 15px; box-sizing: content-box;
    border-radius: 50%;
    border: 1px solid rgba(150,232,255,.5);
    background: rgba(6,20,30,.5);
    transition: border-color .18s var(--ease), background .18s var(--ease), box-shadow .18s var(--ease);
  }
  .ctl:hover svg { border-color: rgba(200,245,255,.95); background: rgba(12,34,48,.72); }
  .ctl b { font-size: 11px; letter-spacing: .14em; }
  /* MIC is the hero of the bar — a larger, brighter central disc */
  .ctl--mic svg {
    width: 30px; height: 30px; padding: 21px;
    border-color: rgba(107,242,154,.65);
    box-shadow: 0 0 26px -4px rgba(107,242,154,.5), inset 0 0 20px -7px rgba(107,242,154,.5);
  }
  .ctl--mic:hover svg { border-color: rgba(150,255,190,.95); }
  .ctl--end svg { border-color: rgba(255,140,120,.5); }
  .ctl--end:hover svg { border-color: rgba(255,175,158,.95); background: rgba(40,14,10,.6); }

  /* ── SYSTEM STATUS TICKER at the foot (matches the FE lobby's corestatus) ──
     [ORION 2026-08-13] @DEE: LYNX / QUANTA / AEGIS version + online status,
     scrolling, at the bottom of the desk. Two groups translate -50% for a
     seamless loop; edges fade via a mask. Sits at the very bottom edge, below
     the engagement bucket (landing) and the control dash (conversation). */
  .deskstatus {
    display: flex; justify-content: space-between; align-items: center;
    position: absolute; bottom: 9px; left: 0; right: 0; z-index: 6;
    padding: 0 6cqw; white-space: nowrap; pointer-events: none;
    font: 500 9px/1 var(--font-mono); letter-spacing: .1em; text-transform: uppercase;
    color: rgba(190,225,245,.82);
  }
  .deskstatus b { color: #9fe6ff; font-weight: 500; margin-left: 5px; }
  .ds-sys { display: inline-flex; align-items: center; gap: 7px; }
  .ds-on { width: 5px; height: 5px; border-radius: 50%; flex: none;
    background: #6effb0; box-shadow: 0 0 6px rgba(110,255,176,.95);
    animation: dsPulse 2.4s ease-in-out infinite; }
  @keyframes dsPulse { 0%,100% { opacity: 1; } 50% { opacity: .45; } }
}

/* ══════════════════════════════════════════════════════════════════════════
   [ORION 2026-08-17] @DEE: "sage your agent is wonderful on desktop, no
   configuration, no opening, no feel on mobile. can we mirror the desktop
   experience … to the mobile CE experience".

   MEASURED the two side by side before changing anything:
     · desktop carries the system strip along the foot (LYNX v3.2 · QUANTA v1.0 ·
       AEGIS v2.1). On mobile `.deskstatus` is display:none and ALL of its styling
       — including its keyframes — lives inside the min-width:768 block, so the
       phone inherits none of it. That strip is the "configuration" cue.
     · desktop's orb is 24% of the frame height; mobile's is 30%, which pushes the
       stack down and leaves a 135px band of dead screen under the panel. Desktop's
       equivalent band is 117px and the strip fills it.

   So this is not new design — it is the desktop composition, at phone scale.
   ══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 767px) {
  /* the orb back to desktop's proportion, so the stack sits where it does there */
  .lorb { width: min(60cqw, 25cqh); }

  /* ══════════════════════════════════════════════════════════════════════════
     [ORION 2026-08-18] @DEE: "the conversation controls are off the bottom of the
     page ... place the TAP TO SPEAK button on the orb and have it fade away after
     clicked. This will allow space to have the voice control menu moved up and in
     screen."

     MEASURED FIRST, in the entangle state at 390x844 (the state his screenshot is
     in — the landing state does not show these at all):
        orb        186..464
        transcript 506..626
        consent    659..684
        tap        692..746
        dash       761..825   <- 19px off the floor, i.e. under Safari's bar

     The tap was a full-width pill occupying its own row between the consent and
     the dash. Moving it onto the orb frees that row, and the dash and consent
     both rise into it. NOTE the page cannot scroll to reveal them (body is
     overflow:hidden) — what moves under a drag is the wallpaper, which is exactly
     what @DEE described.
     ══════════════════════════════════════════════════════════════════════════ */

  /* TAP TO SPEAK, seated on the orb */
  .tap {
    /* 34.4cqh puts the pill's centre on the orb's centre: 1cqh measured at
       8.64px here, orb centre y=325, pill 46px tall. Kept as auto margins rather
       than a translate, because .tap:active uses transform for its press spring
       and a translate would fight it (same reason the desktop block gives). */
    top: 34.4cqh; left: 0; right: 0; margin-inline: auto;
    width: min(58cqw, 216px); height: 46px; z-index: 7;
    background: rgba(6, 22, 32, .58);
    box-shadow: 0 6px 26px -8px rgba(0,0,0,.6), 0 0 4cqw -1.4cqw rgba(63,224,200,.68);
    transition: transform .16s var(--spring), border-color .2s var(--ease),
                opacity .45s var(--ease);
  }
  .tap__cap { font: 700 12px/1 var(--font-body); letter-spacing: .22em; text-indent: .22em; }

  /* "fade away after clicked" — set once the tap actually opens a turn, so it
     does not vanish on a press that was rejected for having no mic. The dash's
     MIC becomes the control from then on, which is why the dash had to come on
     screen in the same change. */
  .tap.is-spent { opacity: 0; transform: scale(.92); pointer-events: none; }

  /* [ORION 2026-08-18 · rev] @DEE: "move both the transcript and conversation
     menu down — the goal is to keep the transcript off the orb. Also the ticker
     on the bottom is not visible."

     Measured before this pass at 390x844: orb core ends 464 and the transcript
     started 475 — an 11px gap, and well inside the orb's ring (to 519) and glow
     (to 584), which is why it read as sitting ON the orb. The ticker was at
     bottom+10, i.e. under Safari's bar.

     The whole stack is re-cut from the floor so every gap is arithmetic:
        ticker  70   dash 92   consent 164   transcript 198  (px above the floor)
     Smaller offset = lower on screen, so the transcript drops 47px and the dash
     15px, both DOWN as asked, while the ticker comes up out of the bar. */
  .dash { bottom: calc(2.2cqh + env(safe-area-inset-bottom, 0px) + 73px); }
  .consent { bottom: calc(2.2cqh + env(safe-area-inset-bottom, 0px) + 145px); }

  /* the ticker's seat is set on the EXISTING mobile .deskstatus rule below, not
     here: that rule re-declares `bottom` and sits later in this same block, so a
     second rule here loses to it at equal specificity. Measured 10px off the
     floor while this said 70 — the tell that I was writing into a rule that was
     overwritten twelve lines later. */

  /* The transcript is anchored by `top` with a height bound whose comment says it
     "keeps the block clear of the notice" — true where the notice USED to be, and
     false the moment I raised it. Measured after the raise: at 375x667 the notice
     landed INSIDE the transcript (consent 460..484 within msg 400..507).

     So on mobile the whole bottom stack is anchored from the FLOOR — dash, then
     consent, then transcript — and each one's clearance is arithmetic rather than
     a coincidence of two independent anchors. 230px carries the notice's TWO-line
     case; it wraps at 360 and narrower. */
  .msg {
    top: auto;
    /* 190, not 179: the consent WRAPS TO TWO LINES at 360 and narrower (37px vs
       24), which lifts its top edge — measured, the transcript's bottom landed
       3px inside it. Sized against the wrapped case, so the narrow screens are
       the ones the number is chosen for rather than the ones it fails on. */
    bottom: calc(2.2cqh + env(safe-area-inset-bottom, 0px) + 190px);
    /* and shorter, so its TOP stays off the orb on a short screen: at 375x667 a
       16cqh block started exactly at the orb's lower edge (gap 0).

       15, not 13: at 13cqh the opening line was CUT MID-SENTENCE on a 390 — the
       body scrolls, so nothing was lost, but a greeting sliced in half reads as
       broken, which is a worse failure than the one I was fixing. 15cqh clears
       the orb at 375 (the binding size) AND fits the line at 390. */
    max-height: 15cqh;
  }

  /* the system strip, mirrored to the foot of the phone frame */
  .deskstatus {
    display: flex; justify-content: space-between; align-items: center;
    /* @DEE 2026-08-18: "the ticker on the bottom is not visible" — at +10 it sat
       under Safari's bar. Lifted clear of it, and it is still the lowest thing in
       the stack, with the dash sized above it. */
    position: absolute; bottom: calc(env(safe-area-inset-bottom, 0px) + 62px);
    left: 0; right: 0; z-index: 6;
    /* three entries + space-between on a 390px frame clipped "AEGIS v2.1" off the
       right edge; tightened until the whole strip fits at 360 too. */
    padding: 0 2.5cqw; white-space: nowrap; pointer-events: none; overflow: hidden;
    font: 500 5.6px/1 var(--font-mono); letter-spacing: .06em; text-transform: uppercase;
    color: rgba(190, 225, 245, .8);
  }
  .deskstatus b { color: #9fe6ff; font-weight: 500; margin-left: 4px; }

  /* [ORION 2026-08-18] SHORT phones are a height problem, not a width one.
     At 375x667 the band between the orb's lower edge (367) and the notice (483)
     is 116px, and at 15cqh the transcript both TOUCHED the orb (-5) and still cut
     the greeting mid-sentence. Neither trimming height nor keeping it could win
     there, because the text itself needed more room than the gap allows.

     So on short viewports the block is made cheaper rather than smaller: less
     padding and a slightly tighter line, which buys the clearance AND the line.
     Keyed on max-height so it follows the constraint that actually binds — a
     tall 375-wide phone has no such problem and is left alone. */
  @media (max-height: 700px) {
    .msg { max-height: 13.5cqh; padding: 1.5cqh 4.4cqw 1.2cqh; }
    .msg__body { font: 400 3.5cqw/1.35 var(--font-body); min-height: 2.4em; }
    .msg__who, .msg__meta { font-size: 8.5px; }
  }
  .ds-sys { display: inline-flex; align-items: center; gap: 4px; }
  /* desktop sizes this dot inside its own block; declared here too because a
     rule — and a @keyframes — inside a media query does not exist outside it. */
  .ds-on { width: 4px; height: 4px; border-radius: 50%; flex: none;
    background: #6effb0; box-shadow: 0 0 6px rgba(110, 255, 176, .95);
    animation: dsPulseMobile 2.4s ease-in-out infinite; }
  @keyframes dsPulseMobile { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }
}
