/* ============================================================
   Prisma: "neighborhood cinema, prism-souled".
   warm charcoal + amber; a projection light behind everything,
   a thin spectral line used as the brand's signature; ticket
   furniture for codes and lobby steps.
   Fonts are self-hosted (web/static/fonts, OFL) so the CSP
   never has to bend: no external dependencies, pure CSS.
   House rules (what this sheet must never grow back) live in
   tests/test_design_hygiene.py: no gradient-painted text, no
   frosted-glass panels, no grain over the background, no italic
   serif accents, and spacing only on the scale documented in :root.
   ============================================================ */

/* ---- self-hosted webfonts (see web/static/fonts/LICENSES.txt) ---- */
@font-face {
  font-family: "Fraunces";
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url("/static/fonts/fraunces-var-r.woff2?v=1") format("woff2");
}
@font-face {
  font-family: "Space Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/static/fonts/spacemono-400r.woff2?v=1") format("woff2");
}
@font-face {
  font-family: "Space Mono";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/static/fonts/spacemono-700r.woff2?v=1") format("woff2");
}
@font-face {
  font-family: "Space Mono";
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("/static/fonts/spacemono-400i.woff2?v=1") format("woff2");
}

:root {
  --bg: #141110;
  --bg-deep: #0d0b09;
  --panel: #1d1915;
  --panel-2: #292219;
  --border: #383026;
  --border-soft: #2a241d;
  --text: #f0e9dd;
  --text-faint: #8a8072;
  --muted: #a89d8b;
  --accent: #f2a33c;
  --accent-ink: #231402;
  --danger: #ff7a64;
  --ok: #9ccb74;
  --gold: #e8c15a;
  /* halfway between ok and gold: the door ramp steps green -> lime -> gold -> red */
  --lime: #b8d168;

  /* the prism's output: the six colors the white beam splits into */
  --spec-red: #ff7a64;
  --spec-amber: #f2a33c;
  --spec-gold: #e8c15a;
  --spec-green: #9ccb74;
  --spec-blue: #7db8d8;
  --spec-violet: #b89ae0;
  --spectral: linear-gradient(90deg,
    var(--spec-red), var(--spec-amber), var(--spec-gold),
    var(--spec-green), var(--spec-blue), var(--spec-violet));

  --radius: 12px;
  --radius-sm: 8px;

  /* The spacing scale: every padding, margin and gap in this sheet is one of
     these steps, in rem at the default root size — a 4px grid, with 2px
     reserved for the hairline boxes that would collapse at 4px:
       0 · 2 · 4 · 8 · 12 · 16 · 20 · 24 · 32 · 40 · 48 · 64 · 80 px
     A value outside it is not an optical choice, it is drift, and the build
     fails on it (tests/test_design_hygiene.py). Steps are written as literals
     on purpose: the grid is the contract, not a name per number. */

  --font-display: "Fraunces", "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
  --font-body: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: "Space Mono", ui-monospace, "SF Mono", "Cascadia Code", Menlo, Consolas, monospace;
}

* { box-sizing: border-box; }

html { color-scheme: dark; }

body {
  margin: 0;
  min-height: 100vh;
  /* the projection light: warm glow above the screen, hall darkness below */
  background:
    radial-gradient(130% 80% at 50% -12%, rgba(242, 163, 60, 0.055), transparent 55%),
    radial-gradient(95% 65% at 50% 112%, rgba(8, 6, 4, 0.85), transparent 60%),
    var(--bg);
  background-attachment: fixed;
  color: var(--text);
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.45;
}

::selection { background: rgba(242, 163, 60, 0.35); }

/* thin warm scrollbar */
* { scrollbar-width: thin; scrollbar-color: #4a4033 transparent; }
*::-webkit-scrollbar { width: 9px; height: 9px; }
*::-webkit-scrollbar-thumb { background: #4a4033; border-radius: 8px; }
*::-webkit-scrollbar-thumb:hover { background: var(--text-faint); }
*::-webkit-scrollbar-track { background: transparent; }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}

.hidden { display: none !important; }
.muted { color: var(--muted); font-size: 0.85rem; }
.error { color: var(--danger); font-size: 0.9rem; min-height: 1.2em; }
/* the lobby error is the card's last child: it reserves nothing when empty */
#lobby-error:empty { display: none; }

/* lobby: the custom-room-code row always reserves its own height (only a
   thin margin extra), so opening it never shifts the join row below */
#custom-room-wrap { visibility: hidden; margin: 0.25rem 0; }
#custom-room-wrap.open { visibility: visible; }

/* ---------------------------------------------------------- topbar */
.topbar {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 1rem 1.5rem;
  border-bottom: 1px solid var(--border);
}
/* the brand's signature: a thin spectral line under the topbar hairline */
.topbar::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: calc(100% - 1px);
  height: 2px;
  background: var(--spectral);
  opacity: 0.4;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 22%, #000 78%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 22%, #000 78%, transparent);
  pointer-events: none;
}
.brand {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  text-decoration: none;
  color: var(--text);
  cursor: pointer;
  border-radius: var(--radius-sm);
  padding: 0.125rem 0.5rem;
  margin-left: -0.5rem; /* the padding doesn't uncenter the logo */
}
.brand:hover h1 { color: var(--accent); }
.brand svg { width: 30px; height: 22px; flex: none; }
/* light that leaves the prism: hidden narrow until you hover, then it fans out */
.brand .spec-ray {
  stroke-dasharray: 26;
  stroke-dashoffset: 26;
  opacity: 0.9;
  transition: stroke-dashoffset 0.5s cubic-bezier(0.2, 0.8, 0.2, 1), transform 0.4s ease-out;
  transform-box: fill-box;
  transform-origin: 0% 100%;
}
.brand .incident { opacity: 0.55; transition: opacity 0.3s ease-out; }
.brand:hover .incident { opacity: 0.95; }
.brand:hover .spec-ray { stroke-dashoffset: 0; }
/* the rays fan out one after another, red leading */
.brand .spec .spec-ray { transition-delay: 0s; }
.brand .spec .spec-ray:nth-child(2) { transition-delay: 0.04s; }
.brand .spec .spec-ray:nth-child(3) { transition-delay: 0.08s; }
.brand .spec .spec-ray:nth-child(4) { transition-delay: 0.12s; }
.brand .spec .spec-ray:nth-child(5) { transition-delay: 0.16s; }
.brand .spec .spec-ray:nth-child(6) { transition-delay: 0.2s; }
.topbar h1 {
  margin: 0;
  font-family: var(--font-display);
  font-size: 1.5rem;
  font-weight: 600;
  letter-spacing: 0.02em;
}
/* the beta release channel paints this pill next to the wordmark (app.js
   reveals it from /api/health; hidden by default) */
.beta-badge {
  font-family: var(--font-mono);
  font-size: 0.58rem;
  font-weight: 700;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--accent);
  background: rgba(242, 163, 60, 0.08);
  border: 1px solid rgba(242, 163, 60, 0.45);
  border-radius: 999px;
  padding: 0.125rem 0.25rem 0.125rem 0.5rem; /* padding-left absorbs the letter-spacing tail */
  pointer-events: none;
  user-select: none;
}
/* a Discord link past the trust ceiling: the refresh button is the door
   out of the dead end, so it quietly begs to be clicked */
.needs-attention {
  border-color: var(--accent);
  animation: attention-pulse 1.9s ease-out infinite;
}
@keyframes attention-pulse {
  0% { box-shadow: 0 0 0 0 rgba(242, 163, 60, 0.45); }
  70% { box-shadow: 0 0 0 8px rgba(242, 163, 60, 0); }
  100% { box-shadow: 0 0 0 0 rgba(242, 163, 60, 0); }
}

/* The topbar's status slot: one address for the two things the room says
   about itself — the link is dying, or the door is about to open to the
   world. Both speak from here, never at the same time, and they speak from
   the MIDDLE of the band between the brand and the room code: the line is
   the subject, the shortcuts ride beside it, all of it on one axis. */
.status-slot {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  min-width: 0;
}
/* A slot with nothing to say is not in the conversation: it must not hold
   any of the topbar's width when nobody is speaking. */
.status-slot:not(:has(> :not(.hidden))) { display: none; }
/* connection status: quiet inline text with a pulsing amber dot */
.conn-status {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.85rem;
  color: var(--muted);
}
.conn-status svg {
  color: var(--accent);
  filter: drop-shadow(0 0 6px rgba(242, 163, 60, 0.55));
  animation: dot-pulse 1.6s ease-in-out infinite;
}
@keyframes dot-pulse { 50% { opacity: 0.45; } }

/* the door's notice: same voice as the connection line, calm green dot (the
   door ramp's own color, and nothing is on fire). The two shortcuts wrap
   under the line rather than squeeze it when the band runs out of room. */
.announce-status {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 0.25rem 0.5rem;
  font-size: 0.85rem;
  color: var(--muted);
  min-width: 0;
}
.announce-status svg { color: var(--ok); }
.announce-status #announce-text { white-space: nowrap; }
/* the retry twin already brings its own margin: inside a gapped flex it
   would double the space */
.announce-status .conn-retry { margin-left: 0; }
/* Losing the room is the news; the countdown is not. While the connection
   pill is on screen the announce is gone (display:none also takes it out of
   the reader, so nothing competes to be spoken). */
.conn-status:not(.hidden) ~ .announce-status { display: none; }

/* room identity + exit, living in the global chrome (topbar right) */
.room-chrome {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-left: auto;
}
.room-chrome .room-code-label {
  font-family: var(--font-display);
  color: var(--muted);
  font-size: 0.95rem;
}
/* the room code wears the face of an ticket stub: punched notches on each end */
.room-chrome code, #invite-room-code, .public-rooms code {
  font-family: var(--font-mono);
  font-size: 1rem;
  letter-spacing: 0.16em;
  color: var(--accent);
  background: rgba(242, 163, 60, 0.09);
  border: 1px solid rgba(242, 163, 60, 0.25);
  padding: 0.25rem 1rem;
  border-radius: 7px;
  cursor: pointer;
  user-select: text;
  -webkit-user-select: text;
  /* fixed line box: the height must not breathe with its content — the
     mask dots come from a fallback font whose metrics differ from the
     code's own letters */
  line-height: 1.4;
  transition: border-color 0.15s ease-out, background-color 0.15s ease-out,
    color 0.15s ease-out;
  /* perforated stub: transparent half-circle notches at the vertical center on both edges */
  --punch: 6px;
  -webkit-mask-image:
    radial-gradient(circle var(--punch) at 0 50%, transparent 97%, #000 100%),
    radial-gradient(circle var(--punch) at 100% 50%, transparent 97%, #000 100%);
  -webkit-mask-composite: source-in;
  mask-image:
    radial-gradient(circle var(--punch) at 0 50%, transparent 97%, #000 100%),
    radial-gradient(circle var(--punch) at 100% 50%, transparent 97%, #000 100%);
  mask-composite: intersect;
}
#invite-room-code { font-size: 1.05rem; cursor: default; padding: 0.125rem 0.75rem; }
/* the slot reserves the width of a six-letter code as EMPTY space: the pill
   itself only ever measures its own length (3-6 chars; a longer one pushes
   the reservation out), so toggling masked (5 dots) or visible never shifts
   the label or the buttons. The pill hugs the leave button: the spare room
   stays between it and the code-visibility button. */
.room-chrome .code-slot {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 1rem;
  min-width: calc(6ch + 0.96em + 2rem + 2px);
  text-align: right;
}
/* masked pill: no code, no length — five dots at their natural size */
.room-chrome code.masked {
  user-select: none;
  -webkit-user-select: none;
}
/* the eye that masks/unmasks the code, sitting at its left */
.room-chrome .code-vis {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.25rem 0.5rem;
  color: var(--muted);
}
.room-chrome .code-vis svg { display: block; }
.room-chrome .code-vis[aria-pressed="true"] {
  color: var(--accent);
  border-color: rgba(242, 163, 60, 0.35);
}
.room-chrome code.copied, .public-rooms code.copied {
  border-color: rgba(156, 203, 116, 0.55);
  background: rgba(156, 203, 116, 0.12);
  color: var(--ok);
  /* the ticket punches: quick inward squeeze, then settle back */
  animation: ticket-punch 0.28s ease-out;
}
@keyframes ticket-punch {
  40% { transform: scale(0.9) rotate(-0.6deg); }
  100% { transform: scale(1) rotate(0deg); }
}
.room-chrome button { padding: 0.25rem 0.75rem; font-size: 0.8rem; }

/* ---------------------------------------------------------- buttons */
button {
  background: transparent;
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.5rem 1rem;
  cursor: pointer;
  font-size: 0.9rem;
  font-family: inherit;
  transition: border-color 0.15s ease-out, background-color 0.15s ease-out,
    color 0.15s ease-out, transform 0.05s ease-out;
}
button:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
button:active:not(:disabled) { transform: translateY(1px); }
button:disabled { opacity: 0.4; cursor: not-allowed; }

button.primary {
  background-color: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
  font-weight: 600;
  /* marquee base: the six spectral colors as a thin edge above the sole */
  background-image: linear-gradient(90deg,
    var(--spec-red), var(--spec-amber), var(--spec-gold),
    var(--spec-green), var(--spec-blue), var(--spec-violet));
  background-repeat: no-repeat;
  background-position: bottom 1.5px center;
  background-size: calc(100% - 12px) 1.5px;
}
button.primary:hover:not(:disabled) {
  background-color: #ffb95c;
  border-color: #ffb95c;
  color: var(--accent-ink);
  box-shadow: 0 0 18px rgba(242, 163, 60, 0.35);
}
button.big { font-size: 1.1rem; padding: 0.75rem 1.5rem; border-radius: var(--radius); }

/* ---------------------------------------------------------- inputs */
input, select {
  background: var(--panel-2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.5rem 0.75rem;
  font-size: 0.95rem;
  font-family: inherit;
  transition: border-color 0.15s ease-out;
}
input:focus, select:focus { outline: none; border-color: var(--accent); }
input::placeholder { color: var(--text-faint); }

select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 2rem;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23a89d8b' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.65rem center;
}

label {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--muted);
}

.card {
  background: var(--panel);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  padding: 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.03) inset, 0 8px 24px rgba(0, 0, 0, 0.25);
}
.card h2, .card h3 {
  margin: 0;
  font-family: var(--font-display);
  font-size: 1.08rem;
  font-weight: 600;
  letter-spacing: 0.01em;
}
.row { display: flex; gap: 0.5rem; }
.row > input { flex: 1; min-width: 0; }
#join-room-btn { white-space: nowrap; }

/* editorial step number of the lobby cards: a little ticket-stub number */
.step {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--accent);
  vertical-align: 0.2em;
  margin-right: 0.5rem;
  letter-spacing: 0.08em;
  border: 1px dashed rgba(242, 163, 60, 0.5);
  border-radius: 5px;
  padding: 0 0.25rem;
  background: rgba(242, 163, 60, 0.07);
}

/* ---------------------------------------------------------- lobby marquee */
/* The lobby/invite heading: a serif tagline over a spectral line, reading
   like the painted marquee above a neighborhood cinema's door. Roman, not
   italic: the sign is painted, not accented. */
.marquee {
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.25rem;
  padding: 0 0.5rem;
}
.marquee-title {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(1.5rem, 4.5vw, 1.9rem);
  letter-spacing: 0.01em;
  color: var(--text);
  /* always exactly one line: anything that doesn't fit fades into an
     ellipsis instead of wrapping (the txt cap in app.js is the first guard) */
  /* the tagline is allowed to escape the lobby's 520px column and use the
     side margins — the marquee is a page-width banner, not a card row.
     The runway: up to 90vw (never overflows the viewport, centered so it
     spills symmetrically) capped at 56rem (~896px) so an ultrawide page
     doesn't stretch one sentence across the whole screen. Measured against
     this runway at the largest font size, MARQUEE_CAP in app.js documents
     the fit: 60 chars fill ~832 of the 896px; below ~1000px vw the
     ellipsis fades overlong lines as the graceful fallback. */
  max-width: min(90vw, 56rem);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.spectral-rule {
  width: min(240px, 70%);
  height: 2px;
  border-radius: 2px;
  background: var(--spectral);
  opacity: 0.55;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 20%, #000 80%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 20%, #000 80%, transparent);
}
.marquee-sub {
  margin: 0;
  color: var(--muted);
  font-size: 0.9rem;
}

/* ticket cards: dashed perforation along the left edge of lobby steps */
.card.ticket {
  position: relative;
  padding-left: 1.5rem;
}
.card.ticket::before {
  content: "";
  position: absolute;
  left: 0.75rem;
  top: 12px;
  bottom: 12px;
  width: 0;
  border-left: 1.5px dashed rgba(242, 163, 60, 0.35);
}

/* ---------------------------------------------------------- lobby */
.lobby {
  max-width: 520px;
  margin: clamp(2rem, 8vh, 4rem) auto;
  padding: 0 1rem;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

/* two-column lobby: the public index sits LEFT of the main cards column.
   The main column's center is never left of the page's half: when the
   index is visible it pushes it slightly right (+28px). The pair is
   anchored to the center (no movement while resizing); only when the
   window is too narrow for both does the index make the big move and
   stack under the cards. The marquee lives outside this row, so it stays
   centered at the page's half like always. */
.lobby-columns {
  --aside-w: 400px;
  --aside-gap: 3rem;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  gap: var(--aside-gap);
  transform: translateX(calc((var(--aside-w) + var(--aside-gap)) / -2 + 56px));
}
/* nothing to sit on the left (index hidden: discord not linked, first
   paint before the first poll, ...): the main column returns to center */
.lobby-columns:has(> .public-aside.hidden) { transform: none; }
.lobby-main {
  flex: none;
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}
/* taller than the 01+02 stack and CONSTANT regardless of how many rooms
   there are: rows scroll INSIDE the card instead of stretching the page */
.public-aside {
  order: 0;
  flex: none;
  width: var(--aside-w);
  display: flex;
  flex-direction: column;
  height: 34rem;
}
/* public rooms index: same row recipe as the queue (.qitem) — avatar,
   who/what in the middle, occupancy chip + code + join on the right */
.public-rooms {
  order: 1;
  list-style: none;
  margin: 0.5rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}
/* every row is exactly three lines (name / age+state / movie) at a fixed
   height — idle rooms drop the movie line and flex-center the other two,
   so no room's content can make its row taller or shorter than another's */
.public-room {
  height: 3.65rem;
  padding-top: 0.25rem; /* three lines: tighter vertical padding */
  padding-bottom: 0.25rem;
  overflow: hidden;
}
/* bigger face than the roster's 22px default: the card has three lines of
   text next to it, the face is the anchor of the row */
.public-room .u-avatar.p-avatar { width: 32px; height: 32px; }
.public-room .qsub { flex-wrap: nowrap; line-height: 1.25; }
/* .qtitle is inline by nature (the queue blockifies it inside its flex
   wrap): here it needs to be a block or its ellipsis never engages */
.public-room .qtitle { display: block; line-height: 1.25; }
.public-room .public-age { flex: none; color: var(--text-faint); }
#public-rooms-empty { margin: 0.5rem 0 0; }
/* the movie's name gets its own line — absent for idle rooms, whose two
   remaining lines center vertically inside the fixed-height row */
.public-room .public-title {
  display: block;
  font-size: 0.74rem;
  line-height: 1.25;
  color: var(--gold);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.public-side {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  flex: none;
}
/* the table's code is a stub too, just a size down */
.public-rooms code { font-size: 0.85rem; padding: 0.125rem 0.75rem; letter-spacing: 0.1em; }
/* below ~1280 the pair doesn't fit side by side: the index drops under
   the cards — same constant height, the scroll stays internal */
@media (max-width: 1279px) {
  .lobby-columns { flex-direction: column; align-items: center; transform: none; }
  .public-aside { order: 2; width: 100%; max-width: 520px; }
  .public-aside .public-rooms { flex: none; max-height: 24rem; }
}

.name-row { display: flex; align-items: center; gap: 0.5rem; }
.name-row #name-input,
.name-row #invite-name-input { flex: 1; min-width: 0; }
/* Discord identity (auth mode "discord"): replaces the name/avatar inputs
   with either the link button (unlinked) or the read-only account card. */
.discord-block { display: flex; flex-direction: column; gap: 0.5rem; }
.discord-btn {
  width: 100%;
  background-color: #5865f2;
  border-color: #5865f2;
  color: #fff;
}
.discord-btn:hover:not(:disabled) { border-color: #7983f5; color: #fff; }
.discord-identity {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.discord-avatar {
  width: 42px; height: 42px;
  flex: none;
  border-radius: 50%;
  object-fit: cover;
  border: 1px solid var(--border);
}
.discord-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.discord-unlink {
  flex: none;
  border: none;
  background: none;
  padding: 0;
  font-size: 0.8rem;
  color: var(--muted);
  text-decoration: underline;
  cursor: pointer;
}
.discord-unlink:hover:not(:disabled) { color: var(--danger); border-color: transparent; }
.discord-refresh { color: var(--muted); }
.avatar-field { display: flex; align-items: center; gap: 0.5rem; flex: none; }
.avatar-picker {
  width: 42px; height: 42px;
  flex: none; padding: 0;
  border-radius: 50%;
  overflow: hidden;
  border: 1px solid var(--border);
  background: var(--panel-2);
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--muted);
}
.avatar-picker:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.avatar-picker .avatar-img { width: 100%; height: 100%; object-fit: cover; display: block; }
.avatar-picker .avatar-ph { display: inline-flex; align-items: center; justify-content: center; }
.square-btn {
  width: 36px; height: 36px;
  flex: none; padding: 0;
  border-radius: var(--radius-sm);
  display: inline-flex; align-items: center; justify-content: center;
}
.avatar-reset-btn:hover:not(:disabled) { border-color: var(--danger); color: var(--danger); }

.progress { display: flex; align-items: center; gap: 0.5rem; }
.progress .bar {
  flex: 1;
  height: 6px;
  background: var(--panel-2);
  border-radius: 4px;
  overflow: hidden;
}
.progress .fill {
  height: 100%;
  width: 0%;
  background: var(--accent);
  transition: width 0.2s;
}
#upload-label { font-variant-numeric: tabular-nums; font-size: 0.82rem; color: var(--muted); }

/* ---------------------------------------------------------- room */
.room { padding: 1rem 1.25rem 1.5rem; display: flex; flex-direction: column; gap: 0.75rem; }

/* entering the room: the house lights come down and the screen fades up */
.room.entering { animation: lights-down 0.5s ease-out both; }
@keyframes lights-down {
  from { opacity: 0; transform: translateY(10px) scale(0.996); }
  to { opacity: 1; transform: none; }
}

/* Two-lane grid: the side column spans ONLY the video + controls rows, so
   the roster and the chat live beside the screen and never stretch down
   alongside the whole queue. Inside that lane the chat keeps its own
   constant height (see .chat): the column is the ceiling, not the size. */
.room-body {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 320px;
  gap: 1rem;
}
.player-col { display: contents; }
.player-col > * { grid-column: 1; }
.side-col {
  grid-column: 2;
  grid-row: 1 / span 2;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

/* ---------------------------------------------------------- video */
.video-wrap {
  position: relative;
  background: #000;
  border-radius: var(--radius);
  overflow: hidden;
  border: 1px solid rgba(242, 163, 60, 0.14);
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.45);
}
.video-wrap video { width: 100%; display: block; aspect-ratio: 16 / 9; }

.overlay {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  /* a dark scrim, not frosted glass: what is behind must simply disappear,
     and a blur over a playing video costs the GPU on every frame */
  background: rgba(8, 6, 4, 0.92);
  text-align: center;
  padding: 1rem;
}
/* intertitle voice for the transcode-frontier message */
#buffering-text {
  font-family: var(--font-mono);
  font-style: italic;
  font-size: 0.92rem;
  letter-spacing: 0.03em;
  margin: 0;
  color: var(--text);
}

.spinner {
  width: 38px; height: 38px;
  border: 4px solid var(--panel-2);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.9s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* the connection guard's private notice: a pill at the bottom of the
   stage (inside .video-wrap so it rides into fullscreen), never blocks
   the controls, and dismisses itself (the JS owns the timer) */
.conn-toast {
  position: absolute;
  left: 50%;
  bottom: 18px;
  transform: translateX(-50%);
  z-index: 6;
  max-width: min(80%, 480px);
  padding: 0.5rem 1rem;
  border: 1px solid rgba(242, 163, 60, 0.45); /* the amber of struggling seats */
  border-radius: 999px;
  background: #0d0b09;
  color: var(--text);
  font-family: var(--font-mono);
  font-size: 0.78rem;
  letter-spacing: 0.02em;
  text-align: center;
  pointer-events: none;
  animation: toast-in 0.25s ease-out;
}
@keyframes toast-in {
  from { opacity: 0; transform: translate(-50%, 8px); }
  to { opacity: 1; transform: translate(-50%, 0); }
}

/* the "announce now" shortcut is a `.conn-retry` twin in the topbar status
   slot (see .announce-status): same address as the connection line. */

/* ---------------------------------------------------------- controls */


/* skip + fullscreen buttons, same base styles as play */
#back-btn, #fwd-btn, #fullscreen-btn {
  border: 1px solid var(--border);
  background: transparent;
  color: var(--muted);
  border-radius: var(--radius-sm);
  padding: 0.25rem 0.5rem;
  font-size: 0.85rem;
}
#back-btn:hover:not(:disabled), #fwd-btn:hover:not(:disabled),
#fullscreen-btn:hover:not(:disabled) {
  border-color: var(--accent);
  color: var(--accent);
}
#back-btn:disabled, #fwd-btn:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}

/* slider with preview: the input lives in a wrapper so the bubble can be positioned */
.seek-wrap { position: relative; flex: 1; display: flex; align-items: center; }
.seek-preview {
  position: absolute;
  bottom: calc(100% + 10px);
  transform: translateX(-50%);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 3px;
  box-shadow: 0 12px 28px rgba(0,0,0,.55);
  pointer-events: none;
  z-index: 40;
}
.seek-preview canvas { display: block; border-radius: 4px; width: 192px; height: 108px; }
.seek-preview.placeholder canvas {
  background: #17130f;
}
.seek-preview-time {
  position: absolute;
  left: 50%; bottom: -1.35rem;
  transform: translateX(-50%);
  font-family: var(--font-mono);
  font-size: .74rem;
  color: var(--text);
  background: rgba(13,11,9,.92);
  border-radius: 6px;
  padding: 0.125rem 0.5rem;
}

/* chat header: title + notification mute */
.chat-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 0.5rem; margin-bottom: 0.5rem;
}
.chat-head h3 { margin: 0; }
#chat-mute-btn {
  border: 1px solid transparent;
  background: transparent;
  font-size: .95rem;
  padding: 0.125rem 0.5rem;
  border-radius: var(--radius-sm);
  color: var(--muted);
}
#chat-mute-btn:hover { border-color: var(--border); color: var(--text); }
#chat-mute-btn.muted-on {
  border-color: rgba(255,122,100,.5);
  color: var(--danger);
}

.controls {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  background: var(--panel);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  padding: 0.5rem 0.75rem;
}
.controls .time {
  font-variant-numeric: tabular-nums;
  font-family: var(--font-mono);
  font-size: 0.8rem;
  color: var(--muted);
  min-width: 3.4ch;
  text-align: center;
}
#play-btn {
  width: 2.7rem;
  height: 2.7rem;
  padding: 0;
  border-radius: 50%;
  flex: none;
  font-size: 0.95rem;
  color: var(--accent);
  border-color: rgba(242, 163, 60, 0.45);
}
#play-btn:hover:not(:disabled) { background: rgba(242, 163, 60, 0.12); color: var(--accent); }
#quality-select { font-size: 0.82rem; padding: 0.25rem 2rem 0.25rem 0.5rem; }

/* progress bar: own track with amber fill via --pct */
#seek-bar {
  flex: 1;
  appearance: none;
  -webkit-appearance: none;
  /* TALL input (generous click zone) with a thin centered visual track */
  height: 26px;
  border: none;
  border-radius: 6px;
  padding: 0;
  background: linear-gradient(90deg,
    var(--accent) 0 calc(var(--pct, 0) * 1%),
    #3b332a calc(var(--pct, 0) * 1%) 100%) no-repeat center;
  background-size: 100% 6px;
  cursor: pointer;
  min-width: 80px;
}
#seek-bar:focus { outline: none; }
#seek-bar::-webkit-slider-thumb {
  appearance: none;
  -webkit-appearance: none;
  width: 16px; height: 16px;
  border-radius: 50%;
  background: var(--text);
  border: 3px solid var(--accent);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
  transition: transform 0.1s ease-out;
}
#seek-bar::-webkit-slider-thumb:hover { transform: scale(1.15); }
#seek-bar::-moz-range-thumb {
  width: 16px; height: 16px;
  border-radius: 50%;
  background: var(--text);
  border: 3px solid var(--accent);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
}
#seek-bar:disabled { cursor: default; filter: saturate(0.4) brightness(0.8); }
#seek-bar:disabled::-webkit-slider-thumb { transform: none; }

/* volume: same amber track language, compact, purely local control */
#vol-bar {
  appearance: none;
  -webkit-appearance: none;
  width: 74px;
  height: 20px;
  border: none;
  border-radius: 6px;
  padding: 0;
  background: linear-gradient(90deg,
    var(--accent) 0 calc(var(--pct, 100) * 1%),
    #3b332a calc(var(--pct, 100) * 1%) 100%) no-repeat center;
  background-size: 100% 6px;
  cursor: pointer;
  min-width: 48px;
}
#vol-bar:focus { outline: none; }
#vol-bar::-webkit-slider-thumb {
  appearance: none;
  -webkit-appearance: none;
  width: 12px; height: 12px;
  border-radius: 50%;
  background: var(--text);
  border: 2px solid var(--accent);
}
#vol-bar::-moz-range-thumb {
  width: 12px; height: 12px;
  border-radius: 50%;
  background: var(--text);
  border: 2px solid var(--accent);
}
@media (max-width: 900px) {
  #vol-bar { display: none; } /* cramped phones: the mute button is enough */
  /* the controls row would overflow a phone: let it wrap, seek bar on its own line */
  .controls { flex-wrap: wrap; row-gap: 0.5rem; }
  .seek-wrap { order: 10; flex: 1 1 100%; }
  /* now-playing: give the title a full row instead of an ellipsis stub */
  .now-playing { flex-wrap: wrap; }
  .now-playing .np-meta { flex: 1 1 150px; }
}

/* ---------------------------------------------------------- participants */
/* The roster is the neighbour that could steal the chat's room: a big party
   scrolls inside its own card instead of pushing the fixed chat box away. */
.participants ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  max-height: 16rem;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.participants li {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem;
  background: var(--panel-2);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  font-size: 0.9rem;
  transition: border-color 0.15s ease-out;
  position: relative;
}
.participants li.me { border-color: rgba(242, 163, 60, 0.28); }
.participants li .pname-wrap {
  flex: 1;
  min-width: 0;
  position: relative;
  display: flex;
  align-items: center;
  overflow: hidden;
}
.participants li .pname {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: var(--font-display);
  font-size: 1.02rem;
  letter-spacing: 0.01em;
}
.participants li.me .pname { font-weight: 600; }
.participants li.disconnected { opacity: 0.55; }
/* playbill halo: each name borrows the same hue they get in chat */
.participants li .u-avatar.p-avatar {
  box-shadow: 0 0 0 2px hsl(var(--hue, 36) 55% 55% / 0.5);
  transition: box-shadow 0.15s ease-out;
}
.p-actions {
  position: absolute;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  gap: 0.25rem;
  align-items: center;
  background: linear-gradient(to right, transparent, var(--panel-2) 16px);
  padding-left: 16px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.14s ease-out;
  z-index: 1;
}
.participants li:hover .p-actions,
.participants li:focus-within .p-actions { opacity: 1; pointer-events: auto; }
.p-actions:empty { display: none; }
/* touch has no hover: keep the actions visible (compact) on coarse pointers */
@media (pointer: coarse) {
  .p-actions { opacity: 1; pointer-events: auto; }
}
.badge { font-family: var(--font-mono); font-size: 0.68rem; color: var(--muted); }
.crown { color: var(--gold); text-shadow: 0 0 8px rgba(232, 193, 90, 0.45); flex: none; }
.dot {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--ok);
  flex: none;
  box-shadow: 0 0 6px rgba(156, 203, 116, 0.5);
}
/* seat light states: green = in the room, yellow = watching the movie,
   pulsing orange = the room waits on their player,
   pulsing red = dropped (reconnect grace) */
.dot.viewing {
  background: var(--gold);
  box-shadow: 0 0 6px rgba(232, 193, 90, 0.5);
}
.dot.busy {
  background: var(--accent);
  box-shadow: 0 0 6px rgba(242, 163, 60, 0.55);
  animation: dot-pulse 1.6s ease-in-out infinite;
}
.dot.off {
  background: var(--danger);
  box-shadow: 0 0 6px rgba(255, 122, 100, 0.55);
  animation: dot-pulse 1.6s ease-in-out infinite;
}
.mini-spin {
  width: 12px; height: 12px; flex: none;
  border: 2px solid var(--panel);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.9s linear infinite;
}
.give-host { font-size: 0.72rem; padding: 0.125rem 0.5rem; border-radius: 6px; flex: none; }
.give-host.kick:hover:not(:disabled) { border-color: var(--danger); color: var(--danger); }
/* two-step make host / kick: the armed pair replaces the single button */
.two-step { display: inline-flex; gap: 0.25rem; flex: none; }
.ts-confirm.host {
  color: var(--accent); border-color: rgba(242, 163, 60, 0.6);
  background: rgba(242, 163, 60, 0.14);
}
.ts-confirm.host:hover:not(:disabled) { background: rgba(242, 163, 60, 0.24); color: var(--accent); }
.ts-confirm.kick {
  color: var(--danger); border-color: rgba(255, 122, 100, 0.6);
  background: rgba(255, 122, 100, 0.14);
}
.ts-confirm.kick:hover:not(:disabled) { background: rgba(255, 122, 100, 0.24); color: var(--danger); }

/* generic user avatar (participants / queue / chat) */
.u-avatar {
  width: 22px; height: 22px;
  border-radius: 50%;
  flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  overflow: hidden;
  background: var(--panel-2);
  border: 1px solid var(--border-soft);
  color: var(--muted);
  font-size: 10px;
  object-fit: cover;
}
.u-avatar.ph { background: var(--panel-2); }
.u-avatar.q-avatar { width: 18px; height: 18px; font-size: 9px; }
.u-avatar.chat-avatar { width: 20px; height: 20px; font-size: 10px; }
.u-avatar.p-avatar { width: 22px; height: 22px; }

/* connection signal bars (sprite #i-sig-1..5, #i-sig-q while measuring) */
.sig { display: inline-flex; opacity: 0.95; flex: none; }

/* ---------------------------------------------------------- the door */
/* four-position cycling join policy switch, inline with the Participants
   heading: a readout for everyone in the room, one click from an admin
   advances the position (locked and inert for regular members) */
.participants-head { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.door {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  flex: none;
}
.door-switch {
  position: relative;
  display: inline-flex;
  width: 88px;
  height: 27px;
  border-radius: 999px;
  background: var(--panel);
  border: 1px solid var(--border-soft);
  overflow: hidden;
  padding: 0;
}
.door-switch .thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: calc((100% - 4px) / 4);
  height: calc(100% - 4px);
  border-radius: 999px;
  /* the detents read as one ramp of openness: green (public) fades to
     red (closed) with no back-and-forth jumps */
  background: var(--ok);
  transform: translateX(calc(var(--i, 0) * 100%));
  transition: transform 0.18s ease-out, background-color 0.18s ease-out;
}
.door-switch[data-mode="public"] .thumb { background: var(--ok); }
.door-switch[data-mode="open"] .thumb { background: var(--lime); }
.door-switch[data-mode="invite"] .thumb { background: var(--gold); }
.door-switch[data-mode="closed"] .thumb { background: var(--danger); }
/* affordance dot per detent: hidden under the active thumb */
.door-switch .ticks {
  position: absolute;
  inset: 0;
  display: flex;
  z-index: 1;
  pointer-events: none;
}
.door-switch .ticks i {
  flex: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.door-switch .ticks i::after {
  content: "";
  display: block;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: rgba(240, 233, 221, 0.55);
}
.door-switch[data-mode="public"] .ticks i:nth-child(1)::after { opacity: 0; }
.door-switch[data-mode="open"] .ticks i:nth-child(2)::after { opacity: 0; }
.door-switch[data-mode="invite"] .ticks i:nth-child(3)::after { opacity: 0; }
.door-switch[data-mode="closed"] .ticks i:nth-child(4)::after { opacity: 0; }
.door-switch:hover:not(:disabled) { border-color: var(--accent); }
/* a member who can't move it reads it instead: no pointer invitation */
.door-switch:disabled { cursor: default; }
.door-label {
  font-size: 0.8rem;
  letter-spacing: 0.02em;
  color: var(--text); /* always readable: the thumb already carries the mood */
  min-width: 3rem;
  text-align: right;
}

/* door queue: newcomers waiting for a verdict */
.join-requests { margin: 0 0 0.5rem; }
.join-requests h4 {
  margin: 0 0 0.5rem;
  font-family: var(--font-display);
  font-size: 0.72rem;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--text);
}
.join-requests ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.join-requests li {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem;
  background: var(--panel-2);
  border: 1px dashed rgba(232, 193, 90, 0.35);
  border-radius: var(--radius-sm);
  font-size: 0.9rem;
}
.join-requests li .pname {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: var(--font-display);
  font-size: 1.02rem;
}
.join-requests li .u-avatar.p-avatar {
  box-shadow: 0 0 0 2px hsl(var(--hue, 36) 55% 55% / 0.5);
}
.req-actions { display: flex; gap: 0.25rem; flex: none; }
.req-actions button { font-size: 0.72rem; padding: 0.125rem 0.5rem; border-radius: 6px; }
.req-actions .req-accept:hover:not(:disabled) { border-color: var(--ok); color: var(--ok); }
.req-actions .req-reject:hover:not(:disabled) { border-color: var(--danger); color: var(--danger); }

/* door chat: messages from someone still waiting at the door — delivered to
   admins only (plus the sender's own echo), so they carry a visible marker */
.chat-chip {
  font-size: 0.68rem;
  line-height: 1;
  color: var(--gold);
  border: 1px solid rgba(232, 193, 90, 0.4);
  border-radius: 999px;
  padding: 0.125rem 0.5rem;
  flex: none;
}
.msg.door-msg { opacity: 0.92; }

/* ---------------------------------------------------------- chat */
/* The chat is the tail of the side column: it fills whatever the roster
   left, so its bottom edge lands exactly on the bottom of the controls bar
   (the column spans only those two grid rows). The flex-basis is 0 — not
   `auto` — so a long conversation can never stretch the column: the
   messages scroll INSIDE the box and the layout stays where it was. */
.chat { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.chat-messages {
  flex: 1 1 0;
  min-height: 0;
  overflow-y: auto;
  /* the bar is furniture, not a surprise: the gutter is reserved (so the
     text never sidesteps when the first overflow arrives) and the track is
     drawn, so what scrolls is legible at a glance */
  scrollbar-gutter: stable;
  scrollbar-color: #6a5b45 rgba(0, 0, 0, 0.28);
  /* scrolling inside must never chain into page rubber-banding */
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  font-size: 0.88rem;
  padding-right: 0.25rem;
}
.chat-messages::-webkit-scrollbar-track {
  background: rgba(0, 0, 0, 0.28);
  border-radius: 8px;
}
.chat-messages::-webkit-scrollbar-thumb {
  background: #6a5b45;
  border-radius: 8px;
}
.chat-messages::-webkit-scrollbar-thumb:hover { background: var(--text-faint); }
.chat-messages .msg { padding-left: 0.25rem; border-left: 2px solid transparent; }
.chat-messages .msg:hover { border-left-color: hsl(var(--hue, 36) 60% 45% / 0.6); }
.chat-who { display: inline-flex; align-items: center; gap: 0.5rem; }
.chat-messages .msg .who {
  color: hsl(var(--hue, 36) 68% 68%);
  font-weight: 600;
}
.chat-messages .msg .when {
  color: var(--text-faint);
  font-family: var(--font-mono);
  font-size: 0.7rem;
  margin-left: 0.5rem;
}
.chat-messages .msg > div:last-child {
  margin-top: 0;
  /* caption above picture: both live in one quiet column */
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.25rem;
}
/* a picture inside the chat: a small print of the real thing, click to
   open the zoom viewer */
.chat-img {
  display: block;
  max-width: min(240px, 100%);
  max-height: 180px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--panel-2);
  cursor: zoom-in;
}
.chat-img:hover { border-color: var(--accent); }
/* the picture on deck, waiting above the composer */
.chat-attach {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem;
  border: 1px dashed var(--border);
  border-radius: var(--radius-sm);
}
#chat-attach-thumb {
  width: 44px;
  height: 44px;
  flex: none;
  object-fit: cover;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--panel-2);
  cursor: zoom-in;
}
#chat-attach-label {
  flex: 1;
  min-width: 0;
  font-size: 0.78rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.chat-messages .sys {
  color: var(--muted);
  font-style: italic;
  font-size: 0.8rem;
  padding: 0.125rem 0 0.125rem 0.25rem;
}

/* ---------------------------------------------------------- responsive */
@media (max-width: 900px) {
  /* stacked: back to a plain column, the chat gets its own height back.
     dvh keeps the layout sane when the soft keyboard eats the viewport */
  .room-body { display: flex; flex-direction: column; }
  .player-col { display: flex; flex-direction: column; gap: 0.5rem; }
  .player-col > * { grid-column: auto; }
  .side-col { width: 100%; grid-column: auto; grid-row: auto; min-height: 320px; }
  /* stacked: no video beside the column to end level with, so the chat gets
     its own box again — capped by the screen, scrolling inside it */
  .chat-messages { flex: 1 1 20rem; max-height: min(20rem, 45dvh); }
  .room-code-label { display: none; }   /* tight topbar: the pill speaks for itself */
  /* the status slot gets a line of its own: a countdown that has to squeeze
     between the code and "salir" is a countdown nobody reads */
  .topbar { flex-wrap: wrap; row-gap: 0.5rem; }
  .status-slot { order: 5; flex-basis: 100%; }
}

/* fingers deserve real targets: ~24px icon buttons grow to 40px+ */
@media (pointer: coarse) {
  .qbtns button,
  .np-btns button,
  .give-host {
    min-width: 40px;
    min-height: 40px;
    font-size: 0.9rem;
  }
  .controls button { min-width: 40px; min-height: 40px; }
  .catalog-menu { overscroll-behavior: contain; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ============================================================
   Playback queue
   ============================================================ */

.queue-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  flex-wrap: wrap;
}
.qcount { color: var(--muted); font-size: 0.8rem; font-family: var(--font-mono); }
#on-end-wrap {
  display: flex; align-items: center; gap: 0.5rem;
  font-size: 0.78rem; color: var(--muted);
}
#on-end-wrap select { font-size: 0.78rem; padding: 0.25rem 1.5rem 0.25rem 0.5rem; }

/* --- now playing: the lit cinema screen, with a running projector --- */
.now-playing {
  position: relative;
  display: flex; align-items: center; gap: 0.75rem;
  background: rgba(242, 163, 60, 0.07);
  border: 1px solid rgba(242,163,60,.45);
  border-radius: var(--radius-sm);
  padding: 0.5rem 0.75rem;
}
/* the screen glow is stronger while the film is actually running */
.now-playing.playing {
  box-shadow: 0 0 0 1px rgba(242,163,60,.14), 0 6px 26px rgba(242,163,60,.14);
}
/* projector shutter sits to the left of the thumb; only spins while playing */
.np-shutter {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 18px; color: var(--accent);
}
.np-shutter svg { animation: spin 1.1s steps(6, end) infinite; }
.now-playing:not(.playing) .np-shutter svg { animation-play-state: paused; opacity: .5; }
.now-playing .qthumb { width: 84px; }
.now-playing .lock {
  flex: none; font-size: 0.72rem; letter-spacing: .04em;
  background: rgba(242,163,60,.16); color: var(--accent);
  border-radius: 999px; padding: 0.125rem 0.5rem;
}
/* two-tap stop: the hidden "confirm" label grows the button open on first tap */
.np-stop {
  display: inline-flex; align-items: center; justify-content: center;
  overflow: hidden; white-space: nowrap;
}
.np-stop .np-stop-confirm {
  max-width: 0; opacity: 0;
  transition: max-width 0.22s ease-out, opacity 0.16s ease-out, margin-left 0.22s ease-out;
}
.np-stop.armed { border-color: var(--danger); color: var(--danger); background: rgba(255, 122, 100, 0.12); }
.np-stop.armed .np-stop-confirm { max-width: 6em; opacity: 1; margin-left: 0.5rem; }
.np-meta { min-width: 0; flex: 1; }
.np-title {
  font-family: var(--font-display);
  font-weight: 560; font-size: 1.05rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* --- queue list entries --- */
.queue-list { list-style: none; margin: 0.25rem 0 0; padding: 0; display: flex; flex-direction: column; gap: 0.25rem; }
.qitem {
  display: flex; align-items: center; gap: 0.5rem;
  background: var(--panel-2);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-sm);
  padding: 0.5rem;
}
/* --- drag-to-reorder --- */
.qitem.reorderable { cursor: grab; touch-action: pan-y; }
.qitem.reorderable:active { cursor: grabbing; }
/* the hole the row leaves behind: a dark, hollow slot in the list */
.qitem.drag-src {
  background: var(--bg-deep);
  border: 1.5px dashed var(--border);
  box-shadow: none;
}
.qitem.drag-src > * { visibility: hidden; }
/* the floating clone that follows the pointer */
.queue-float {
  position: fixed;
  z-index: 9998;
  margin: 0;
  cursor: grabbing;
  pointer-events: none;
  background: var(--panel-2);
  border-color: rgba(242, 163, 60, 0.5);
  box-shadow: 0 14px 32px rgba(0, 0, 0, 0.55), 0 0 0 1px rgba(242, 163, 60, 0.2);
  transform-origin: center;
  will-change: transform;
}
.queue-float.snap { transition: transform 0.16s ease-out, opacity 0.18s ease-out; }
.queue-float.fade { opacity: 0; }
/* while dragging, rows glide to their previewed positions (FLIP in app.js) */
.queue-list.drag-active .qitem { user-select: none; }

/* the red way out: materializes under the pointer's hand during a drag */
.queue-dropzone {
  position: fixed;
  left: 50%;
  bottom: 26px;
  transform: translateX(-50%);
  z-index: 9997;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.75rem 1.25rem;
  border-radius: 999px;
  background: rgba(255, 122, 100, 0.13);
  border: 1.5px dashed rgba(255, 122, 100, 0.55);
  color: var(--danger);
  font-size: 0.85rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5);
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.16s ease-out, transform 0.16s ease-out, background 0.16s ease-out;
}
.queue-dropzone.active { opacity: 1; }
.queue-dropzone.over {
  background: rgba(255, 122, 100, 0.32);
  border-style: solid;
  transform: translateX(-50%) scale(1.07);
}

.qthumb {
  width: 64px; aspect-ratio: 16/9; flex: none;
  border-radius: 5px; object-fit: cover; background: #0a0908;
  display: block;
}
.qthumb.ph {
  display: flex; align-items: center; justify-content: center;
  color: var(--text-faint); font-size: 1.05rem;
}
/* dual-purpose thumbnail: click plays, drag reorders */
.qthumb-btn {
  padding: 0; border: none; background: transparent; border-radius: 5px;
  flex: none; width: 64px; aspect-ratio: 16/9; position: relative; overflow: hidden;
  display: block; cursor: pointer;
}
.qthumb-btn.no-perm { cursor: default; opacity: 0.6; }
.qthumb-btn:not(.no-perm):hover .qthumb { filter: brightness(0.85); }
.qthumb-btn .qthumb-overlay {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  background: rgba(0,0,0,0.32); opacity: 0; transition: opacity 0.14s ease-out; color: var(--text);
}
.qthumb-btn:not(.no-perm):hover .qthumb-overlay { opacity: 1; }
.qthumb-btn .qthumb.ph { opacity: 0.85; }
.qthumb-btn:not(.no-perm):hover .qthumb.ph { opacity: 1; }

.qmeta { min-width: 0; flex: 1; }
.qtitle {
  font-weight: 550; font-size: .92rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  line-height: 1.3;
}
.qtitle-wrap { display: flex; align-items: center; gap: 0.25rem; min-width: 0; flex: 1; }
/* the hover pill breathes OUTWARD: negative margins cancel the padding so
   the title's text sits on the same left edge as qorig/qsub in every row */
.qtitle.editable {
  cursor: text; padding: 0.125rem 0.25rem; margin: -0.125rem -0.25rem;
  border: 1px solid transparent; border-radius: 6px;
  transition: border-color .14s, background .14s;
}
.qtitle.editable:hover { border-color: var(--border); background: rgba(255,255,255,0.04); }
.qtitle-input { font-weight: 550; font-size: .92rem; background: var(--panel-2); color: var(--text); border: 1px solid var(--accent); border-radius: 6px; padding: 0.125rem 0.25rem; flex: 0 1 auto; min-width: 14ch; width: auto; max-width: 100%; field-sizing: content; }
.qtitle-cancel { flex: none; width: 28px; height: 28px; padding: 0; display: inline-flex; align-items: center; justify-content: center; border-radius: 6px; border: 1px solid var(--border); background: transparent; color: #fff; }
.qtitle-cancel:hover:not(:disabled) { border-color: var(--danger); color: var(--danger); }
.qtitle-cancel svg { display: block; }
.qorig {
  font-weight: 400; font-size: .74rem;
  color: var(--text-faint);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  /* one reserved line, present even when empty: rows keep a constant height;
     same line-height as the title above so the stack reads as one column */
  line-height: 1.3;
  min-height: 1.3em;
}
.qsub { font-size: .76rem; color: var(--muted); display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; line-height: 1.3; }
.qby { color: var(--gold); display: inline-flex; align-items: center; gap: 0.25rem; }

.qchip {
  font-size: .68rem; border-radius: 999px; padding: 0.125rem 0.5rem;
  background: rgba(168,157,139,.15); color: var(--muted);
  display: inline-flex; align-items: center; gap: 0.25rem;
}
.qchip.ok { background: rgba(156,203,116,.15); color: var(--ok); }
.qchip.proc { background: rgba(242,163,60,.16); color: var(--accent); }
.qchip.err { background: rgba(255,122,100,.16); color: var(--danger); }
.qchip.here { background: rgba(232,193,90,.18); color: var(--gold); }

.qbtns { display: flex; gap: 0.25rem; flex: none; align-items: center; }
/* collapsed: only the chevron shows; expanded: the full set slides in after it */
.qbtns .qbtns-extra {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  overflow: hidden;
  max-width: 0;
  opacity: 0;
  transition: max-width .2s ease-out, opacity .14s ease-out;
}
.qitem.expanded .qbtns-extra { max-width: 220px; opacity: 1; }
.qbtns .qtoggle { padding: 0.25rem 0.5rem; }
.qbtns button, .np-btns button {
  padding: 0.25rem 0.5rem; font-size: .85rem; line-height: 1.2;
  background: transparent; border-color: var(--border);
}
.qbtns button:hover:not(:disabled), .np-btns button:hover:not(:disabled) {
  border-color: var(--accent); color: var(--accent);
}
.qbtns button:disabled { opacity: .35; cursor: not-allowed; }
.qbtns .qdel:hover:not(:disabled) { border-color: var(--danger); color: var(--danger); }

/* --- queue actions --- */
.queue-actions { display: flex; gap: 0.5rem; align-items: center; margin-top: 0.75rem; flex-wrap: wrap; }

/* custom "from this room" menu: popover with thumbnails and queue status */
.catalog-wrap { position: relative; }
#catalog-open-btn { font-size: .85rem; }
.catalog-menu {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 30;
  width: min(360px, 78vw);
  max-height: 320px;
  overflow-y: auto;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: 0 14px 34px rgba(0,0,0,.5);
  padding: 0.5rem;
}
.catalog-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.25rem; }
.catalog-item:hover:not(:has(button:disabled)) { border-color: var(--accent); }

/* ================= upload modal ================= */
.modal {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
  color: var(--text);
  padding: 0;
  max-width: 420px; width: calc(100vw - 2.4rem);
}
.modal::backdrop { background: rgba(8, 6, 4, 0.86); }
.modal-body { display: flex; flex-direction: column; gap: 0.75rem; padding: 1.25rem; }
.modal-body h3 { margin: 0; }
.modal-file { margin: 0; word-break: break-all; }
.modal-body label { font-size: .82rem; color: var(--muted); }
.modal-body input[type="text"] { width: 100%; }
.modal-actions { justify-content: flex-end; margin-top: 0.25rem; }

/* ================ chat image picker ================
   one step wider than the upload modal: the grabbed frame deserves a
   real preview, lit like a little screen */
#chat-image-modal { max-width: 520px; }
.chat-image-body { position: relative; }
/* the picker's second action row: "bring your own" under "with the frame",
   separated by a hairline so the two intents read as groups */
.chat-image-alt-actions {
  border-top: 1px solid var(--border-soft);
  padding-top: 0.5rem;
}
.modal-close {
  position: absolute;
  top: .8rem;
  right: 1.05rem;
}
.chat-shot-preview {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 180px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background:
    radial-gradient(ellipse at center, rgba(242, 163, 60, .06), transparent 72%),
    var(--panel-2);
  overflow: hidden;
}
.chat-shot-preview img { display: block; max-width: 100%; max-height: 42vh; }
#chat-shot-empty { padding: 1.25rem 1.5rem; text-align: center; }

/* ================ chat image viewer ================
   the screen takes over: the picture fits, one click zooms in at the
   pointer, the next sits back down; buttons and wheel fine-tune.
   The dialog shrink-wraps the picture (aspect ratio preserved, capped
   to the viewport) inside a bezel — a framed screen, not a bare
   rectangle. */
.image-view {
  width: fit-content;
  max-width: min(960px, 94vw);
  height: fit-content;
  max-height: 92vh;
  background: rgba(14, 11, 9, .97);
}
/* flex only while open: an author `display` would override the UA
   display:none of a closed <dialog> and paint the viewer on every page */
dialog.image-view[open] { display: flex; flex-direction: column; }
.viewer-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.5rem 0.5rem 0.5rem 1rem;
  border-bottom: 1px solid var(--border-soft);
}
.viewer-who {
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: .78rem;
  letter-spacing: .02em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.viewer-stage {
  flex: 1;
  min-height: 0;
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0.75rem; /* the bezel: air between screen and frame */
  touch-action: none; /* the pointer pans the picture, not the page */
}
.viewer-stage img {
  /* caps are viewport-relative, not 100% of the parent: the dialog is
     fit-content and sized BY this image, so a percentage would be circular */
  max-width: calc(min(94vw, 960px) - 1.6rem);
  max-height: calc(92vh - 7rem); /* head + controls + bezel */
  cursor: zoom-in;
  user-select: none;
  -webkit-user-drag: none;
  will-change: transform;
}
.viewer-stage img.zoomed { cursor: grab; }
.viewer-stage img.anim { transition: transform .16s ease; }
.viewer-controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.25rem;
  padding: 0.5rem;
  border-top: 1px solid var(--border-soft);
}
.viewer-zoom-label {
  min-width: 3.8em;
  text-align: center;
  font-family: var(--font-mono);
  font-size: .78rem;
  color: var(--muted);
}
/* avatar flavor: the same viewer, shrunk for the profile picture — the
   96px avatar shows at a friendly ~3x inside a fit-content dialog */
.image-view.avatar-mode { max-width: min(460px, 94vw); }
.image-view.avatar-mode .viewer-stage img {
  width: min(280px, 70vw);
  height: auto;
  max-width: none;
  max-height: none;
  border-radius: 10px;
}

/* ================ chat canvas editor ================
   mark-up over a chat picture: a dark stage with the paper centered
   and transform-scaled (same viewer trick — flex only while open, an
   author `display` would paint a closed dialog on every page). The
   toolbar groups wrap below on narrow windows. */
.canvas-editor {
  width: fit-content;
  max-width: min(900px, 92vw);
  height: fit-content;
  max-height: 96vh;
  background: rgba(14, 11, 9, .97);
}
dialog.canvas-editor[open] { display: flex; flex-direction: column; }
.canvas-viewport {
  position: relative;
  overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  height: clamp(240px, 52vh, 560px);
  margin: 0.75rem; /* the bezel, as in the viewer */
  border-radius: var(--radius-sm);
  background:
    radial-gradient(ellipse at center, rgba(242, 163, 60, .05), transparent 72%),
    var(--bg-deep);
  touch-action: none; /* the fingers pinch and draw, the page never scrolls */
  cursor: crosshair;
}
.canvas-paper {
  position: relative;
  flex: none;
  transform-origin: center;
  will-change: transform;
  box-shadow: 0 2px 24px rgba(0, 0, 0, .45); /* the paper floats over the void */
}
.canvas-paper.anim { transition: transform .16s ease; }
#canvas-base { display: block; width: 100%; height: 100%; }
#canvas-ink { position: absolute; inset: 0; width: 100%; height: 100%; }
/* the live text placement rides inside the paper, so the zoom scales it
   with the drawing; the shadow mirrors the halo the export paints */
.canvas-text-input {
  position: absolute;
  transform: translateY(-50%);
  margin: 0; padding: 0 0.12em;
  width: 12em; min-width: 2em;
  border: 1px dashed var(--accent);
  border-radius: 4px;
  background: transparent;
  font-weight: 700; line-height: 1.15;
  outline: none;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .55);
}
.canvas-toolbar {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: 0.25rem 0.5rem;
  padding: 0.5rem;
  border-top: 1px solid var(--border-soft);
}
.tool-group { display: inline-flex; align-items: center; gap: 0.25rem; }
.tool-sep { width: 1px; height: 24px; flex: none; background: var(--border); margin: 0 0.125rem; }
.tool-btn.active, .canvas-size.active {
  border-color: var(--accent);
  color: var(--accent);
  background: rgba(242, 163, 60, .12);
}
.tool-glyph { font-weight: 700; font-size: .95rem; line-height: 1; }
.size-dot { border-radius: 50%; background: currentColor; }
.swatch {
  width: 22px; height: 22px; flex: none;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 50%;
}
.swatch.active { outline: 2px solid var(--accent); outline-offset: 1px; }
#canvas-color-custom {
  width: 28px; height: 28px; flex: none;
  padding: 2px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--panel-2);
}
.canvas-use-group { margin-left: auto; } /* "use" keeps its place at the right end */
@media (max-width: 720px) {
  .canvas-use-group { margin-left: 0; width: 100%; } /* wraps to its own line */
  .canvas-use-group button { width: 100%; }
}

/* ================= permissions editor ================= */
/* labeled pills: each permission is readable without hover, click to toggle */
.perm ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.25rem; }
.perm-row {
  display: grid;
  grid-template-columns: minmax(86px, 120px) 1fr;
  gap: 0.5rem; align-items: start;
  font-size: .8rem;
  padding: 0.25rem 0.5rem;
  border-bottom: 1px solid var(--border-soft);
}
.perm-row:last-of-type { border-bottom: none; }
.perm-row .pname {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  padding-top: 0.25rem; font-weight: 550;
}
.perm-row.default .pname { color: var(--gold); }
.perm-group {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  width: 100%;
}
.tog {
  font-size: .72rem; line-height: 1;
  padding: 0.25rem 0.5rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--muted);
}
.tog:hover:not(:disabled) { border-color: var(--accent); }
.tog.on {
  background: rgba(242,163,60,.16);
  border-color: rgba(242,163,60,.6);
  color: var(--accent);
}
.tog.all {
  border-style: dashed;
}
.tog.all.on {
  background: rgba(242,163,60,.16);
  border-color: rgba(242,163,60,.6);
  border-style: solid;
  color: var(--accent);
}
.tog:disabled { opacity: .45; cursor: not-allowed; }
.tog.on:disabled { opacity: 1; }
.perm-row.locked .tog:disabled { opacity: 1; }

/* --- svg icons (sprite #i-*) and connection signal --- */
.controls button, .qbtns button, .np-btns button, #gate-btn {
  display: inline-flex; align-items: center; justify-content: center;
}
#gate-btn { gap: 0.5rem; }
#chat-mute-btn svg { display: block; }
.crown { display: inline-flex; align-items: center; }
.crown svg { display: block; filter: drop-shadow(0 0 4px rgba(232, 193, 90, 0.45)); }
.perm-row .crown svg { width: 12px; height: 12px; margin-left: 0.25rem; }
.now-playing .lock { display: inline-flex; align-items: center; gap: 0.25rem; }
#catalog-open-btn { display: inline-flex; align-items: center; gap: 0.25rem; }

/* --- in-flight buttons: a slow request shows a spinner, not a dead click */
button.busy { position: relative; color: transparent !important; pointer-events: none; }
button.busy::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  width: 1em;
  height: 1em;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-top-color: transparent;
  color: inherit;
  opacity: .9;
  animation: busy-spin .7s linear infinite;
  color: var(--ink, #f0e9dd);
}
@keyframes busy-spin { to { transform: rotate(360deg); } }

/* --- host-hint danger flash: color ownership moved here from inline styles */
#host-hint.danger { color: var(--danger); }

/* --- connection pill: manual retry affordance */
.conn-retry {
  margin-left: 0.5rem;
  border: 1px solid currentColor;
  border-radius: 999px;
  background: none;
  color: inherit;
  font: inherit;
  font-size: 0.78rem;
  padding: 0 0.5rem;
  cursor: pointer;
}
/* no resting opacity here: the global button hover (amber ink, amber ring)
   is the answer to the pointer, and a dimmed control reads as a disabled one */

/* --- debug-mode beacon: server runs on the "debug" release channel --- */
/* fixed to the body it rides over every view (lobby, invite gate, room)
   and every scroll position; never interactive, it can't block clicks */
.debug-badge {
  position: fixed;
  left: 14px;
  bottom: 14px;
  z-index: 10000;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.25rem 0.75rem;
  border-radius: 999px;
  background: rgba(13, 11, 9, 0.82);
  border: 1px solid rgba(255, 122, 100, 0.55);
  color: var(--danger);
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.16em;
  pointer-events: none;
  user-select: none;
}
.debug-badge .debug-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--danger);
  animation: debug-pulse 1.6s ease-in-out infinite;
}
@keyframes debug-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.25; }
}

/* ------------------------------------------------- about footer (lobby) */
/* collapsed by default; when open, the body holds a fixed height and
   scrolls internally — the truth about Prisma never resizes the lobby */
.about {
  margin-top: 2rem;
  text-align: center;
}
.about summary {
  display: inline-block;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.18em;
  text-transform: lowercase;
  cursor: pointer;
  padding: 0.5rem;
  border-radius: var(--radius-sm);
  list-style: none;
  transition: color 0.2s ease;
}
.about summary::-webkit-details-marker { display: none; }
/* the disclosure marker is a state (what will happen on click), not ornament:
   the em dash that used to sit here was punctuation doing no job at all */
.about summary::before { content: "+ "; color: var(--accent); }
.about details[open] summary::before { content: "\2212 "; color: var(--accent); }
.about summary:hover { color: var(--text); }
.about details[open] summary { color: var(--text); }
.about-body {
  text-align: left;
  max-height: 42vh;
  overflow-y: auto;
  overscroll-behavior: contain;
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-sm);
  padding: 1rem;
  margin-top: 0.5rem;
  background: rgba(13, 11, 9, 0.35);
}
.about-body h3 {
  margin: 1rem 0 0.25rem;
  font-family: var(--font-mono);
  font-size: 0.64rem;
  font-weight: 400;
  letter-spacing: 0.16em;
  text-transform: lowercase;
  color: var(--accent);
}
.about-body section:first-child h3 { margin-top: 0.125rem; }
.about-body p,
.about-body li {
  margin: 0;
  font-size: 0.78rem;
  line-height: 1.55;
  color: var(--muted);
}
.about-body ul { margin: 0; padding-left: 1rem; }
.about-body li { margin: 0.25rem 0; }
.about-build {
  margin-top: 1rem !important;
  padding-top: 0.75rem;
  border-top: 1px dashed var(--border-soft);
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  text-align: center;
  opacity: 0.75;
}
.about-build:empty { display: none; }

/* The legal line sits under the collapsed "about" panel, so it is the only
   footer text always on screen: keep it quiet but never below AA contrast
   (var(--muted) on --bg is 7:1; opacity would eat that, so no opacity here). */
.about-legal {
  margin-top: 1rem;
  font-family: var(--font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.14em;
  text-transform: lowercase;
  text-align: center;
}
.about-legal a { color: var(--muted); text-decoration: none; }
.about-legal a:hover,
.about-legal a:focus-visible { color: var(--accent); text-decoration: underline; }
