/* Transmission skin — shared by every page that mounts the live chair.
   Three layers, one file, loaded AFTER vex-app.css so its overrides win:

   1. The booking console (#vex-booking). Formerly duplicated inline in
      enlist and savetheworld and MISSING from captain, which is why the
      quiz page shipped an unstyled grey list. One copy, all pages.
   2. The distant-transmission look over the Runway call frame, via the
      bundle's stable data attributes ([data-avatar-*]). CSS only — the
      770KB bundle is never edited.
   3. Voice indicators: a pulsing speaker glyph and equaliser bars, so the
      channel reads as AUDIO even before video locks, and the written quiz
      still feels like a transmission (.vx-speak utility).                */

/* ---- 1. Booking console ------------------------------------------------ */
#vex-booking { width:100%; max-width:min(92vw, 560px); margin:-1px auto 0; border:1px solid rgba(247,147,30,0.38); border-top-color:rgba(247,147,30,0.22); border-radius:0 0 14px 14px; background:linear-gradient(180deg, rgba(11,22,36,0.96), rgba(8,18,30,0.96)); padding:16px 18px 18px; text-align:left; position:relative; overflow:hidden }
#vex-booking:before { content:""; position:absolute; inset:0; pointer-events:none; background:repeating-linear-gradient(180deg, rgba(247,147,30,0.035) 0 1px, transparent 1px 3px) }
#vex-booking > * { position:relative }
#vex-booking .bk-h { font-family:'JetBrains Mono',monospace; font-size:11px; letter-spacing:0.14em; color:#f7931e; margin:0 0 12px; display:flex; align-items:center; gap:8px }
#vex-booking .bk-h:before { content:""; width:6px; height:6px; border-radius:50%; background:#f7931e; box-shadow:0 0 8px #f7931e; flex:none; animation:vx-blink 1.6s ease-in-out infinite }
#vex-booking ol { margin:0; padding:0; list-style:none }
#vex-booking li { display:flex; gap:12px; align-items:center; padding:10px 0; border-bottom:1px solid rgba(247,147,30,0.1); color:#f5efe3; line-height:1.45; font-size:16px }
#vex-booking li:last-of-type { border-bottom:0 }
#vex-booking .num { font-family:'JetBrains Mono',monospace; font-weight:700; color:#0b1626; background:#f7931e; border-radius:6px; min-width:26px; height:26px; display:inline-flex; align-items:center; justify-content:center; font-size:14px; flex:none }
#vex-booking li.held { color:#f7931e }
#vex-booking li.held .num { box-shadow:0 0 0 3px rgba(247,147,30,0.28) }
#vex-booking li.dim { opacity:0.4 }
#vex-booking .wname { display:block; font-family:'JetBrains Mono',monospace; font-size:10px; letter-spacing:0.14em; color:#f7931e }
#vex-booking .bk-mail { margin:14px 0 0; padding-top:14px; border-top:1px solid rgba(247,147,30,0.16) }
#vex-booking .bk-mail label { display:block; font-family:'JetBrains Mono',monospace; font-size:10px; letter-spacing:0.12em; color:#f7931e; margin-bottom:7px }
#vex-booking .bk-wrap { position:relative }
#vex-booking .bk-mail input { width:100%; background:#080f1a; border:1px solid rgba(247,147,30,0.3); color:#f5efe3; padding:13px 40px 13px 13px; border-radius:8px; font-family:inherit; font-size:16px; transition:border-color .15s ease }
#vex-booking .bk-mail input:focus { outline:none; border-color:#f7931e; box-shadow:0 0 0 3px rgba(247,147,30,0.15) }
#vex-booking .bk-tick { position:absolute; right:13px; top:50%; transform:translateY(-50%); color:#4ade80; font-size:17px; opacity:0; transition:opacity .15s ease; pointer-events:none }
#vex-booking .bk-wrap.good .bk-tick { opacity:1 }
#vex-booking .bk-wrap.good input { border-color:rgba(74,222,128,0.6) }
#vex-booking .bk-note { margin:8px 0 0; line-height:1.5 }
#vex-booking.ok { border-color:rgba(74,222,128,0.55); background:linear-gradient(180deg, rgba(10,32,22,0.96), rgba(8,24,18,0.96)) }
#vex-booking.ok .bk-h { color:#4ade80 }
#vex-booking.ok .bk-h:before { background:#4ade80; box-shadow:0 0 10px #4ade80; animation:none }
#vex-booking .bk-when { font-size:clamp(24px, 6vw, 32px); font-weight:700; line-height:1.15; color:#f5efe3; margin:2px 0 10px; letter-spacing:-0.01em }
#vex-booking.closed { border-color:rgba(248,113,113,0.5) }
#vex-booking.closed .bk-h { color:#f87171 }
#vex-booking.closed .bk-h:before { background:#f87171; box-shadow:0 0 8px #f87171; animation:none }
#vex-booking a { color:#f7931e }
#vex-booking a.btn-primary { color:#0b1626 }

/* ---- 2. The distant transmission --------------------------------------- */
/* The frame: on-brand navy while connecting, hairline seam into the booking
   console below, orange spinner instead of the bundle's white one. */
#vex-app [data-avatar-call] {
  --avatar-bg-connecting: #0b1626;
  --avatar-radius: 16px 16px 0 0;
  border: 1px solid rgba(247,147,30,0.42);
  border-bottom-color: rgba(247,147,30,0.24);
  /* A lit viewport, not a flat rectangle: inner top highlight, deep drop, and
     a warm halo so the character sits IN the console rather than on it. */
  box-shadow:
    0 1px 0 rgba(255,255,255,0.10) inset,
    0 26px 60px rgba(4,9,15,0.62),
    0 0 90px rgba(247,147,30,0.13);
  /* Taller than the bundle's 16/9 default: a talking head framed at 16/9 gets
     cropped at the chin and crown, because the avatar feed is nearer square.
     4/3 gives her head and shoulders room. NEVER override min-height on
     [data-avatar-video] to do this — it is a flex child with min-height:0, so
     forcing a height breaks the flex sizing and letterboxes the frame. */
  aspect-ratio: 4 / 3;
  max-width: min(94vw, 620px);
  margin: 0 auto;
}
/* Fill the frame edge to edge. In a live call the control bar takes part of
   the flex column, which left a dead band under the picture; pinning the feed
   to the frame makes the geometry independent of whatever else the bundle
   puts in that column. The control bar has its own z-index and still floats
   over the video. */
#vex-app [data-avatar-video] { position: absolute; inset: 0; }
#vex-app [data-avatar-video] video { width: 100%; height: 100%; object-fit: cover; }
/* Scanlines + vignette over the video itself: a signal from very far away,
   not a webcam. Sits above the video (z0), below controls (z2) and spinner. */
#vex-app [data-avatar-video]::before {
  content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background:
    repeating-linear-gradient(180deg, rgba(8,18,30,0.16) 0 1px, transparent 1px 3px),
    radial-gradient(120% 90% at 50% 40%, transparent 55%, rgba(8,18,30,0.55) 100%);
  animation: vx-flicker 7s steps(1) infinite;
}
#vex-app [data-avatar-video] video { filter: saturate(0.88) contrast(1.05) brightness(0.98); }
/* The bundle's spinner, re-tuned to the brand. */
#vex-app [data-avatar-call]:has([data-avatar-video][data-avatar-status=connecting])::after,
#vex-app [data-avatar-call]:has([data-avatar-video][data-avatar-status=waiting])::after {
  border-color: rgba(247,147,30,0.18); border-top-color: #f7931e;
}
/* Connecting: telemetry instead of a blank stare. */
#vex-app [data-avatar-video][data-avatar-status=connecting]::after,
#vex-app [data-avatar-video][data-avatar-status=waiting]::after {
  content: "ACQUIRING SIGNAL // DEEP SPACE RELAY";
  /* above the control bar (its buttons + 16px padding), never behind it */
  position: absolute; inset: auto 0 76px 0; height: auto; z-index: 2;
  text-align: center; font-family: 'JetBrains Mono', monospace; font-size: 11px;
  letter-spacing: 0.22em; color: #f7931e; background: none;
  animation: vx-blink 1.2s ease-in-out infinite;
}
/* Locked: a quiet LIVE tag in the corner once video is up. */
#vex-app [data-avatar-video][data-avatar-status=ready]::after {
  content: "● LIVE // RELAY LOCKED";
  position: absolute; top: 10px; left: 12px; z-index: 2; pointer-events: none;
  font-family: 'JetBrains Mono', monospace; font-size: 10px; letter-spacing: 0.18em;
  color: #f7931e; background: rgba(8,18,30,0.55); border: 1px solid rgba(247,147,30,0.35);
  border-radius: 5px; padding: 3px 8px;
}

/* Ended ("done") state. The bundle exposes no [data-avatar-*] hook here and
   pins its REOPEN CHANNEL button absolutely at the poster's bottom edge
   (inline bottom:22px), where it lands on top of the caption below. That
   button is the ONLY .btn-ghost the bundle renders, so lift just it into
   normal flow — centered below the poster, clear of the heading and caption. */
#vex-app .btn.btn-ghost {
  position: static !important;
  transform: none !important;
  display: block;
  width: max-content;
  max-width: 100%;
  margin: 16px auto 2px !important;
  box-shadow: none !important;
}

/* The idle poster is a transmission too: scanlines masked into the image
   itself (no overlay geometry — the caption and button stay clean), the
   palette pulled toward a long-range feed, a faint RGB fringe on the edges,
   and the same occasional flicker the live video carries. */
#vex-app > div > img {
  /* the bundle inlines width:100% AND max-width:min(92vw,560px) on the poster;
     !important is the only way to win that fight, keep the ~square portrait
     from stretching, and stop 92vw from overflowing the narrower card it
     actually sits in on phones */
  display: block; max-height: 400px; width: auto !important; max-width: 100% !important; margin: 0 auto;
  filter: saturate(0.8) contrast(1.09) brightness(0.96)
          drop-shadow(1px 0 0 rgba(255, 70, 70, 0.10))
          drop-shadow(-1px 0 0 rgba(80, 220, 255, 0.10));
  -webkit-mask-image: repeating-linear-gradient(180deg, #000 0 2px, rgba(0,0,0,0.72) 2px 4px);
  mask-image: repeating-linear-gradient(180deg, #000 0 2px, rgba(0,0,0,0.72) 2px 4px);
  animation: vx-flicker 7s steps(1) infinite;
}

/* ---- 3. Voice indicators ----------------------------------------------- */
/* The caption under the chair becomes a console readout with a speaker glyph
   and live equaliser bars: the channel is VOICE, whatever the video does. */
#vex-app > p, #vex-app p.small {
  font-family: 'JetBrains Mono', monospace; font-size: 11.5px; letter-spacing: 0.12em;
  text-transform: uppercase; color: #cfc9bc;
  display: flex; align-items: center; justify-content: center; gap: 9px;
}
/* IDLE CHAIR: OPEN CHANNEL sits under the poster, not on it.
   The bundle pins it `position:absolute; bottom:22px` against the wrapper
   that holds the poster AND the caption — not the poster — so the anchor is
   measured from the bottom of the caption and the button lands on the text.
   Re-anchoring only chases the symptom (and adding margin to the caption
   makes it worse: that grows the wrapper and the button follows it down).
   In normal flow the collision cannot happen at all, and the primary action
   reads as a button under the picture rather than a sticker on it. Same
   treatment the ended state's REOPEN CHANNEL already gets above.

   The button matches the poster by sharing its ceiling rather than by
   resizing the wrapper: the poster is `max-height:400px` above and
   near-square, so ~400px is its widest, and both are centred in the same
   column. Shrinking the wrapper instead would drag the caption in with it and
   wrap a line that used to fit. Scoped with :has(> img) so it only touches
   the idle state — during a call this wrapper holds [data-avatar-call], which
   sizes itself. */
#vex-app > div:has(> img):not(:has([data-avatar-call])) > button.btn.btn-primary {
  position: static !important;
  transform: none !important;
  display: block;
  width: 100%;
  max-width: 400px;
  margin: 14px auto 0 !important;
}
#vex-app > p::before, #vex-app p.small::before, .vx-speak::before {
  content: ""; width: 15px; height: 15px; flex: none; background: #f7931e;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M3 9v6h4l5 4V5L7 9H3z'/%3E%3Cpath d='M16 8a4.5 4.5 0 010 8v-2a2.5 2.5 0 000-4V8z'/%3E%3Cpath d='M16 4.5a8 8 0 010 15v-2a6 6 0 000-11v-2z'/%3E%3C/svg%3E") center/contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M3 9v6h4l5 4V5L7 9H3z'/%3E%3Cpath d='M16 8a4.5 4.5 0 010 8v-2a2.5 2.5 0 000-4V8z'/%3E%3Cpath d='M16 4.5a8 8 0 010 15v-2a6 6 0 000-11v-2z'/%3E%3C/svg%3E") center/contain no-repeat;
  animation: vx-pulse 1.8s ease-in-out infinite;
}
#vex-app > p::after, #vex-app p.small::after {
  content: ""; width: 16px; height: 13px; flex: none;
  background:
    linear-gradient(#f7931e 0 0) left   bottom / 3px 55% no-repeat,
    linear-gradient(#f7931e 0 0) center bottom / 3px 95% no-repeat,
    linear-gradient(#f7931e 0 0) right  bottom / 3px 70% no-repeat;
  animation: vx-eq 1.1s ease-in-out infinite;
}
/* Utility for written-mode voice lines (the quiz): same speaker glyph inline. */
.vx-speak::before { display: inline-block; vertical-align: -2px; margin-right: 8px; }

/* Fine print, folded: the console keeps its paperwork in a drawer. */
details.vx-fine { max-width: 640px; }
details.vx-fine summary {
  cursor: pointer; list-style: none; font-family: 'JetBrains Mono', monospace;
  font-size: 10.5px; letter-spacing: 0.18em; color: #cfc9bc; text-transform: uppercase;
  padding: 6px 0;
}
details.vx-fine summary::before { content: "▸ "; color: #f7931e; }
details.vx-fine[open] summary::before { content: "▾ "; }
details.vx-fine summary::-webkit-details-marker { display: none; }

@keyframes vx-blink { 0%,100% { opacity: 1 } 50% { opacity: 0.25 } }
@keyframes vx-pulse { 0%,100% { opacity: 1; transform: scale(1) } 50% { opacity: 0.55; transform: scale(0.92) } }
@keyframes vx-eq {
  0%,100% { background-size: 3px 55%, 3px 95%, 3px 70% }
  33%     { background-size: 3px 90%, 3px 45%, 3px 85% }
  66%     { background-size: 3px 40%, 3px 75%, 3px 50% }
}
@keyframes vx-flicker {
  0%, 93%, 100% { opacity: 1 }
  94% { opacity: 0.86 }
  95% { opacity: 1 }
  97% { opacity: 0.92 }
}
@media (prefers-reduced-motion: reduce) {
  #vex-booking .bk-h:before, #vex-app [data-avatar-video]::before,
  #vex-app [data-avatar-video][data-avatar-status=connecting]::after,
  #vex-app [data-avatar-video][data-avatar-status=waiting]::after,
  #vex-app > p::before, #vex-app > p::after,
  #vex-app p.small::before, #vex-app p.small::after, .vx-speak::before,
  #vex-app > div > img { animation: none }
}

/* ---- CHAIR STATE ---------------------------------------------------------
   The badge and the button tell the truth BEFORE anyone presses anything:
   busy (someone else is in the chair), capacity (today's cap is spent), and
   offline (the channel itself is down — no key, Runway erroring, or credits
   gone). Set --chair-who on the page to name the character; the captain page
   keeps its own copy of these rules inline, and since its <style> is parsed
   after this file, that copy still wins there. */
#vex-app.chair-busy > div:has(> img):not(:has([data-avatar-call])),
#vex-app.chair-capacity > div:has(> img):not(:has([data-avatar-call])),
#vex-app.chair-offline > div:has(> img):not(:has([data-avatar-call])) {
  position: relative;
}
#vex-app.chair-busy > div:has(> img):not(:has([data-avatar-call]))::before,
#vex-app.chair-capacity > div:has(> img):not(:has([data-avatar-call]))::before,
#vex-app.chair-offline > div:has(> img):not(:has([data-avatar-call]))::before {
  position: absolute; top: 10px; left: 12px; z-index: 2; pointer-events: none;
  font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.16em;
  background: rgba(8,18,30,0.65); border: 1px solid; border-radius: 999px;
  padding: 4px 9px;
}
#vex-app.chair-busy > div:has(> img):not(:has([data-avatar-call]))::before {
  content: var(--chair-who, "CHANNEL") " // ON ANOTHER CALL";
  color: var(--orange); border-color: rgba(247,147,30,0.45);
}
#vex-app.chair-capacity > div:has(> img):not(:has([data-avatar-call]))::before {
  content: var(--chair-who, "CHANNEL") " // AT CAPACITY TODAY";
  color: var(--orange); border-color: rgba(247,147,30,0.45);
}
#vex-app.chair-offline > div:has(> img):not(:has([data-avatar-call]))::before {
  content: var(--chair-who, "CHANNEL") " // CHANNEL OFFLINE";
  color: #f87171; border-color: rgba(248,113,113,0.45);
}
/* A dead channel gets a dead button — visible, so the state stays legible,
   but not pressable, because pressing it can only end in a shrug. Capacity
   gets the same: today's retries are already spent. Busy does not — it
   clears itself inside five minutes and a retry is the right instinct. */
#vex-app.chair-offline button.btn.btn-primary,
#vex-app.chair-capacity button.btn.btn-primary {
  pointer-events: none; opacity: 0.35; filter: saturate(0.4);
}
/* The line directly under the dead button, where the eye already is. */
.chair-note {
  margin: 10px auto 14px; max-width: min(92vw, 560px);
  font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.12em;
  line-height: 1.6; color: var(--cream-dim); text-transform: uppercase;
  text-align: center;
}
