/* Cuppa shell. Dark surround so the pixel world is the only lit thing.
   Everything here is hard-edged on purpose: 1px borders, no radii above 2px,
   no blur, no gradient fields. The UI is furniture, the world is the subject.
   The room's own signs (the notice board, the host lamp) are NOT in here: they
   are drawn in the world, because a sign in a sidebar is not a room's sign. */

:root {
  --void: #08080d;
  --surround: #0b0b11;
  --panel: rgba(14, 14, 22, 0.92);
  --line: #2a2a3d;
  --line-hot: #3d3d5c;
  --ink: #cfd0dd;
  --ink-dim: #7c7d94;
  --gold: #e8b45a;
  --cyan: #7fb3d5;
  --warn: #e8735a;
  --danger: #ff4d4d;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
}

* { box-sizing: border-box; }

/* The hidden attribute must beat every display rule below it, or the name
   popover sits open on load and reads as the join gate C1 exists to forbid. */
[hidden] { display: none !important; }

html, body {
  height: 100%;
  margin: 0;
  background: var(--void);
  color: var(--ink);
  font-family: var(--mono);
  font-size: 13px;
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
}

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 50;
  background: var(--gold); color: #14141f; padding: 8px 12px; font-weight: 700;
}
.skip:focus { left: 8px; top: 8px; }
#exitLink:focus { top: 44px; }

#stage {
  position: fixed;
  inset: 0;
  background: var(--surround);
}

#world {
  display: block;
  width: 100%;
  height: 100%;
  image-rendering: pixelated;
  cursor: crosshair;
  touch-action: none;
}

/* ---- top bar ---------------------------------------------------------- */

#hud {
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 40px;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 8px;
  background: linear-gradient(180deg, rgba(8, 8, 13, 0.85), rgba(8, 8, 13, 0));
  pointer-events: none;
}
#hud > * { pointer-events: auto; }
.spacer { flex: 1; }

.brand { display: flex; align-items: center; gap: 8px; }
.mark {
  width: 14px; height: 14px;
  background:
    linear-gradient(var(--gold), var(--gold)) 0 2px / 14px 2px no-repeat,
    linear-gradient(var(--cyan), var(--cyan)) 0 7px / 10px 2px no-repeat,
    linear-gradient(var(--cyan), var(--cyan)) 0 12px / 6px 2px no-repeat;
}
.wordmark {
  font-size: 11px; letter-spacing: 0.22em; font-weight: 700; color: var(--ink);
}

.chip {
  display: inline-flex; align-items: center; gap: 6px;
  height: 24px; padding: 0 9px;
  font: inherit; font-size: 11px;
  color: var(--ink);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 2px;
  cursor: pointer;
}
.chip:hover { border-color: var(--line-hot); }
.chip:focus-visible { outline: 2px solid var(--gold); outline-offset: 1px; }
#nameLabel { max-width: 18ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.conn { cursor: default; color: var(--ink-dim); }
.conn .dot { width: 6px; height: 6px; background: var(--ink-dim); }
.conn[data-state="live"] { color: var(--ink); }
.conn[data-state="live"] .dot { background: #6fbf73; }
.conn[data-state="connecting"] .dot { background: var(--gold); animation: pulse 1s steps(2, end) infinite; }
.conn[data-state="offline"] { color: var(--warn); }
.conn[data-state="offline"] .dot { background: var(--warn); }
@keyframes pulse { 50% { opacity: 0.25; } }

/* Ringing out, and in a call. The caller's whole view of their own call: gold
   because it is live, pulsing only while it is still a question. `data-state`
   rather than a class so the one chip is one chip in two truths, the way conn
   already is. */
.chip.call { color: var(--gold); border-color: var(--gold); }
.chip.call[data-state="out"] .dot { background: var(--gold); animation: pulse 1s steps(2, end) infinite; }
.chip.call[data-state="in"] .dot { background: var(--gold); }
.chip.call .dot { width: 6px; height: 6px; }

.gwrap { display: flex; align-items: center; min-height: 24px; }
.gwrap .stub {
  height: 24px; padding: 0 9px;
  display: inline-flex; align-items: center; gap: 6px;
  font: inherit; font-size: 11px;
  color: var(--ink-dim);
  background: var(--panel);
  border: 1px dashed var(--line);
  border-radius: 2px;
  cursor: not-allowed;
}

/* ---- name popover ----------------------------------------------------- */

.popover {
  position: absolute;
  top: 44px; right: 8px;
  display: flex; align-items: center; gap: 6px;
  flex-wrap: wrap;
  max-width: 384px;
  padding: 8px;
  background: var(--panel);
  border: 1px solid var(--line-hot);
  border-radius: 2px;
  z-index: 6;
}

/* ---- the mirror (avatar picker) ---------------------------------------- */
.lookWrap {
  flex-basis: 100%;
  display: flex; gap: 12px; align-items: flex-start;
  padding-top: 8px;
  border-top: 1px solid var(--line);
}
.mirror {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  flex: 0 0 auto;
}
#lookPrev {
  width: 80px; height: 120px;
  image-rendering: pixelated;
  background: #05050a;
  border: 1px solid var(--line);
  border-radius: 2px;
}
.dice {
  width: 80px;
  font: inherit; font-size: 10px; line-height: 1.7;
  color: var(--ink); background: #05050a;
  border: 1px solid var(--line); border-radius: 2px;
  cursor: pointer;
}
.dice:hover { border-color: var(--gold); color: var(--gold); }
.dice:focus-visible { outline: 2px solid var(--gold); outline-offset: 1px; }
.lookRows { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.lookRow { display: flex; align-items: flex-start; gap: 4px; }
.lookRow > span {
  width: 7.5ch; flex: 0 0 auto;
  padding-top: 2px;
  font-size: 10px; color: var(--ink-dim);
}
/* wrapped option lines align under the options, never under the label */
.lookRow .opts { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; min-width: 0; }
.swatch {
  width: 14px; height: 14px;
  border: 1px solid var(--line);
  border-radius: 2px;
  cursor: pointer;
  display: inline-block;
}
.lookRow .chipTall { height: 44px !important; width: 28px !important; }
.lookRow .chip {
  width: 32px; height: 26px;
  image-rendering: pixelated;
  background: #05050a;
  border: 1px solid var(--line);
  border-radius: 2px;
  cursor: pointer;
}
.swatch:hover, .lookRow .chip:hover { border-color: var(--line-hot); }
.swatch:focus-visible, .lookRow .chip:focus-visible { outline: 2px solid var(--gold); outline-offset: 1px; }
.swatch.sel, .lookRow .chip.sel { border-color: var(--gold); box-shadow: 0 0 0 1px var(--gold); }
.claimNote { flex-basis: 100%; margin: 2px 0 0; font-size: 10px; color: var(--ink-dim); }
#nameBtn[data-verified="1"] #nameLabel::after {
  content: ' \2713';
  color: var(--gold);
}
.popover label { font-size: 11px; color: var(--ink-dim); }
.popover input {
  width: 16ch;
  font: inherit; font-size: 12px;
  color: var(--ink);
  background: #05050a;
  border: 1px solid var(--line);
  border-radius: 2px;
  padding: 4px 6px;
}
.popover input:focus { outline: none; border-color: var(--gold); }
.btn {
  font: inherit; font-size: 11px;
  color: #14141f; background: var(--gold);
  border: 0; border-radius: 2px;
  padding: 5px 9px; cursor: pointer; font-weight: 700;
}

/* ---- hint ------------------------------------------------------------- */

.hint {
  position: absolute;
  left: 50%; top: 52px;
  transform: translateX(-50%);
  padding: 6px 12px;
  font-size: 11px;
  color: var(--ink);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 2px;
  letter-spacing: 0.04em;
  transition: opacity 900ms linear;
  pointer-events: none;
}
.hint b { color: var(--gold); font-weight: 700; }
.hint.gone { opacity: 0; }

/* ---- person chip (click a nearby avatar) ------------------------------- */

.personChip {
  position: fixed;
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 5px 7px;
  font-size: 11px;
  color: var(--ink);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 2px;
  letter-spacing: 0.04em;
}
/* Stacked, for the one caller whose items are sentences rather than words: a
   list of microphones laid out in a row is a menu as wide as the window. The
   items keep the same button, the same hover and the same ink; only the axis
   changes, because only the axis was wrong. */
.personChip.col {
  flex-direction: column;
  align-items: stretch;
  gap: 3px;
  max-width: 260px;
}
.personChip.col .who { padding: 0 0 2px; }
.personChip.col button {
  text-align: left;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.personChip .who { color: var(--gold); font-weight: 700; padding-right: 2px; }
.personChip button {
  font: inherit; font-size: 11px;
  color: var(--ink);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 2px;
  padding: 3px 8px;
  cursor: pointer;
}
.personChip button:hover, .personChip button:focus-visible {
  color: #14141f; background: var(--gold); border-color: var(--gold); outline: 0;
}

/* ---- chat ------------------------------------------------------------- */

#chat {
  position: absolute;
  left: 8px; bottom: 8px;
  width: min(380px, calc(100vw - 16px));
  display: flex; flex-direction: column; gap: 6px;
}

#log {
  list-style: none;
  margin: 0; padding: 0;
  max-height: 26vh;
  overflow-y: auto;
  overflow-x: hidden;
  font-size: 11.5px;
  line-height: 1.5;
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
  mask-image: linear-gradient(180deg, transparent, #000 22px);
}
#log:empty { display: none; }
/* Each line carries its own dark plate. Round 1's world was a dark office and
   bare text read fine on it; this room is warm, bright wood, and it changes
   colour four times a year. Text that relies on the floor being dark is text
   that vanishes in summer. The plate is the only thing that reads on all of
   them, so the line owns its background rather than borrowing the room's. */
#log li {
  display: table;
  max-width: 100%;
  margin: 1px 0;
  padding: 1px 5px;
  color: var(--ink);
  background: rgba(8, 8, 13, 0.72);
  overflow-wrap: anywhere;
}
#log li .who { color: var(--cyan); }
#log li.self .who { color: var(--gold); }
#log li.host .who { color: var(--gold); }
#log li.sys { color: #9a9bb4; }
#log li.sys::before { content: "* "; }
#log li.warn { color: var(--warn); }
#log li.warn::before { content: "! "; }
/* An in-app action in the log (the finalized talk: "watch it"). A button, never a
   link: it opens the Replays overlay over the room, so the member is never ejected
   from where they stand. Gold so it reads as the one thing to press in the line. */
#log li button.logaction {
  font: inherit; font-size: 11px; font-weight: 700;
  color: var(--gold); background: transparent;
  border: 1px solid var(--gold); border-radius: 2px;
  padding: 0 6px; margin-left: 2px; cursor: pointer;
  vertical-align: baseline;
}
#log li button.logaction:hover { color: #14141f; background: var(--gold); }
#log li button.logaction:focus-visible { outline: 2px solid var(--gold); outline-offset: 1px; }

#sayForm {
  display: flex; align-items: center; gap: 6px;
  padding: 0 8px;
  height: 30px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 2px;
}
#sayForm:focus-within { border-color: var(--gold); }
.prompt { color: var(--gold); font-weight: 700; }
#say {
  flex: 1;
  font: inherit; font-size: 12px;
  color: var(--ink);
  background: transparent;
  border: 0;
  padding: 0;
}
#say::placeholder { color: #55566b; }
#say:focus { outline: none; }

/* ---- the ring ---------------------------------------------------------- */
/* Someone is calling you, and this is the whole screen saying so. Written
   directly under the gate below, and deliberately built out of the same three
   parts, because it is the same move: one dark field, one hard card, one gold
   hairline. The room only takes the whole screen for two things, and both of
   them are a person at a door.
   The field is TRANSLUCENT where the gate's is opaque, and that is the one
   difference that matters: at the gate you are not in the room yet, so there is
   nothing behind it. Here you are standing in it, so the room stays visible,
   dimmed and pushed back behind the person in front of you. A ring that painted
   the room out would be a phone app wearing the room's clothes. */

.ring {
  position: fixed;
  inset: 0;
  z-index: 90;                 /* over the world and the HUD, under the gate:
                                  a member who is not in the room cannot be rung */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background:
    radial-gradient(120% 90% at 50% 0%, rgba(232, 180, 90, 0.10), transparent 60%),
    rgba(6, 6, 10, 0.88);
  animation: ringIn 140ms ease-out;
}
.ring:focus { outline: none; }
@keyframes ringIn { from { opacity: 0; } }

.ringCard {
  width: min(360px, 100%);
  padding: 24px 24px 18px;
  text-align: center;
  background: rgba(14, 14, 22, 0.97);
  border: 1px solid var(--line-hot);
  border-top: 1px solid var(--gold);      /* the gold hairline the notice wears */
  border-radius: 4px;
  box-shadow: 0 24px 80px rgba(0, 0, 0, 0.7), 0 0 0 1px rgba(232, 180, 90, 0.06);
}

/* The caller, drawn from the sprite the whole room already sees them wearing.
   This is #lookPrev's frame on purpose: the mirror in the avatar picker is where
   this room had already decided how a person looks when you look straight at
   them, so the ring did not need to invent anything. No camera opens for this,
   ever: they have published nothing, and a face shown to someone who has not
   accepted is the exact violation the whole call design exists to refuse.

   The frame is also the ONLY thing on this card that moves, and that is the
   result of throwing the first version away. It had a ☎ glyph pulsing above the
   portrait, and rendered it was the one piece of the card that looked pasted on:
   an emoji, drawn by a font this room does not own, sitting over a world drawn
   pixel by pixel. It also said nothing that "is calling you" was not already
   saying two lines below it. So it is gone, and its job moved onto the person:
   the frame blinks, in two hard steps, in the same gold at the same cadence the
   conn dot has always used for "live, right now". The motion is on the caller,
   which is where a ring is actually coming from. */
.ringArt {
  width: 96px; height: 144px;
  image-rendering: pixelated;
  background: #05050a;
  border: 1px solid var(--gold);
  border-radius: 2px;
  animation: ringLive 1s steps(2, end) infinite;
}
@keyframes ringLive { 50% { border-color: var(--line); } }

.ringWho {
  margin: 14px 0 2px;
  font-size: 15px; font-weight: 700; letter-spacing: 0.18em;
  color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ringSub {
  margin: 0 0 18px;
  font-size: 12px; letter-spacing: 0.04em;
  color: var(--ink-dim);
}

.ringActions { display: flex; gap: 8px; }
.ringBtn {
  flex: 1 1 0;                 /* peers: the same width, forever, by construction */
  min-height: 46px;            /* a thumb, not a mouse */
  font: inherit; font-size: 12px; letter-spacing: 0.08em;
  background: #05050a;
  border: 1px solid var(--line-hot);
  border-radius: 2px;
  cursor: pointer;
}
.ringBtn.yes { color: var(--gold); border-color: var(--gold); }
.ringBtn.no { color: var(--ink); }
/* Note what is NOT here: no dimming of `no`, no fill on `yes`, no size
   difference. Gold means live in this room, not correct. */
.ringBtn:hover { background: #0d0d18; }
.ringBtn:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.ringBtn:active { transform: translateY(1px); }

.ringNote {
  margin: 14px 0 0;
  padding-top: 12px;
  border-top: 1px solid var(--line);
  font-size: 10px; line-height: 1.6;
  color: #6a6b82;
}
/* An iPhone has no Escape key, so on a touch device that half of the sentence is
   instructions for hardware that is not there. This room has already made this
   ruling once, about the speaker chip that WebKit could never have honoured: a
   platform that cannot do the thing must not grow a control that pretends to.
   The second half is the half that matters anyway, and it is true everywhere. */
@media (pointer: coarse) { .ringKey { display: none; } }

/* The phone, one-handed. The card sits low rather than centred so the buttons
   land under a thumb instead of in the middle of the screen, and the portrait
   gives up height first because the name is what identifies a caller. */
@media (max-height: 700px), (max-width: 420px) {
  .ring { align-items: flex-end; padding-bottom: max(24px, env(safe-area-inset-bottom)); }
  .ringArt { width: 72px; height: 108px; }
}
@media (prefers-reduced-motion: reduce) {
  .ring { animation: none; }
  .ringArt { animation: none; border-color: var(--gold); }
}

/* ---- the members-only front door -------------------------------------- */

.gate {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background:
    radial-gradient(120% 90% at 50% 0%, rgba(232, 180, 90, 0.08), transparent 60%),
    linear-gradient(180deg, #07070c, #0b0b11 60%, #06060a);
}
.gateCard {
  width: min(440px, 100%);
  padding: 34px 30px 26px;
  text-align: center;
  background: rgba(14, 14, 22, 0.96);
  border: 1px solid var(--line-hot);
  border-radius: 4px;
  box-shadow: 0 24px 80px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(232, 180, 90, 0.06);
}
.gateMark {
  width: 30px; height: 30px;
  margin: 0 auto 14px;
  background:
    linear-gradient(var(--gold), var(--gold)) 0 5px / 30px 3px no-repeat,
    linear-gradient(var(--cyan), var(--cyan)) 0 15px / 22px 3px no-repeat,
    linear-gradient(var(--cyan), var(--cyan)) 0 25px / 13px 3px no-repeat;
}
.gateWordmark {
  font-size: 12px; letter-spacing: 0.34em; font-weight: 700;
  color: var(--ink-dim);
  margin-bottom: 18px;
}
.gateCard h1 {
  margin: 0 0 10px;
  font-size: 20px; font-weight: 700; letter-spacing: 0.01em;
  color: var(--ink);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
#gateMsg {
  margin: 0 auto 22px;
  max-width: 34ch;
  font-size: 13px; line-height: 1.6;
  color: var(--ink-dim);
}
.gateBtn {
  display: flex; justify-content: center;
  min-height: 44px;
  margin-bottom: 20px;
}
/* The way back for a tab the room asked to step out. Gold, because in this room
   gold is already the word for "this one is live", and coming back is the live
   choice on this screen. */
.gateBack {
  font: inherit;
  font-size: 13px;
  letter-spacing: 0.02em;
  padding: 10px 22px;
  color: #0b0b11;
  background: var(--gold);
  border: 1px solid var(--gold);
  border-radius: 999px;
  cursor: pointer;
}
.gateBack:hover { filter: brightness(1.08); }
.gateBack:focus-visible { outline: 2px solid var(--cyan); outline-offset: 2px; }

/* The presence gate device-check. Gold pill, because gold is this room's word for
   "this one is live", and turning your camera and mic on is the live act here. One
   obvious action; the same button becomes Retry if a device is missing or dead. */
.gateEnable {
  font: inherit;
  font-size: 14px; font-weight: 600;
  letter-spacing: 0.01em;
  padding: 12px 26px;
  color: #0b0b11;
  background: var(--gold);
  border: 1px solid var(--gold);
  border-radius: 999px;
  cursor: pointer;
  transition: filter 120ms ease, opacity 120ms ease;
}
.gateEnable:hover { filter: brightness(1.08); }
.gateEnable:focus-visible { outline: 2px solid var(--cyan); outline-offset: 2px; }
.gateEnable:disabled { opacity: 0.6; cursor: default; }

/* The green-room device readout: two chips that go from grey to live-green as the
   real tracks come up. Truthful, painted off the MediaStreamTrack readyState. */
.gateAv {
  display: flex; justify-content: center; gap: 10px;
  margin: 0 0 18px;
}
.gateAvChip {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 6px 12px;
  font-size: 12px; letter-spacing: 0.01em;
  color: var(--ink-dim);
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid var(--line);
  border-radius: 999px;
}
.gateAvDot {
  width: 8px; height: 8px; border-radius: 50%;
  background: #4a4b5e;
  box-shadow: none;
  transition: background 160ms ease, box-shadow 160ms ease;
}
.gateAvChip[data-on="1"] { color: var(--ink); border-color: rgba(74, 222, 128, 0.4); }
.gateAvChip[data-on="1"] .gateAvDot {
  background: #4ade80;
  box-shadow: 0 0 8px rgba(74, 222, 128, 0.7);
}

.gateHelp {
  margin: 0 auto 18px;
  max-width: 34ch;
  font-size: 12px; line-height: 1.55;
  color: #8a8ba2;
}

.gateNote {
  margin: 0;
  padding-top: 16px;
  border-top: 1px solid var(--line);
  font-size: 11px; line-height: 1.6;
  color: #6a6b82;
}

@media (max-width: 560px) {
  .wordmark { display: none; }
  #log { max-height: 18vh; }
  .gateCard { padding: 28px 20px 22px; }
}


/* ---- the notice (hover the brand) ------------------------------------- */
/* Was painted on the canvas over the floor and followed the board around the
   room. It is chrome now: it hangs off the brand, it is hard-edged like every
   other panel here, and it never covers the world unless someone asks for it. */

.brand { position: relative; cursor: default; }
.brand:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

.notice {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 40;
  min-width: 210px;
  padding: 7px 9px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-top: 1px solid var(--gold);      /* the gold hairline, kept */
  font-size: 11px;
  letter-spacing: 0.04em;
  line-height: 1.6;
  white-space: nowrap;
}
.notice .nTitle { color: var(--gold); font-weight: 700; }
.notice .nStatus { color: var(--ink-dim); font-size: 10px; }
.notice .nStatus.hot { color: var(--ink); }
/* Where the rabbit is. The room answers it in the same quiet voice it answers
   "is anyone home", because that is what he is: a fact about the room, not a
   feature of the product. No gold, no weight, no dot that blinks. */
.notice .nLiam { color: var(--ink-dim); font-size: 10px; }
.notice .nTiers { margin-top: 5px; padding-top: 5px; border-top: 1px solid var(--line); }
.notice .nTier { color: #f2efe6; padding-left: 7px; position: relative; }
.notice .nTier::before {                 /* the gold bar per tier, kept */
  content: ''; position: absolute; left: 0; top: 3px; bottom: 3px; width: 3px;
  background: var(--gold);
}
.notice .nNote { margin-top: 4px; color: var(--ink-dim); font-size: 10px; }

/* ---- your voice and your face (PLAN-VIDEO wave V1) --------------------- */
/* Off is the resting state, and it is drawn as off: dim, with a hollow dot.
   Live is gold with a filled dot, because gold is already this room's word for
   "this is on" (the host lamp, the caret, the connection chip). A member must
   never have to wonder. */

.chip.av { color: var(--ink-dim); letter-spacing: 0.06em; }
.chip.av .dot {
  width: 6px; height: 6px;
  background: transparent;
  border: 1px solid var(--ink-dim);
}
.chip.av[data-live="1"] { color: var(--gold); border-color: var(--gold); }
.chip.av[data-live="1"] .dot { background: var(--gold); border-color: var(--gold); }

/* THE FAULT. A device that is genuinely on, whose track the room could not
   carry.

   This state was invisible until 2026-07-17 and it cost the product its one
   sentence. The failure was written to the chip's `title` and nowhere else: a
   tooltip, which needs a mouse and a hover, and which does not exist on the
   phone the bug was reported from. So a member stood in the room with a gold
   chip, three level bars dancing off his own real microphone, and not one pixel
   anywhere saying that his voice was reaching nobody. Reproduced by answering
   502 to every tracks/new: mic=true, publishing.audio=true, deaf=[], silence.

   Warn, not gold, and for the same reason "hear the room" is warn: gold means
   "this is on", and the room not carrying you is the opposite of on. But the
   chip keeps its live dot rather than going dark, because the mic IS on and the
   camera light IS lit; going dark would trade this lie for the mirror-image one.
   Live and faulted is exactly what it is: your device works, the room does not
   have it. The pulse is borrowed from the same place, to reach the member who is
   sitting perfectly still wondering why nobody is answering him. */
.chip.av[data-fault="1"] { color: var(--warn); border-color: var(--warn); }
.chip.av[data-fault="1"] .dot {
  background: var(--warn); border-color: var(--warn);
  animation: pulse 1s steps(2, end) infinite;
}
.chip.av[data-fault="1"]:hover { border-color: var(--warn); color: #fff; }

/* "hear the room": the browser refused to play a voice. Warn, not gold: gold is
   this room's word for "this is on", and a refusal is the opposite of on. Warn
   is already the word for "a thing that should be working is not" (the
   connection chip says offline in exactly this colour), which is precisely what
   this is. It borrows the connecting dot's pulse to catch the eye of a member
   who is sitting still, because a member who is sitting still is the only one
   this chip has to reach: anybody who walks or types fixes it without reading a
   word. */
.chip.hear { color: var(--warn); border-color: var(--warn); }
.chip.hear .dot {
  width: 6px; height: 6px;
  background: var(--warn); border: 1px solid var(--warn);
  animation: pulse 1s steps(2, end) infinite;
}
.chip.hear:hover { border-color: var(--warn); color: #fff; }

/* THE RECORD BUTTON. Not another av chip. During a live lecture the host hunted
   for it and could not find it: it was display:none unless he happened to be
   standing on the stage tiles, and even when shown it wore the same 24px grey
   grammar as mic/cam/screen and vanished among them. So it is deliberately
   louder than everything beside it: taller, a pill, a filled red dot and the
   word REC, and a red border it never loses. It is always on the host's HUD;
   off the stage it is dimmed (not hidden, never struck through) and the title
   carries the reason. Live: a pulsing red ring and the elapsed clock. */
/* The resting state is a FILLED red pill, not a faint outline: the host reported
   the old #ff6a6a-on-8%-tint was too dim to find on the dark navbar. White text
   on a solid #c9282e clears WCAG AA (about 5.5:1); the pill stays loud and red so
   it still out-shouts the mic/cam/screen chips beside it. */
.chip.rec {
  height: 30px; padding: 0 14px; gap: 8px;
  font-size: 12px; font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase;
  border-radius: 999px;
  color: #fff;
  border: 1.5px solid #e0454b;
  background: #c9282e;
}
.chip.rec .dot {
  width: 10px; height: 10px; border-radius: 50%;
  background: #ff7a7a; border: none;
  box-shadow: 0 0 6px rgba(255, 190, 190, 0.9);
}
.chip.rec:hover { background: #d62f35; border-color: #ff9a9a; color: #fff; }
.chip.rec[data-host="0"] { cursor: default; }
/* Off the stage: dimmer, but still an unmistakable FILLED red record button, not
   a ghost. Floor raised from opacity 0.42 to 0.7 over a real dark-red fill, so
   the pale text (about 7:1 on the fill) stays legible. */
.chip.rec[data-can="0"] {
  opacity: 0.7; cursor: default;
  background: #6e2020; border-color: #c9282e; color: #f2c6c6;
}
.chip.rec[data-can="0"] .dot { background: #e07a7a; box-shadow: none; }
.chip.rec[data-can="0"]:hover { border-color: #c9282e; color: #f2c6c6; background: #6e2020; }
/* Recording live: a brighter solid pill, a pulsing ring, the clock in the label. */
.chip.rec[data-live="1"] {
  color: #fff; border-color: #ff6a6a;
  background: #d62f35;
  opacity: 1;
  animation: recring 1.6s ease-in-out infinite;
}
.chip.rec[data-live="1"] .dot { animation: recdot 1.2s ease-in-out infinite; }
/* Armed to stop: the next click ends the tape, so it says so and looks it. */
.chip.rec[data-arm="1"] {
  color: #fff; background: #cf2626; border-color: #ff9a9a;
  animation: none;
}
.chip.rec[data-arm="1"] .dot { background: #fff; box-shadow: none; animation: none; }
/* The upload fault still wins the colour, the amber the chunk path paints. */
.chip.rec[data-fault="1"] { color: var(--warn); border-color: var(--warn); background: rgba(232, 115, 90, 0.12); animation: none; }
.chip.rec[data-fault="1"] .dot { background: var(--warn); box-shadow: none; }
@keyframes recring {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255, 77, 77, 0.5); }
  50% { box-shadow: 0 0 0 6px rgba(255, 77, 77, 0); }
}
@keyframes recdot { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
@media (prefers-reduced-motion: reduce) {
  .chip.rec[data-live="1"], .chip.rec[data-live="1"] .dot { animation: none; }
}

/* GO LIVE. It wears the whole REC pill (.chip.rec) — shape, colours, and every
   data-live / data-can / data-arm / data-fault state — and adds only a min width,
   so the label swapping between GO LIVE and END 00:12 never makes the header jump.
   Loud on purpose: this is the room's one obvious action. */
.chip.golive { min-width: 96px; justify-content: center; }
.chip.golive[data-live="1"] { min-width: 108px; }

/* SHARE SCREEN. The quiet partner beside GO LIVE: a resting bordered chip with a
   dim screen glyph, understated so GO LIVE stays the loud action. While sharing it
   fills to gold with a soft ring (data-on), the same on-state grammar the av chips
   and burger rows use, so "I am sharing" reads at a glance. */
.chip.share { color: var(--ink-dim); letter-spacing: 0.02em; }
.chip.share .rico { opacity: 0.85; }
.chip.share:hover { color: var(--ink); border-color: var(--line-hot); }
.chip.share[data-on="1"] {
  color: var(--gold); border-color: var(--gold);
  background: rgba(232, 180, 90, 0.12);
  box-shadow: 0 0 0 1px rgba(232, 180, 90, 0.35);
}
.chip.share[data-on="1"] .rico { opacity: 1; }

/* The burger. Deliberately quiet, the subtractive half of the pair: a plain
   bordered square with three hairline bars, no colour of its own until hovered.
   The granular camera / mic / screen / speaker control lives behind it so the
   header can stay one loud action instead of five chips. */
.chip.burger {
  padding: 0; width: 30px; height: 30px;
  justify-content: center; border-radius: 6px;
  color: var(--ink-dim);
}
.chip.burger:hover { color: var(--ink); border-color: var(--line-hot); }
.chip.burger .bars { display: inline-flex; flex-direction: column; gap: 3px; width: 14px; }
.chip.burger .bars span { display: block; height: 1.5px; border-radius: 1px; background: currentColor; }

/* The burger menu's state dots ride the room's stacked context menu (.personChip
   .col). The leading ● / ○ is plain text so it aligns without a glyph font; a
   touch of gold on the "on" rows makes a lit device readable at a glance. */
.personChip.col button { font-variant-numeric: tabular-nums; }

/* The self portrait and the voice mark, pinned over the canvas by the frame
   loop. They hang by their foot off the same head anchor the caret is drawn
   from, so they walk when the body walks. Never a click target: the world is
   underneath them. */
.rtcSelf {
  position: fixed;
  left: 0; top: 0;
  z-index: 4;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0;
  pointer-events: none;
  will-change: transform;
}

/* Hard-edged, small, and framed in the room's own furniture: the 2px ink border
   and the 1px top rule are the name tag's language, one size up. No radius, no
   blur, no glow. The FRAME belongs to the pixel world; the face deliberately
   does not get pixelated, because the face is the one thing here whose fidelity
   is the entire point of walking over to someone. */
.rtcCam {
  position: relative;
  padding: 2px;                        /* the mat: this room prints its photographs */
  background: #f2efe6;                 /* the same parchment the speech bubble is cut from */
  border: 1px solid #14141f;           /* and the same ink hairline around it */
  line-height: 0;
}
.rtcCam::after {                       /* the top rule: parchment for members, gold for the host */
  content: '';
  position: absolute;
  left: 0; right: 0; top: 0; height: 2px;
  background: #f2efe6;
}
.rtcCam[data-host="1"]::after { background: var(--gold); }
.rtcCam video {
  display: block;
  width: 64px; height: 48px;
  object-fit: cover;
  transform: scaleX(-1);               /* a mirror is what you expect of yourself */
  filter: saturate(0.85) contrast(1.05);   /* seated in the palette, not lied about */
  background: #0b0b11;
}

/* Mic on, camera off is a normal way to be in a room, not a broken way to be on
   camera: it gets its own mark rather than a black rectangle apologising. Three
   gold bars that move with your real level, quantised to whole pixels so they
   step like everything else here. */
.rtcMic {
  display: flex;
  align-items: flex-end;
  gap: 2px;
  height: 13px;
  padding: 1px 3px 2px;
  background: rgba(11, 11, 17, 0.92);
  border-top: 1px solid var(--gold);
}
.rtcMic i { width: 2px; height: 2px; background: var(--gold); }

/* The shared screen, for the audience. A face is small furniture over a head; a
   screen is the thing the room came to look at, so it is large and centred and
   framed in the room's own language: the 2px ink border and the parchment mat
   the portraits and speech bubbles are cut from, one size up. Auto-shown the
   moment a screen track arrives; a click toggles the full-viewport read. */
.rtcScreenStage {
  position: fixed;
  left: 50%;
  top: 72px;                              /* clear of the header */
  transform: translateX(-50%);
  width: min(68vw, 880px);
  max-width: calc(100vw - 24px);
  z-index: 5;
  padding: 3px;
  background: #f2efe6;
  border: 2px solid #14141f;
  line-height: 0;
  cursor: zoom-in;
  box-shadow: 0 8px 28px rgba(11, 11, 17, 0.45);
}
.rtcScreenStage::after {                  /* the gold top rule: this is the host's broadcast */
  content: '';
  position: absolute;
  left: 0; right: 0; top: 0; height: 3px;
  background: var(--gold);
}
.rtcScreenStage video {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: contain;
  background: #0b0b11;
}
.rtcScreenStage.max {
  top: 12px;
  width: calc(100vw - 24px);
  cursor: zoom-out;
}

/* ---- the Replays chip + panel ----------------------------------------- */
/* One understated navbar affordance, then a world-facing panel: the talk you
   missed, played inside the room. No white modal; the app's own dark/wood chrome. */
.chip.replays { color: var(--ink-dim); letter-spacing: 0.04em; }
.chip.replays .rico { color: var(--gold); opacity: 0.9; }
.chip.replays:hover { color: var(--ink); border-color: var(--line-hot); }

.replays-panel {
  position: fixed;
  inset: 0;
  z-index: 95;                 /* over the world + HUD, under the gate (100) and the ring's own modality */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: max(16px, env(safe-area-inset-top)) 16px max(16px, env(safe-area-inset-bottom));
  background:
    radial-gradient(120% 90% at 50% 0%, rgba(232, 180, 90, 0.06), transparent 60%),
    rgba(4, 4, 8, 0.78);
  backdrop-filter: blur(2px);
}
.rp-card {
  position: relative;   /* the delete confirm scrim (#rpConfirm) fills this card */
  display: flex;
  flex-direction: column;
  width: min(1000px, 100%);
  height: min(680px, 100%);
  background: linear-gradient(180deg, #12100c, #0c0b10 55%, #08080d);   /* a warm wood-dark, not a generic panel */
  border: 1px solid var(--line-hot);
  border-radius: 5px;
  box-shadow: 0 30px 90px rgba(0, 0, 0, 0.65), 0 0 0 1px rgba(232, 180, 90, 0.05);
  overflow: hidden;
}
.rp-head {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 14px;
  border-bottom: 1px solid var(--line);
  background: rgba(0, 0, 0, 0.22);
}
.rp-mark {
  width: 18px; height: 18px; flex: none;
  background:
    linear-gradient(var(--gold), var(--gold)) 0 3px / 18px 2px no-repeat,
    linear-gradient(var(--cyan), var(--cyan)) 0 9px / 13px 2px no-repeat,
    linear-gradient(var(--cyan), var(--cyan)) 0 15px / 8px 2px no-repeat;
}
.rp-title {
  margin: 0; font-size: 15px; font-weight: 700; letter-spacing: 0.01em;
  color: var(--ink);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
.rp-sub { color: var(--ink-dim); font-size: 11px; letter-spacing: 0.04em; }
.rp-close {
  margin-left: auto;
  width: 28px; height: 28px; line-height: 1;
  font: inherit; font-size: 20px;
  color: var(--ink-dim); background: transparent;
  border: 1px solid var(--line); border-radius: 3px; cursor: pointer;
}
.rp-close:hover { color: var(--ink); border-color: var(--line-hot); }
.rp-close:focus-visible { outline: 2px solid var(--gold); outline-offset: 1px; }

.rp-body { display: flex; flex: 1; min-height: 0; }
.rp-shelf {
  width: 260px; flex: none;
  border-right: 1px solid var(--line);
  overflow-y: auto; scrollbar-width: thin; scrollbar-color: var(--line) transparent;
  background: rgba(0, 0, 0, 0.14);
}
.rp-list { list-style: none; margin: 0; padding: 6px; }
.rp-row {
  display: flex; flex-direction: column; gap: 2px;
  width: 100%; text-align: left;
  padding: 9px 10px; margin-bottom: 4px;
  font: inherit; color: var(--ink);
  background: rgba(14, 14, 22, 0.6);
  border: 1px solid var(--line); border-radius: 3px; cursor: pointer;
}
.rp-row:hover { border-color: var(--line-hot); }
.rp-row.sel { border-color: var(--gold); box-shadow: inset 0 0 0 1px rgba(232, 180, 90, 0.5); }
.rp-row:focus-visible { outline: 2px solid var(--gold); outline-offset: 1px; }
.rp-rtitle { font-size: 12.5px; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rp-rmeta { font-size: 11px; color: var(--ink-dim); }
.rp-live { align-self: flex-start; margin-top: 2px; font-size: 9px; font-weight: 800; letter-spacing: 0.08em; color: var(--warn); }
.rp-empty { padding: 18px; color: var(--ink-dim); font-size: 12px; }

.rp-stage { flex: 1; min-width: 0; display: flex; flex-direction: column; padding: 14px; overflow-y: auto; scrollbar-width: thin; scrollbar-color: var(--line) transparent; }
.rp-placeholder { margin: auto; color: var(--ink-dim); font-size: 13px; }
.rp-player { display: flex; flex-direction: column; min-height: 0; }
/* A framed 16:9 stage that can never collapse to nothing: even before metadata
   loads, or when a talk has no playable recording, the box holds its shape so the
   player always reads as a player. The video letterboxes inside it (object-fit),
   and rp-videomsg speaks in the same box when there is nothing to play. */
.rp-videowrap { position: relative; display: flex; align-items: center; justify-content: center;
  background: #000; border: 1px solid var(--line); border-radius: 3px; overflow: hidden;
  aspect-ratio: 16 / 9; max-height: 46vh; min-height: 180px; }
.rp-video { display: block; width: 100%; height: 100%; max-height: 46vh; object-fit: contain; background: #000; }
.rp-video[hidden] { display: none; }
.rp-videomsg { margin: 0; padding: 0 24px; text-align: center; color: var(--ink-dim); font-size: 13px; }
.rp-nowtitle { margin: 12px 0 2px; font-size: 15px; font-weight: 700; color: var(--ink);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }
.rp-nowmeta { margin: 0 0 10px; font-size: 11.5px; color: var(--ink-dim); }
/* The one-sentence Sonnet summary: a calm caption under the meta, gold hairline
   at the left so it reads as the room's own note, not the talk's own words. */
.rp-summary {
  margin: 0 0 12px; padding: 8px 12px;
  font-size: 13px; line-height: 1.5; color: var(--ink);
  background: rgba(232, 180, 90, 0.05);
  border-left: 2px solid var(--gold); border-radius: 0 2px 2px 0;
}
.rp-tx-head { font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-dim); margin-bottom: 6px; }
.rp-transcript { display: flex; flex-direction: column; gap: 1px; }
.rp-seg {
  display: flex; gap: 10px; align-items: baseline;
  width: 100%; text-align: left;
  padding: 5px 8px;
  font: inherit; font-size: 12.5px; line-height: 1.5; color: var(--ink);
  background: transparent; border: 0; border-radius: 2px; cursor: pointer;
}
.rp-seg:hover { background: rgba(232, 180, 90, 0.08); }
.rp-seg:focus-visible { outline: 2px solid var(--gold); outline-offset: -2px; }
.rp-ts { flex: none; width: 42px; color: var(--gold); font-variant-numeric: tabular-nums; font-size: 11px; }
.rp-segtext { color: var(--ink); }

/* Downloads. A quiet row above the transcript: save the video (.webm) or the
   transcript (.txt). Gold on hover, the room's own affordance grammar. */
.rp-actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 10px; }
.rp-dl {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 5px 10px;
  font: inherit; font-size: 11.5px; font-weight: 700; letter-spacing: 0.02em;
  color: var(--ink); background: transparent; text-decoration: none;
  border: 1px solid var(--line-hot); border-radius: 2px;
  cursor: pointer;
}
.rp-dl:hover { color: var(--gold); border-color: var(--gold); }
.rp-dl:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

/* Delete (host-only). A quiet danger link until hover: destructive, so it does
   not compete with the transcript, but it never hides what it is. */
.rp-delete {
  align-self: flex-start;
  margin: 0 0 12px;
  padding: 5px 10px;
  font: inherit; font-size: 11.5px; font-weight: 700; letter-spacing: 0.02em;
  color: var(--danger); background: transparent;
  border: 1px solid rgba(255, 77, 77, 0.4); border-radius: 2px;
  cursor: pointer;
}
.rp-delete:hover { color: #fff; background: var(--danger); border-color: var(--danger); }
.rp-delete:focus-visible { outline: 2px solid var(--danger); outline-offset: 2px; }

/* The red confirm. Its own scrim over the Replays card, so nothing else can be
   clicked while a permanent erase is one button away. */
.rp-confirm {
  position: absolute; inset: 0; z-index: 5;
  display: flex; align-items: center; justify-content: center;
  padding: 20px;
  background: rgba(40, 6, 6, 0.72);
  backdrop-filter: blur(2px);
}
.rp-confirm-card {
  width: min(420px, 100%);
  padding: 22px 22px 18px;
  text-align: center;
  background: var(--panel);
  border: 1px solid var(--danger);
  border-radius: 3px;
  box-shadow: 0 0 0 1px rgba(255, 77, 77, 0.25), 0 18px 60px rgba(0, 0, 0, 0.6);
}
.rp-confirm-badge { font-size: 28px; line-height: 1; color: var(--danger); }
.rp-confirm-title { margin: 10px 0 8px; font-size: 16px; font-weight: 800; color: #fff; }
.rp-confirm-body { margin: 0 0 8px; font-size: 12.5px; line-height: 1.5; color: var(--ink); }
.rp-confirm-body strong { color: #ffb3b3; }
.rp-confirm-name { display: block; margin-bottom: 6px; color: #fff !important; font-size: 13.5px; }
.rp-confirm-warn {
  margin: 0 0 16px; padding: 7px 10px;
  font-size: 11.5px; font-weight: 700; color: #ffd0d0;
  background: rgba(255, 77, 77, 0.1);
  border: 1px solid rgba(255, 77, 77, 0.35); border-radius: 2px;
}
.rp-confirm-actions { display: flex; gap: 10px; justify-content: center; }
.rp-confirm-keep, .rp-confirm-del {
  padding: 8px 16px;
  font: inherit; font-size: 12.5px; font-weight: 700;
  border-radius: 2px; cursor: pointer;
}
.rp-confirm-keep {
  color: var(--ink); background: transparent; border: 1px solid var(--line-hot);
}
.rp-confirm-keep:hover { color: #fff; border-color: var(--ink); }
.rp-confirm-del {
  color: #fff; background: var(--danger); border: 1px solid var(--danger);
}
.rp-confirm-del:hover { background: #ff2d2d; border-color: #ff2d2d; }
.rp-confirm-del:disabled, .rp-confirm-keep:disabled { opacity: 0.55; cursor: default; }
.rp-confirm-keep:focus-visible, .rp-confirm-del:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }

@media (max-width: 720px) {
  .rp-card { height: 100%; border-radius: 0; }
  .rp-body { flex-direction: column; }
  .rp-shelf { width: 100%; max-height: 34vh; border-right: 0; border-bottom: 1px solid var(--line); }
  .rp-videowrap { max-height: 34vh; }
  .rp-video { max-height: 34vh; }
}
@media (prefers-reduced-motion: reduce) {
  .replays-panel { backdrop-filter: none; }
}

/* ---------------------------------------------------------------------------
   STAGE 2: the camera-off warning bar. Calm, not alarming (Jobs bar: honest and
   kind). Fixed near the top-centre, over the world and HUD but UNDER the gate
   (100), so an actual eviction gate always wins the screen. Amber, not red: this
   is a nudge with a deadline, not an error.
   --------------------------------------------------------------------------- */
.camWarn {
  position: fixed;
  top: 76px;
  left: 50%;
  transform: translateX(-50%) translateY(-8px);
  z-index: 98;
  display: flex;
  align-items: center;
  gap: 10px;
  max-width: min(92vw, 560px);
  padding: 11px 16px;
  border: 1px solid var(--gold);
  border-radius: 12px;
  background: linear-gradient(180deg, rgba(24, 20, 12, 0.96), rgba(16, 14, 10, 0.96));
  box-shadow: 0 10px 34px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(232, 180, 90, 0.18);
  backdrop-filter: saturate(150%) blur(10px);
  color: var(--ink);
  font-size: 13.5px;
  line-height: 1.35;
  opacity: 0;
  pointer-events: none;
  transition: opacity 180ms ease, transform 180ms ease;
}
.camWarn.show {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}
.camWarnDot {
  flex: 0 0 auto;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--gold);
  box-shadow: 0 0 0 3px rgba(232, 180, 90, 0.2);
  animation: camWarnPulse 1s steps(2, end) infinite;
}
.camWarnText { font-weight: 600; letter-spacing: 0.005em; }
@keyframes camWarnPulse { 50% { opacity: 0.3; } }
@media (prefers-reduced-motion: reduce) {
  .camWarn { transition: opacity 120ms ease; transform: translateX(-50%); }
  .camWarn.show { transform: translateX(-50%); }
  .camWarnDot { animation: none; }
}
