/* Shared styles for the VH Study Wizard (Builder + Runner). Plain CSS, no build. */
/* Palette follows ride.ict.usc.edu: warm off-white, khaki accents, gold-brown primary. */
:root {
  --ink: #262626;
  --ink-soft: #69603e;
  --line: #e3ddc9;
  --bg: #f5efe0;
  --card: #ffffff;
  --accent: #876911;
  --accent-strong: #6d540d;
  --accent-soft: #efe9d3;
  --khaki: #d7cfab;
  --ok: #5d7a32;
  --radius: 12px;
  --shadow: 0 1px 3px rgba(70,60,30,.10), 0 8px 24px rgba(70,60,30,.07);
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  font-family: var(--font);
  color: var(--ink);
  background: var(--bg);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  /* Full-viewport column: topbar + scrolling middle + footer, pinned to exactly the viewport
     height so the BODY never scrolls. Only the middle region scrolls — otherwise you get a second
     (window) scrollbar on top of the content scrollbar. */
  display: flex; flex-direction: column; height: 100vh; overflow: hidden;
}
/* The middle region fills the space between topbar and footer and is the sole vertical scroller. */
.app, #participantRoot { flex: 1 1 auto; min-height: 0; }
#participantRoot { overflow-y: auto; }
a { color: var(--accent); }
h1, h2, h3 { line-height: 1.25; margin: 0 0 .4em; }
p { margin: 0 0 1em; }
.muted { color: var(--ink-soft); }
.small { font-size: .85rem; }
.center { text-align: center; }

/* Top bar — light, logo-led (black logos on warm off-white). Sticky: always visible. */
.topbar {
  display: flex; align-items: center; gap: 16px;
  padding: 12px 22px; background: var(--card); color: var(--ink);
  border-bottom: 3px solid var(--accent);
  position: sticky; top: 0; z-index: 60; flex: 0 0 auto;
}
.topbar .logo-vht { height: 30px; display: block; }
.topbar .logo-ict-card { height: 48px; display: block; margin: -6px 0; }
.topbar .brand { font-weight: 700; letter-spacing: .2px; }
.topbar .product {
  font-weight: 600; color: var(--ink-soft); font-size: .95rem;
  padding-left: 14px; border-left: 1px solid var(--line);
}
/* The wordmark links to the Studio root. It should read as part of the chrome rather than as
   body-copy link, so it keeps the muted colour and gains underline only on hover. */
a.product { text-decoration: none; }
a.product:hover { color: var(--accent); text-decoration: underline; }
.topbar .spacer { flex: 1; }
/* Environment label, on everything except production. Deliberately loud - mistaking test for
   production is the mistake it exists to prevent. */
.env-badge {
  font-size: .72rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  padding: 3px 9px; border-radius: 999px; white-space: nowrap;
  background: #b3261e; color: #fff;
}
.topbar-user { color: var(--ink-soft); white-space: nowrap; }
@media (max-width: 860px) { .topbar-user { display: none; } }
.topbar .pill {
  font-size: .75rem; padding: 3px 10px; border-radius: 999px;
  background: var(--accent-soft); color: var(--accent);
}
/* Prominent per-view label so the Builder and the Encounter (participant) view are never
   confused. Paired with the per-view palette below + the accent header border. */
.topbar .view-label {
  font-weight: 800; font-size: .82rem; letter-spacing: .4px; text-transform: uppercase;
  padding: 4px 12px; border-radius: 8px; background: var(--accent); color: #fff;
}

/* Per-view theming. The Builder keeps the warm gold palette (the :root default); the
   Encounter view switches to a cool teal palette so the two are unmistakable at a glance. */
body.view-encounter {
  --accent: #1f6f78;
  --accent-strong: #155059;
  --accent-soft: #d8eaec;
  --bg: #eef2f3;
  --line: #d4dee0;
}

/* Footer byline (SAIA credited in plain text per branding rule).
   Sticky: always visible at the bottom of the viewport. */
.app-footer {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  min-height: 52px; padding: 10px 24px; border-top: 1px solid var(--line);
  color: var(--ink-soft); font-size: .8rem; background: var(--bg);
  position: sticky; bottom: 0; z-index: 60; flex: 0 0 auto;
  box-shadow: 0 -2px 10px rgba(70, 60, 30, .07);
}
.app-footer img { height: 34px; opacity: .92; }
.app-footer .spacer { flex: 1; }
.app-footer a { color: var(--accent); font-weight: 600; text-decoration: none; }
.app-footer a:hover { text-decoration: underline; }
/* Keep the footer a single, constant-height line: drop the tagline when narrow. */
@media (max-width: 980px) { .app-footer .footer-extra { display: none; } }

/* Live VHToolkit WebGL player embed.
   The iframe lives in a fixed-position holder (#vhswPlayerHold) so it can preload
   hidden during consent and then be "docked" over .player-slot without a reload. */
/* Sized from the viewport so the whole conversation screen (topbar, dots, player,
   transcript, finish row, footer) fits WITHOUT a vertical scrollbar. */
.player-slot {
  aspect-ratio: 16 / 9;
  width: min(100%, calc((100vh - 520px) * 16 / 9));
  min-width: min(100%, 480px);
  margin: 0 auto;
  border-radius: 14px; background: #14171b;
}

/* Live conversation: player + tall transcript side by side on wide screens. */
.player-guide { text-align: center; font-weight: 600; color: var(--ink-soft); margin: 0 0 12px; }
.live-shell {
  display: grid; grid-template-columns: minmax(0, 1fr) 340px; gap: 14px;
  align-items: stretch; max-width: 1280px; margin: 0 auto;
}
.live-shell .player-slot {
  width: 100%; min-width: 0; margin: 0; justify-self: center;
  max-width: calc((100vh - 400px) * 16 / 9);
  /* Never stretch with the row: the chat's growing transcript would otherwise drive the
     row taller and squeeze the player from 16:9 toward portrait as the conversation grows. */
  align-self: start;
}
/* Starting height only. The dock loop measures the player slot and sets an exact pixel height
   here so the two panels share a bottom edge - CSS cannot tie one grid sibling's height to
   another's, and an auto height lets a growing transcript drive the row taller. */
.live-shell .chat.compact {
  height: auto; min-height: 0; max-height: none; margin-top: 0; max-width: none;
}
@media (max-width: 1024px) {
  .live-shell { grid-template-columns: 1fr; }
  /* Stacked layout, which includes phones in portrait. 200px left roughly two visible lines
     once the composer and padding were subtracted; 320px shows four or five turns. */
  .live-shell .chat.compact { height: 320px; min-height: 0; max-height: none; }
}
/* Tighter chrome on the live-conversation screen so player + transcript fit 100vh. */
.stage.wide { padding-top: 18px; padding-bottom: 18px; }
.stage.wide .progress-dots { margin-bottom: 14px; }
/* Inside the viewport, behind the page, rather than parked off-screen.
   The obvious way to preload something invisibly is to move it far off-screen, and that is what
   this did (left: -10000px). It costs a great deal: a browser does not paint what is outside the
   viewport, and a Unity WebGL build boots on requestAnimationFrame and warms its shaders by
   drawing. Off-screen it still loads, just slowly - measured at about 50 seconds against about 15
   for the identical build in a harness page where the iframe is on screen.
   So it stays within the viewport and is hidden by stacking order instead: behind an opaque body
   background, transparent, and not clickable. The browser composites it, the participant never
   sees it. Docking to .player-slot on the conversation screen is unchanged. */
#vhswPlayerHold {
  position: fixed; left: 0; top: 0;
  width: 1280px; height: 720px;
  z-index: -1;
  opacity: 0.01;
  pointer-events: none;
}
/* Docked over the visible slot: undo the hiding, since it is now the thing being looked at.
   Position stays fixed - dockPlayer drives left/top/width/height inline to track the slot's box -
   so only the three properties that hide it are reversed. */
#vhswPlayerHold.docked {
  z-index: 40;
  opacity: 1;
  pointer-events: auto;
}
#vhswPlayerHold iframe {
  width: 100%; height: 100%; border: 0; border-radius: 14px;
  background: #14171b; display: block;
}

/* Layout */
.app { display: flex; min-height: 0; overflow: hidden; }
.rail {
  width: 280px; flex: 0 0 280px; background: var(--card);
  border-right: 1px solid var(--line); padding: 22px 18px;
  overflow-y: auto;
}
.main { flex: 1; padding: 32px 40px; max-width: 820px; overflow-y: auto; }
.main.wide { max-width: 1000px; }

/* Step list (builder rail) */
.steps { list-style: none; margin: 0; padding: 0; }
.steps li {
  display: flex; align-items: center; gap: 12px;
  padding: 11px 12px; border-radius: 10px; cursor: pointer; color: var(--ink-soft);
  font-weight: 500;
}
.steps li:hover { background: var(--bg); }
.steps li.active { background: var(--accent-soft); color: var(--accent); }
.steps li.done .num { background: var(--ok); border-color: var(--ok); color: #fff; }
.steps .num {
  width: 26px; height: 26px; flex: 0 0 26px; border-radius: 50%;
  border: 2px solid var(--line); display: grid; place-items: center;
  font-size: .8rem; font-weight: 700; background: #fff;
}
.steps li.active .num { border-color: var(--accent); color: var(--accent); }

/* Cards & forms */
.card {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 24px; box-shadow: var(--shadow);
}
.card + .card { margin-top: 18px; }
/* A step mixes cards, question editors and button rows, so spacing cannot rely on .card + .card
   alone: anything non-card sitting between two cards leaves them touching. Two cases seen in
   the Questions & debrief step - the first question butting against the section card, and the
   add-question buttons butting against the Debrief card. */
.main > * + .card { margin-top: 18px; }
.main > .card + .qitem { margin-top: 18px; }
.field { margin-bottom: 18px; }
.field label { display: block; font-weight: 600; margin-bottom: 6px; }
.field .hint { font-size: .82rem; color: var(--ink-soft); margin: 0 0 8px; }
input[type=text], input[type=email], input[type=password], input[type=number], textarea, select {
  width: 100%; padding: 10px 12px; font: inherit; color: var(--ink);
  border: 1px solid var(--line); border-radius: 8px; background: #fff;
}
input:focus, textarea:focus, select:focus { outline: 2px solid var(--accent-soft); border-color: var(--accent); }
textarea { min-height: 96px; resize: vertical; }
.row { display: flex; gap: 16px; }
.row > * { flex: 1; }
.checkbox { display: flex; align-items: flex-start; gap: 10px; margin-bottom: 10px; }
.checkbox input { margin-top: 4px; }
/* The same control sitting inline in a button row rather than stacked in a form. */
.checkbox.mic-consent { align-items: center; margin-bottom: 0; white-space: nowrap; font-weight: 600; }
.checkbox.mic-consent input { margin-top: 0; }
/* A checkbox and an inline action sharing one line. Unlike .row this does not stretch its
   children into equal columns, so the action sits next to the label instead of drifting to
   the middle of its own half. */
.filter-row { display: flex; align-items: center; gap: 14px; }
.filter-row .checkbox { align-items: center; margin-bottom: 0; }
.filter-row .checkbox input { margin-top: 0; }

/* Buttons */
.btn {
  display: inline-flex; align-items: center; gap: 8px; cursor: pointer;
  font: inherit; font-weight: 600; padding: 10px 18px; border-radius: 9px;
  border: 1px solid var(--line); background: #fff; color: var(--ink);
}
.btn:hover { border-color: var(--khaki); }
.btn.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.btn.primary:hover { background: var(--accent-strong); }
.btn.ghost { background: transparent; border-color: transparent; color: var(--accent); }
.btn.small { padding: 6px 12px; font-size: 14px; }

/* Load progress on the pre-conversation brief, as named stages rather than a percentage. A bar
   invited clock-watching and made a long load feel longer; two of these three stages are driven
   by real milestones from the player, so they report what is happening instead of counting down
   an estimate. */
.loadsteps { list-style: none; margin: 16px 0 6px; padding: 0; display: grid; gap: 8px; }
.loadstep { display: flex; align-items: center; gap: 10px; font-size: .92rem; color: var(--ink-soft); }
.loadstep-mark {
  width: 16px; height: 16px; flex: none; border-radius: 50%;
  border: 2px solid var(--khaki); background: transparent; position: relative;
}
.loadstep.active { color: var(--ink); font-weight: 600; }
.loadstep.active .loadstep-mark {
  border-color: var(--accent);
  animation: loadstep-pulse 1.4s ease-in-out infinite;
}
.loadstep.done { color: var(--ink-soft); }
.loadstep.done .loadstep-mark { border-color: #4b8b3b; background: #4b8b3b; }
/* Tick, drawn rather than an icon font so it needs no asset. */
.loadstep.done .loadstep-mark::after {
  content: ""; position: absolute; left: 3px; top: 0px;
  width: 5px; height: 9px; border: solid #fff; border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}
/* Animating from an accent-coloured ring of zero spread to a transparent one keeps the pulse in
   whatever accent the page uses - the Encounter screens are teal, the Builder gold - without
   hardcoding a colour a keyframe cannot read from a variable as an rgba(). */
@keyframes loadstep-pulse {
  0%, 100% { box-shadow: 0 0 0 0 var(--accent); }
  50%      { box-shadow: 0 0 0 5px transparent; }
}
@media (prefers-reduced-motion: reduce) {
  .loadstep.active .loadstep-mark { animation: none; }
}
.btn:disabled { opacity: .45; cursor: not-allowed; }
.btn-row { display: flex; align-items: center; gap: 12px; margin-top: 26px; }
.btn-row .spacer { flex: 1; }

/* Choice cards (character/scene/voice pickers) */
.choice-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; }
.choice {
  position: relative;
  border: 2px solid var(--line); border-radius: 12px; padding: 16px; cursor: pointer;
  text-align: center; background: #fff; transition: border-color .12s;
}
.choice .zoom-btn {
  position: absolute; top: 8px; right: 8px; width: 28px; height: 28px; border-radius: 6px;
  border: none; background: rgba(0,0,0,.55); color: #fff; font-size: .95rem; cursor: pointer;
  line-height: 1; display: flex; align-items: center; justify-content: center; padding: 0;
}
.choice .zoom-btn:hover { background: rgba(0,0,0,.78); }
.choice .gen-btn {
  position: absolute; top: 8px; left: 8px; width: 28px; height: 28px; border-radius: 6px;
  border: none; background: var(--accent); color: #fff; font-size: .95rem; cursor: pointer;
  line-height: 1; display: flex; align-items: center; justify-content: center; padding: 0;
}
.choice .gen-btn:hover { background: var(--accent-strong); }
.choice:hover { border-color: var(--khaki); }
.choice.sel { border-color: var(--accent); background: var(--accent-soft); }
.choice .emoji { font-size: 2.4rem; line-height: 1; }
.choice .portrait {
  width: 84px; height: 84px; border-radius: 50%; object-fit: cover;
  display: block; margin: 0 auto; background: var(--khaki);
}
.choice .thumb {
  width: 100%; aspect-ratio: 16/9; border-radius: 8px; object-fit: cover; display: block;
}
.choice .name { font-weight: 700; margin-top: 8px; }
.choice .blurb { font-size: .78rem; color: var(--ink-soft); }

/* Questionnaire item editor */
.qitem { border: 1px solid var(--line); border-radius: 10px; padding: 16px; margin-bottom: 14px; background: #fff; }
.qitem .qhead { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; flex-wrap: nowrap; }
.qitem .qhead .qtype { width: 200px; flex: 0 0 200px; }
.qitem .qhead .spacer { flex: 1; }
/* Inputs default to width:100%, which in a flex row pushes the siblings onto a second line.
   Let the text field shrink instead; min-width:0 is required or flex refuses to go below the
   input's intrinsic width. */
.qitem .qhead input[type="text"] { flex: 1 1 140px; min-width: 0; width: auto; }
.qitem .qhead input[type="number"] { flex: 0 0 58px; width: 58px; }
.tag { font-size: .72rem; padding: 2px 8px; border-radius: 999px; background: var(--bg); color: var(--ink-soft); white-space: nowrap; }
.opt-row { display: flex; gap: 8px; margin-bottom: 8px; }
.opt-row .btn { padding: 8px 12px; }

/* Review summary */
.summary dl { display: grid; grid-template-columns: 180px 1fr; gap: 8px 18px; margin: 0; }
.summary dt { font-weight: 600; color: var(--ink-soft); }
.summary dd { margin: 0; }
.errors { background: #fff4f4; border: 1px solid #f3c6c6; border-radius: 10px; padding: 14px 16px; }
.errors ul { margin: 6px 0 0; padding-left: 20px; }

/* Banner */
.banner { padding: 10px 16px; border-radius: 8px; background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.banner.ok { background: #e7f6ee; color: var(--ok); }

/* ---------- Runner (participant) ---------- */
.stage { max-width: 760px; margin: 0 auto; padding: 40px 24px; }
/* Wide enough for the live shell's own 1280px cap plus the stage padding. At 920px the shell
   never reached that cap, so the character panel was rendered roughly a third narrower than the
   viewport formula intends while the space beside it went unused. */
.stage.wide { max-width: min(1328px, 96vw); }
/* Embedded Qualtrics survey: near full width so the survey isn't clipped. */
.stage.survey { max-width: min(1200px, 96vw); }
.progress-dots { display: flex; gap: 8px; justify-content: center; margin-bottom: 28px; }
.progress-dots .dot { width: 9px; height: 9px; border-radius: 50%; background: var(--line); }
.progress-dots .dot.on { background: var(--accent); }
.hero { text-align: center; padding: 18px 0 8px; }
.hero .big { font-size: 1.8rem; font-weight: 800; }
.reqs { display: flex; gap: 16px; justify-content: center; margin: 18px 0; flex-wrap: wrap; }
.req { display: flex; align-items: center; gap: 8px; background: #fff; border: 1px solid var(--line);
       border-radius: 999px; padding: 8px 14px; font-size: .85rem; }

/* VH / Unity stub */
.vh-shell { display: grid; grid-template-columns: 1fr 340px; gap: 18px; }
@media (max-width: 820px){ .vh-shell { grid-template-columns: 1fr; } }
.vh-stage {
  position: relative; background: linear-gradient(160deg, #2b2f36, #14171b);
  border-radius: 14px; min-height: 420px; overflow: hidden; color: #fff;
  display: flex; align-items: center; justify-content: center;
}
.vh-stage .placeholder { text-align: center; opacity: .92; }
.vh-stage .avatar { font-size: 6rem; line-height: 1; }
.vh-stage .portrait-large { width: 180px; height: 180px; border-radius: 50%; object-fit: cover; display: block; margin: 0 auto; }
.vh-stage .stub-tag {
  position: absolute; top: 12px; left: 12px; font-size: .72rem;
  background: rgba(255,255,255,.12); padding: 3px 10px; border-radius: 999px;
}
.vh-stage .speaking { position: absolute; bottom: 16px; left: 16px; right: 16px;
  background: rgba(0,0,0,.45); padding: 10px 14px; border-radius: 10px; font-size: .95rem; }
.chat { background: #fff; border: 1px solid var(--line); border-radius: 14px; display: flex; flex-direction: column; height: 420px; }
.chat.compact { height: 150px; margin-top: 12px; max-width: 920px; margin-left: auto; margin-right: auto; }
/* min-height:0 is what lets the log scroll rather than pushing the panel taller than its row. */
.chat .log { flex: 1; min-height: 0; overflow-y: auto; padding: 14px; display: flex; flex-direction: column; gap: 10px; }
.bubble { padding: 9px 13px; border-radius: 14px; max-width: 85%; font-size: .92rem; }
.bubble.vh { background: var(--bg); align-self: flex-start; border-bottom-left-radius: 4px; }
.bubble.user { background: var(--accent); color: #fff; align-self: flex-end; border-bottom-right-radius: 4px; }
.chat .composer { display: flex; gap: 8px; padding: 12px; border-top: 1px solid var(--line); }
.chat .composer input { flex: 1; }
.mic { width: 46px; flex: 0 0 46px; display: grid; place-items: center; padding: 0; }
.mic.live { background: var(--accent); border-color: var(--accent); color: #fff; }

/* Questionnaire (runner) */
.likert { display: flex; gap: 6px; justify-content: space-between; margin-top: 10px; }
.likert label { flex: 1; text-align: center; border: 1px solid var(--line); border-radius: 8px;
  padding: 10px 4px; cursor: pointer; font-size: .85rem; }
.likert input { display: block; margin: 0 auto 6px; }
.likert label.sel { border-color: var(--accent); background: var(--accent-soft); }
.likert-ends { display: flex; justify-content: space-between; font-size: .78rem; color: var(--ink-soft); margin-top: 4px; }
.qcard { margin-bottom: 16px; }
.qcard .qprompt { font-weight: 600; margin-bottom: 6px; }
.req-star { color: var(--accent); }

.code-box { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 1.3rem; font-weight: 700;
  background: var(--bg); border: 1px dashed var(--line); border-radius: 10px; padding: 14px 20px;
  display: inline-block; margin: 10px 0; letter-spacing: 1px; }

/* Qualtrics embed */
.qframe { width: 100%; height: 85vh; min-height: 640px; border: 1px solid var(--line); border-radius: 10px; background: #fff; display: block; }

/* Generated-backdrop option grid */
.backdrop-options { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-top: 10px; }
.backdrop-cell { position: relative; }
.backdrop-cell img {
  width: 100%; aspect-ratio: 16 / 9; object-fit: cover; border-radius: 8px;
  border: 2px solid var(--line); cursor: pointer; transition: border-color .12s; display: block;
}
.backdrop-cell img:hover { border-color: var(--khaki); }
.backdrop-cell.sel img { border-color: var(--accent); }
.backdrop-check {
  position: absolute; top: 6px; left: 6px; font-size: .72rem; font-weight: 700;
  background: var(--accent); color: #fff; padding: 2px 8px; border-radius: 999px;
}

/* Backdrop review lightbox */
.lightbox {
  background: #fff; border-radius: var(--radius); box-shadow: var(--shadow);
  padding: 18px; max-width: min(1100px, 92vw);
}
/* The generator does not always return exactly 16:9, so each option would otherwise be reviewed
   at a different size. Frame them identically in the shape they will actually be used in - the
   crop is honest, since the backdrop is displayed on a 16:9 screen. Radius and overflow live on
   the frame so it clips regardless of the image's own dimensions. */
.lightbox .shot {
  width: min(1000px, 88vw); aspect-ratio: 16 / 9; margin: 0 auto;
  border-radius: 10px; overflow: hidden; background: var(--bg);
}
.lightbox .shot img { display: block; width: 100%; height: 100%; object-fit: cover; }

/* In-page confirm modal (native confirm() hangs in embedded preview webviews) */
.modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,.35); display: grid; place-items: center; z-index: 100; }
.modal { background: #fff; border-radius: var(--radius); box-shadow: var(--shadow); padding: 24px; max-width: 380px; width: calc(100% - 40px); }
.modal p { margin: 0; font-weight: 600; }

/* Access-request badge, bottom-right. Shown on the researcher-facing Studio pages only - see
   shared/signup-badge.js for why the participant flow is excluded. Sits above page content but
   below the modal layer, so a confirm dialog is never obscured by it. */
.signup-badge {
  position: fixed; right: 16px; bottom: 16px; z-index: 60;
  display: flex; align-items: center; gap: 12px;
  padding: 10px 14px 10px 10px;
  background: #fff; border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow); text-decoration: none; color: inherit;
  max-width: min(300px, calc(100vw - 32px));
}
.signup-badge:hover { border-color: var(--accent); }
/* Rendered inside the left rail instead of pinned to the viewport: no fixed positioning, so it
   scrolls with the rail and cannot overlap the footer. */
.signup-badge.inline {
  position: static; right: auto; bottom: auto; max-width: 100%;
  padding: 10px; gap: 10px;
}
.signup-badge.inline .signup-badge-close { display: none; }
.signup-badge.inline img { width: 56px; height: 56px; }
.signup-badge img { display: block; width: 64px; height: 64px; flex: none; border-radius: 4px; }
.signup-badge-text { display: flex; flex-direction: column; gap: 2px; line-height: 1.25; }
.signup-badge-text strong { font-size: .95em; }
.signup-badge-text span { font-size: .82em; opacity: .75; }
.signup-badge-close {
  position: absolute; top: -8px; right: -8px; width: 20px; height: 20px;
  border: 1px solid var(--line); border-radius: 50%; background: #fff;
  font-size: 11px; line-height: 1; cursor: pointer; color: inherit; padding: 0;
}
.signup-badge-close:hover { border-color: var(--accent); }

/* On a phone the badge would cover the form it is inviting people to fill in, so drop the QR
   (nobody scans a code on the screen they are holding) and keep just the link. */
@media (max-width: 560px) {
  .signup-badge { right: 10px; bottom: 10px; padding: 8px 12px; }
  .signup-badge img { display: none; }
}

/* Phone-width topbar. At full size the two logos plus the wordmark are wider than a phone
   viewport, so the right-hand logo was pushed off the screen and clipped. Both logos shrink
   and the chrome tightens rather than either being dropped - the USC and VHToolkit marks are
   the attribution on pages that have no footer. */
@media (max-width: 560px) {
  .topbar { padding: 10px 12px; gap: 10px; }
  .topbar .logo-ict-card { height: 32px; margin: 0; }
  .topbar .logo-vht { height: 22px; }
  .topbar .product { font-size: .82rem; padding-left: 10px; }
  .topbar .view-label { font-size: .68rem; padding: 3px 7px; }
}

/* Narrow viewports (e.g. the docked desktop preview panel): stack rail on top */
@media (max-width: 720px) {
  .app { flex-direction: column; }
  .rail { width: 100%; flex: none; border-right: none; border-bottom: 1px solid var(--line); }
  .steps { display: flex; flex-wrap: wrap; gap: 6px; }
  .steps li { padding: 8px 10px; }
  .main { padding: 22px 18px; max-width: 100%; }

  /* Let the PAGE scroll on a phone instead of pinning the body to the viewport and scrolling
     only the middle pane. With the rail stacked on top, that pane collapsed to a couple of
     hundred pixels holding the whole step, which is unusable - and 100vh is unreliable on iOS
     anyway because the address bar is not counted consistently. Desktop keeps the pinned
     layout, where the single inner scroller is what stops a double scrollbar appearing. */
  body { height: auto; min-height: 100vh; overflow: visible; }
  .app, #participantRoot { min-height: 0; }
  .main { overflow-y: visible; }
  #participantRoot { overflow-y: visible; }

  /* The badge costs vertical space above the content on a stacked layout, and nobody scans a
     QR code on the phone they are holding. The footer links still reach the page. */
  .signup-badge.inline { display: none; }
}

/* ---- Video-based character option ------------------------------------------------------
   The character-type choice, and the webcam capture that feeds a video character. Kept with
   the other picker styles rather than in the Builder, because the choice tiles reuse the same
   selected/hover language as .choice above and the two should not drift apart. */

.kind-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 12px; }
.kind-option {
  position: relative;
  border: 2px solid var(--line); border-radius: 12px; padding: 14px 16px; cursor: pointer;
  background: #fff; transition: border-color .12s;
}
.kind-option:hover { border-color: var(--khaki); }
.kind-option.sel { border-color: var(--accent); background: var(--accent-soft); }
.kind-option strong { display: block; margin-bottom: 4px; }
.kind-option p { margin: 0; }
.kind-option .pill.warn {
  display: inline-block; margin-top: 8px;
  font-size: .68rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  background: #8a5a00; color: #fff; padding: 2px 7px; border-radius: 4px;
}

/* The requirements the video vendor imposes on a source photo. A list rather than a paragraph
   because each line is a separate thing to check against your own reflection. */
.guide-list { margin: 8px 0 12px; padding-left: 20px; font-size: .86rem; color: var(--muted); }
.guide-list li { margin: 3px 0; }

.webcam-host { margin: 12px 0; }
.webcam-frame {
  position: relative; display: inline-block; max-width: 100%;
  background: #14171b; border-radius: 10px; overflow: hidden; line-height: 0;
}
.webcam-preview {
  display: block; max-width: 100%; max-height: 340px;
  /* Mirrored so posing feels natural. The saved photo is NOT mirrored - see
     shared/webcam-portrait.js, which draws the capture unflipped on purpose. */
  transform: scaleX(-1);
}
/* The 2:3 region that will survive the crop, drawn over the landscape preview so the framing
   is visible before the photo is taken rather than after. */
.webcam-guide {
  position: absolute; top: 0; bottom: 0; left: 50%; transform: translateX(-50%);
  aspect-ratio: 2 / 3; border: 2px dashed rgba(255,255,255,.85);
  box-shadow: 0 0 0 9999px rgba(0,0,0,.35); pointer-events: none;
}
.webcam-guide-label {
  position: absolute; top: 4px; left: 6px;
  font-size: .68rem; font-weight: 700; color: rgba(255,255,255,.9);
  font-family: ui-monospace, Consolas, monospace;
}

/* The conversation stage for a video character. The 3D path's .player-slot is locked to 16:9
   for the Unity canvas; a video character is delivered at 2:3, so this variant carries its own
   aspect and lets the shared height sync treat it the same way. */
.player-slot.video-portrait {
  aspect-ratio: 2 / 3;
  height: min(calc(100vh - 260px), 620px);
  width: auto; max-width: 100%;
  margin: 0 auto; align-self: start;
  background: #14171b; border-radius: 12px; overflow: hidden; position: relative;
}
.player-slot.video-portrait .avatar-video {
  width: 100%; height: 100%; display: block; object-fit: contain;
}
@media (max-width: 1024px) {
  .player-slot.video-portrait { height: min(60vh, 480px); }
}

/* ---- Feedback -------------------------------------------------------------------------- */

.feedback-btn { margin-left: 4px; }

/* Centred over the page rather than docked to a corner. Feedback is a deliberate act - the
   person has stopped to write something - so it gets focus rather than sitting in the periphery
   competing with the thing they are commenting on. */
.feedback-panel {
  position: fixed; z-index: 100;
  top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: min(560px, 92vw); max-height: 88vh; overflow-y: auto;
  background: #fff; border: 1px solid var(--line); border-radius: 14px;
  padding: 20px 22px; box-shadow: 0 18px 50px rgba(0,0,0,.28);
}
.feedback-panel h3 { margin: 0 0 6px; }
.feedback-panel textarea { width: 100%; resize: vertical; }
.feedback-panel .btn-row { margin-top: 18px; }

@media (max-width: 640px) {
  .feedback-panel { width: 94vw; padding: 16px; }
  /* The two optional fields stack rather than squeezing to unusable widths. */
  .feedback-panel .row { flex-direction: column; }
}

/* Video-character stills in the picker grid. The 3D roster's .portrait is an 84px circle, which
   is right for a square headshot and wrong for a 2:3 source - a circle cut from a portrait takes
   the top of the head and the chin. These keep their own aspect and show the whole frame. */
.choice .portrait-tall {
  width: 96px; aspect-ratio: 2 / 3; height: auto;
  object-fit: cover; object-position: center top;
  border-radius: 8px; display: block; margin: 0 auto 8px;
  background: var(--line);
}

.webcam-shot { margin-top: 12px; }
.webcam-shot img {
  width: 168px; aspect-ratio: 2 / 3; object-fit: cover;
  border-radius: 10px; border: 1px solid var(--line); display: block;
}
.webcam-shot p { margin: 6px 0 0; }
