/* Pinch Interpreter — the listener's page.
 *
 * The palette, type and geometry are the Pinch brand's, not this page's
 * invention: tokens from pinch-brand/pinch-tokens.css, the screen design from
 * pinch-brand/sidecar-frontend.html. Before this the page used a generic blue
 * accent on flat white with square buttons, which is what every transcription
 * product on the internet looks like — including the one whose API we happen
 * to call. Looking like your own supplier is a branding failure, not a style
 * preference.
 *
 * Three rules carried over from the brand book:
 *
 *   Warm paper, not white. `--paper` is the Pinch ground; cards lift off it
 *   and panels recess beneath it, so the page reads three planes deep.
 *
 *   Colour only where it carries meaning. On this page that is exactly two
 *   things: the live dot, and the per-speaker swatch. The swatch earns it
 *   because giving every speaker their own voice is the one thing this does
 *   that nothing else does.
 *
 *   Display type is Bricolage Grotesque, body is IBM Plex Sans, and anything
 *   numeric or label-shaped is IBM Plex Mono in caps with letterspacing.
 */

:root {
  color-scheme: light dark;

  --paper: #FBFAF8;
  --card: #FFFFFF;
  --panel: #F0F2F5;
  --ink: #1A202E;
  --mute: #5E6778;
  --rule: #E1E4EA;
  --blue: #4078A5;
  --peri: #BDC1ED;
  --good: #2F7D62;
  --warn: #A8752F;
  --shadow: 0 1px 2px rgb(26 32 46 / .05), 0 8px 24px -12px rgb(26 32 46 / .14);

  /* Named for their job so the rest of the sheet reads in page terms. */
  --bg: var(--paper);
  --surface: var(--panel);
  --line: var(--rule);
  --line-strong: #CDD3DD;
  --ink-soft: var(--mute);
  --ink-faint: #8A93A4;
  --accent: var(--blue);
  /* A hover tint and a soft accent wash. Both have to follow the theme: as
     literal fallbacks they stayed light-mode values in dark mode, which is
     how hovering a menu item produced near-black text on a near-black
     background. */
  --hover: rgb(26 32 46 / .05);
  --accent-soft: rgb(90 145 191 / .10);
  --live: var(--good);
  --on-live: #FFFFFF;

  --sans: "IBM Plex Sans", system-ui, -apple-system, BlinkMacSystemFont,
          "Segoe UI", Roboto, sans-serif;
  --disp: "Bricolage Grotesque", "IBM Plex Sans", system-ui, sans-serif;
  --mono: "IBM Plex Mono", ui-monospace, Menlo, monospace;

  --pad: clamp(16px, 3.4vw, 32px);
}

@media (prefers-color-scheme: dark) {
  :root {
    --paper: #0E121B;
    --card: #171C26;
    --panel: #1B2130;
    --ink: #E2E4F3;
    --mute: #8F99AE;
    --rule: #252B37;
    --blue: #5A91BF;
    --peri: #BDC1ED;
    --good: #5FBF9B;
    --warn: #D9A75C;
    --shadow: 0 1px 2px rgb(0 0 0 / .4), 0 8px 24px -12px rgb(0 0 0 / .6);
    --hover: rgb(226 228 243 / .08);
    --accent-soft: rgb(90 145 191 / .18);

    --line-strong: #39414C;
    --ink-faint: #6F7985;
    --on-live: #08130E;
  }
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}
h1, h2, .disp {
  font-family: var(--disp);
  font-optical-sizing: auto;
  letter-spacing: -0.032em;
}

body { overflow: hidden; }
.sprite { position: absolute; width: 0; height: 0; }
button, input, select { font: inherit; color: inherit; }


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

header {
  display: flex;
  align-items: center;
  gap: clamp(10px, 2vw, 20px);
  padding: clamp(12px, 2vw, 18px) var(--pad);
  border-bottom: 1px solid var(--line);
}

.wordmark {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: clamp(15px, 1.5vw, 18px);
  font-weight: 600;
  letter-spacing: -0.02em;
  white-space: nowrap;
  /* The console's wordmark is an <a> (it goes back to the Sidecar page) while
     the other views use a <div>. Stated here so the link doesn't arrive blue
     and underlined on the one page that links. */
  color: inherit;
  text-decoration: none;
}
a.wordmark:hover span { color: var(--ink); }
/* Beta, beside the name.
 *
 * On every page that carries the wordmark rather than only the first one: a
 * listener who was sent a link never sees the front page, and they are the
 * person most likely to mistake a rough edge for a broken product.
 *
 * Sized off the wordmark rather than the page, so it stays in proportion as
 * the wordmark scales with the viewport. */
.wordmark .beta {
  margin-left: 2px;
  padding: 0.15em 0.5em;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: 0.62em;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--mute);
  text-transform: uppercase;
}
a.wordmark:hover .beta { color: var(--mute); }

.wordmark img {
  height: 1.25em;
  width: auto;
  display: block;
}
/* No dark-mode filter on the mark, deliberately.
 *
 * It used to be `invert(1) brightness(1.6)`, on the assumption that the logo
 * is drawn dark and would vanish on a dark background. It is not: it is brand
 * blue (#4f89b7) and lavender (#bdc1ed), both light enough to read on the
 * dark ground as they are. Inverting a coloured mark does not brighten it, it
 * changes its hue — the blue came out orange — so the one thing on the page
 * that has to look the same everywhere was the one thing that did not. */

/* Right-hand links. Deliberately one quiet link rather than a status light:
   the health of the service is our problem, and a green dot saying "ready"
   only ever tells someone what they already assumed. */
.navlink {
  font-size: 14px;
  color: var(--ink-faint);
  text-decoration: none;
}
.navlink:hover { color: var(--ink); }
.wordmark span { color: var(--ink-faint); font-weight: 400; }

.spacer { flex: 1; }

/* The only always-on colour on the page. */
.status {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 5px 11px 5px 9px;
  border-radius: 999px;
  background: var(--surface);
  font-size: clamp(12px, 1.2vw, 14px);
  font-weight: 500;
  white-space: nowrap;
}
.status i {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--ink-faint);
  flex: none;
}
.status.live { color: var(--live); }
.status.live i { background: var(--live); }
.status.warn { color: var(--warn); }
.status.warn i { background: var(--warn); }

/* ==================================================================== */
/* The listener's app: a phone-shaped surface first, a window second.    */
/* ==================================================================== */

/* Four bands, and only one of them scrolls.
 *
 *   bar     who this is and whether it is working
 *   stage   the meeting, when there is one to show
 *   caps    what is being said, over time — the reason the page exists
 *   ctl     the controls, at the bottom, where a thumb is
 *
 * `caps` is the flexible band. Making the stage flexible instead lets a 16:9
 * video eat a phone screen and leave two lines of caption under it, which is
 * the wrong way round: they can already see the room, they cannot understand
 * it.
 *
 * `dvh` rather than `vh` because mobile Safari's `vh` includes the browser
 * chrome it then draws on top of, so a `100vh` control bar sits underneath the
 * address bar and cannot be tapped.
 */
.app {
  display: grid;
  grid-template-areas: "bar" "stage" "caps" "hint" "ctl";
  grid-template-rows: auto auto minmax(0, 1fr) auto auto;
  height: 100vh;
  height: 100dvh;
}

header.bar { grid-area: bar; padding: 10px var(--pad); gap: 10px; }
header.bar .wordmark { min-width: 0; overflow: hidden; }
header.bar .status { flex: none; }
/* Below about 420px the wordmark's second word is the first thing to go: the
   listener knows what site they are on, and the status does not. */
@media (max-width: 420px) { header.bar .wordmark span { display: none; } }

/* ------------------------------------------------------------------ stage */

/* The meeting itself. Black regardless of theme: it is a video, and letterbox
   bars in "surface grey" read as a broken layout rather than as the edge of
   the frame. */
.stage {
  grid-area: stage;
  position: relative;
  background: #000;
  line-height: 0;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.stage[hidden] { display: none; }
/* A third of the screen, not a half. The transcript is what somebody cannot
   get anywhere else, and a 16:9 video at full width takes the room it needs
   from the only thing on the page they came for. Tapping collapses it further,
   for a meeting whose faces are not the point. */
.stage .stage-media {
  display: block;
  width: 100%;
  height: auto;
  max-height: 32dvh;
  aspect-ratio: 16 / 9;
  object-fit: contain;
  margin: 0 auto;
  transition: max-height .2s ease;
}
.app.video-small .stage .stage-media { max-height: 15dvh; }
@media (prefers-reduced-motion: reduce) { .stage .stage-media { transition: none; } }

/* A phone held sideways has ~390px of height for everything. Stacking a 16:9
   video on top of the captions there leaves four lines of text, so the two go
   side by side instead — the same arrangement a wide desktop gets, for the
   same reason. */
@media (max-height: 520px) and (min-width: 640px) {
  .app.has-video {
    grid-template-areas: "bar bar" "stage caps" "hint hint" "ctl ctl";
    grid-template-columns: minmax(0, 1fr) minmax(15rem, 1fr);
    grid-template-rows: auto minmax(0, 1fr) auto auto;
  }
  .app.has-video .stage { display: grid; place-items: center; }
  .app.has-video .stage .stage-media { max-height: 100%; }
  .app.has-video .caps { border-left: 1px solid var(--line); }
  header.bar { padding: 6px var(--pad); }
  .ctl { padding: 3px 0 2px; }
}
/* Why the video is behind, said quietly and over the picture rather than in a
   line of its own — a caption band under a video is a band of screen a phone
   does not have. It is answering a question somebody asks once, so it fades
   out rather than sitting on a participant's face for the whole meeting. */
.stage-note {
  animation: noteFade 1.2s ease 9s forwards;
  position: absolute;
  left: 10px; bottom: 10px;
  padding: 4px 9px;
  border-radius: 999px;
  background: rgb(0 0 0 / 0.55);
  color: #dfe6ee;
  font-size: 11px;
  line-height: 1.4;
  letter-spacing: 0.01em;
  backdrop-filter: blur(6px);
  pointer-events: none;
}
.stage-note:empty { display: none; }
@keyframes noteFade { to { opacity: 0; visibility: hidden; } }
@media (prefers-reduced-motion: reduce) { .stage-note { animation-duration: 0s; } }

/* ----------------------------------------------------------------- captions */

/* One scrolling column, newest at the bottom, and it *accumulates* — the whole
   point of a transcript is being able to scroll back through a fast-moving
   conversation and find the number somebody just said.
 *
 * The layout is the brand's: speaker in a fixed left column with their voice
 * swatch, translation as the line, original underneath in mute. The original
 * goes below because a listener checking a figure wants the source and burying
 * it costs trust — but the translation carries a min-height so a line that has
 * not been translated yet does not shove the page around as the words land. */
.caps {
  grid-area: caps;
  /* The row layout depends on how wide *this column* is, not how wide the
     window is — with video beside it and the chat open over it, a 1440px
     monitor can leave the transcript narrower than a phone. */
  container-type: inline-size;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding: clamp(14px, 3vw, 24px) var(--pad) clamp(18px, 4vw, 30px);
  scrollbar-width: thin;
  min-height: 0;
}
.caps::-webkit-scrollbar { width: 10px; }
.caps::-webkit-scrollbar-thumb {
  background: var(--line-strong);
  border-radius: 5px;
  border: 3px solid var(--bg);
}
.caps-empty {
  margin: 0;
  padding: clamp(20px, 8vw, 48px) 0;
  color: var(--ink-faint);
  font-size: clamp(15px, 4vw, 17px);
  text-align: center;
}

.cap {
  display: grid;
  grid-template-columns: 116px minmax(0, 1fr);
  gap: 14px;
  padding: clamp(10px, 2.4vw, 14px) 0;
}
/* Under about 460px of column there is no room for a name gutter beside the
   text, so the name becomes a line above it rather than squeezing every
   sentence into four words per line. */
@container (max-width: 460px) {
  .cap { grid-template-columns: minmax(0, 1fr); gap: 4px; }
  /* One column: the name row has the whole width, so the time sits beside the
     name again rather than on a line of its own. */
  .cap-who .at { flex: 0 0 auto; }
}
/* Fallback for browsers without container queries: the window is the only
   width they can see. */
@media (max-width: 560px) {
  .cap { grid-template-columns: minmax(0, 1fr); gap: 4px; }
  .cap-who .at { flex: 0 0 auto; }
}

/* Swatch and name on one line, the time under them. Side by side, a 116px
   gutter has to fit "Priya Nair" and "05:00 PM" at once and truncates the only
   half that identifies anybody. */
.cap-who {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 2px 6px;
  align-content: start;
  font-size: 12.5px;
  font-weight: 600;
  min-width: 0;
}
.cap-who .at { flex: 0 0 100%; }
.cap-who .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The only colour on the page besides the live dot, and it is carrying the one
   thing this product does that nothing else does: this person has their own
   voice, and it is this one. */
.cap-who .swatch {
  width: 7px; height: 7px;
  border-radius: 50%;
  flex: none;
  align-self: center;
}
.cap-who .at {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 400;
  color: var(--ink-faint);
  white-space: nowrap;
}

.cap-text {
  margin: 0;
  font-size: clamp(16px, 4.1vw, 18px);
  line-height: 1.45;
  min-height: 1.45em;
  text-wrap: pretty;
}
.cap-src {
  margin: 3px 0 0;
  font-size: 12.5px;
  line-height: 1.4;
  color: var(--mute);
}
.cap-src:empty { display: none; }

/* The listener's own voice, going the other way. Marked so it is obviously
   theirs and not something the room said. */
.cap.self .cap-who .swatch { background: var(--ink) !important; }
.cap.self .cap-who .name::after {
  content: " → meeting";
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 400;
  letter-spacing: .06em;
  color: var(--ink-faint);
}

/* Still being said. Muted rather than a different size, so the line does not
   resize under the reader when it finalises. */
.cap.draft .cap-text { color: var(--mute); }
.cap.skipped .cap-text { color: var(--ink-faint); }
.cap .skipped-note {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--warn);
}

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

/* Round, thumb-sized, and pinned to the bottom edge with the home indicator
   accounted for. Every one of these is optional except the volume: a meeting
   with no bot has no chat and nothing to speak through, and a control that
   cannot do anything is worse than a missing one. */
.controls {
  grid-area: ctl;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(2px, 1.5vw, 14px);
  padding: 10px clamp(6px, 2vw, 20px);
  padding-bottom: max(10px, env(safe-area-inset-bottom));
  border-top: 1px solid var(--line);
  background: var(--surface);
}

.ctl {
  position: relative;
  display: grid;
  place-items: center;
  gap: 3px;
  flex: 1 1 0;
  min-width: 0;
  max-width: 76px;
  padding: 7px 2px 5px;
  border: 0;
  border-radius: 14px;
  background: none;
  color: var(--ink-soft);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.ctl[hidden] { display: none; }
.ctl svg { width: 23px; height: 23px; }
.ctl-label {
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.005em;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ctl:active { background: var(--line); }
.ctl:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ctl.on { color: var(--accent); }

/* A dot on the chat button when something arrived while the sheet was shut.
   The alternative is opening the sheet for them, which takes the screen away
   from the captions to show a line they may not care about. */
/* An unread count, not a bare dot: "3 people said something" and "somebody
   said something" are different amounts of reason to open a panel. */
.ctl .dot {
  position: absolute;
  top: 2px; right: 10px;
  min-width: 17px; height: 17px;
  padding: 0 4px;
  border-radius: 999px;
  background: var(--blue);
  color: #fff;
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 500;
  line-height: 17px;
  text-align: center;
}
.ctl .dot[hidden] { display: none; }

/* The microphone. Filled when live, because "am I on?" is the one question a
   control like this has to answer from across a room. */
.ctl .on { display: none; }
.ctl[aria-pressed="true"] .on { display: block; }
.ctl[aria-pressed="true"] .off { display: none; }
.ctl.mic[aria-pressed="true"] { color: var(--on-live); }
/* Filled directly rather than with a `z-index: -1` pseudo-element, which
   paints behind the control bar's own background instead of behind the
   button — the oldest way to make a button look like it has no fill. */
.ctl.mic[aria-pressed="true"] { background: var(--live); }
.ctl.mic.starting { opacity: 0.55; }

/* A ring that breathes with the listener's own voice. It is the answer to the
   only real failure this control has — a microphone that is open and deaf,
   which otherwise looks exactly like an open one. */
.ctl.mic .ring {
  position: absolute;
  inset: -3px;
  border-radius: 16px;
  border: 2px solid var(--live);
  opacity: 0;
  transform: scale(1);
  pointer-events: none;
}
.ctl.mic[aria-pressed="true"] .ring {
  opacity: calc(0.15 + var(--level, 0) * 0.85);
  transform: scale(calc(1 + var(--level, 0) * 0.22));
}

/* ------------------------------------------------------------------- sheets */

/* Two different things, deliberately shaped differently.
 *
 * Sound is a couple of faders somebody sets once: a small panel, anchored to
 * the control that opened it, gone as soon as it is used.
 *
 * Chat is a conversation, and a conversation wants a column — tall, scrolled,
 * with a composer pinned under it. It comes in from the right on a desktop and
 * takes the full height on a phone, which is what every chat anyone has used
 * does. It was a bottom sheet showing three lines, which is a notification
 * tray pretending to be a chat.
 */
.scrim {
  position: fixed;
  inset: 0;
  background: rgb(14 18 27 / 0.38);
  opacity: 0;
  visibility: hidden;
  transition: opacity .2s ease, visibility .2s;
  z-index: 30;
}
.scrim.open { opacity: 1; visibility: visible; }

.sheet {
  position: fixed;
  z-index: 31;
  background: var(--card);
  box-shadow: var(--shadow);
  visibility: hidden;
  overflow: hidden;
  transition: transform .24s cubic-bezier(.32, .72, 0, 1), opacity .2s, visibility .24s;
}
.sheet.open { visibility: visible; }
@media (prefers-reduced-motion: reduce) {
  .sheet, .scrim { transition-duration: 0s; }
}

/* Chat: a column from the right, full height. */
#chatSheet {
  top: 0; right: 0; bottom: 0;
  width: min(26rem, 100vw);
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  border-left: 1px solid var(--rule);
  padding-bottom: env(safe-area-inset-bottom);
  transform: translateX(100%);
}
#chatSheet.open { transform: translateX(0); }

/* Sound: a small panel above the control bar. */
#mixSheet {
  left: 50%; bottom: 0;
  width: min(30rem, calc(100vw - 2 * var(--pad)));
  transform: translate(-50%, 101%);
  border-radius: 14px 14px 0 0;
  border: 1px solid var(--rule);
  border-bottom: 0;
  display: grid;
  gap: 12px;
  padding: 6px var(--pad) max(18px, env(safe-area-inset-bottom));
}
#mixSheet.open { transform: translate(-50%, 0); }

@media (min-width: 700px) {
  #mixSheet {
    bottom: 96px;
    border-radius: 14px;
    border-bottom: 1px solid var(--rule);
    opacity: 0;
    transform: translate(-50%, 10px);
  }
  #mixSheet.open { opacity: 1; transform: translate(-50%, 0); }
}

.sheet-head {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px var(--pad) 12px;
  border-bottom: 1px solid var(--rule);
}
#mixSheet .sheet-head { padding: 12px 0 10px; border-bottom: 0; }
.sheet-head h2 {
  margin: 0;
  font-family: var(--disp);
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -0.028em;
}
.grip {
  position: absolute;
  top: 5px; left: 50%;
  transform: translateX(-50%);
  width: 38px; height: 4px;
  border-radius: 2px;
  background: var(--line-strong);
}
@media (min-width: 700px) { .grip { display: none; } }
#chatSheet .grip { display: none; }

.iconbtn {
  margin-left: auto;
  display: grid;
  place-items: center;
  width: 34px; height: 34px;
  border: 0;
  border-radius: 50%;
  background: var(--panel);
  color: var(--mute);
  cursor: pointer;
}
.iconbtn svg { width: 18px; height: 18px; }
.iconbtn:active { background: var(--rule); }

/* Scoped to the sheet: `.hint` is also the console's and the host's, beside
   their step actions, and this is not a rule about those. */
.sheet .hint {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--mute);
}

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

.sheet .chat-log {
  overflow-y: auto;
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 14px var(--pad);
  font-size: 14px;
  line-height: 1.45;
}
.sheet .chat-log:empty::after {
  content: "Nothing in the chat yet.";
  color: var(--ink-faint);
  margin: auto;
}

.msg { max-width: 88%; }
.msg .who {
  display: block;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--mute);
  margin-bottom: 3px;
}
.msg .bubble {
  display: block;
  padding: 9px 12px;
  border-radius: 12px;
  background: var(--panel);
  border-bottom-left-radius: 4px;
}
/* Your own, on the right and in ink, the way every chat since 2011 has done
   it. These used to not appear at all: the panel polls Attendee, Attendee does
   not list what the bot itself sent, so a listener typed into a void. */
.msg.mine { align-self: flex-end; }
.msg.mine .who { text-align: right; }
.msg.mine .bubble {
  background: var(--ink);
  color: var(--paper);
  border-bottom-left-radius: 12px;
  border-bottom-right-radius: 4px;
}
/* What the room actually read. Their language is the message; yours is the
   gloss underneath it, which is exactly how the bot posts it. */
.msg .as-sent {
  display: block;
  margin-top: 4px;
  font-size: 12px;
  opacity: .72;
}
.msg.pending { opacity: .55; }

.composer {
  display: grid;
  gap: 8px;
  padding: 12px var(--pad) 14px;
  border-top: 1px solid var(--rule);
  background: var(--card);
}
.composer-row { display: flex; gap: 8px; }
.composer-row input { flex: 1; min-width: 0; }
.composer input {
  padding: 11px 14px;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: var(--card);
  color: var(--ink);
  /* 16px exactly: anything smaller and iOS zooms the whole page in on focus,
     which is how a chat box hijacks a meeting. */
  font-size: 16px;
  outline: none;
}
.composer input:focus { outline: 2px solid var(--blue); outline-offset: -1px; border-color: transparent; }
.composer .name { max-width: 11rem; }

/* ---------------------------------------------------------------- mix sheet */

/* Label, track, value — one row each, because a volume control is not worth a
   block of screen. */
.sheet .fader {
  display: grid;
  grid-template-columns: minmax(6rem, auto) 1fr auto;
  align-items: center;
  gap: 12px;
}
.sheet .fader > label {
  display: contents;
  font-size: 14px;
  font-weight: 600;
}
/* All three on row 1 explicitly. Without it, auto-placement puts the value in
   column 3 and then refuses to go back for the slider in column 2, so every
   fader silently became two rows — a label line and an orphaned track. */
.sheet .fader > label > span:first-child { grid-column: 1; grid-row: 1; }
.sheet .fader .pct {
  grid-column: 3;
  grid-row: 1;
  font-weight: 500;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
  text-align: right;
  min-width: 2.8em;
}
.sheet .fader input[type="range"] { grid-column: 2; grid-row: 1; margin: 0; }

/* Scoped: index.html has its own `.share` section. */
.sheet .share {
  display: flex;
  align-items: center;
  gap: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
}
.sheet .share[hidden] { display: none; }
.sheet .share b { display: block; font-size: 14px; font-weight: 600; }
.sheet .share small { display: block; font-size: 12px; line-height: 1.45; color: var(--ink-faint); }
.sheet .share .btn { flex: none; }

/* ------------------------------------------------------------------- enter */

/* The door. One field, because there is exactly one thing we need before
   somebody can listen, and because a browser will not make a sound until it
   has seen a gesture — so the answer to "what is your name" is also the tap
   that turns the audio on. */
.enter {
  position: fixed;
  inset: 0;
  z-index: 45;
  display: grid;
  place-items: center;
  padding: var(--pad);
  padding-bottom: max(var(--pad), env(safe-area-inset-bottom));
  background: var(--paper);
  overflow-y: auto;
}
.enter[hidden] { display: none; }
.enter-inner { max-width: 26rem; width: 100%; text-align: center; }
.enter-mark { height: 42px; width: auto; margin: 0 0 18px; }
/* Same mark, same reason as .wordmark img above: no inversion. */
.enter h1 {
  margin: 0 0 22px;
  /* Body type, deliberately. The display face is the brand's landing-page
     voice; on a line read once on the way into a meeting it is costume. */
  font-family: var(--sans);
  font-size: clamp(19px, 4.6vw, 22px);
  line-height: 1.3;
  letter-spacing: -0.012em;
  font-weight: 600;
  text-wrap: balance;
  overflow-wrap: anywhere;
}
.enter p {
  margin: 0 0 24px;
  font-size: clamp(14px, 3.8vw, 16px);
  line-height: 1.55;
  color: var(--mute);
  text-wrap: pretty;
}
.enter form { display: grid; gap: 10px; text-align: left; }
.enter-label {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--mute);
}
.enter input {
  width: 100%;
  padding: 13px 16px;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: var(--card);
  color: var(--ink);
  /* 16px exactly, or iOS zooms the page on focus. */
  font-size: 16px;
  outline: none;
}
.enter input:focus { outline: 2px solid var(--blue); outline-offset: -1px; border-color: transparent; }
.enter-note {
  margin: 0 !important;
  font-size: 12.5px;
  color: var(--ink-faint) !important;
}
.enter .btn.big { width: 100%; margin-top: 4px; }

/* Always changeable, never in the way: below the button, because it is a
   correction rather than a decision most people have to make. */
.enter-langs { display: grid; gap: 8px; margin-top: 18px; }
.enter-langs[hidden] { display: none; }
.langpick { display: flex; flex-wrap: wrap; gap: 7px; }
.langbtn {
  font: inherit;
  font-size: 13.5px;
  cursor: pointer;
  border: 1px solid var(--rule);
  background: var(--card);
  color: var(--ink);
  border-radius: 999px;
  padding: 7px 14px;
}
.langbtn:hover { border-color: var(--ink); }
.langbtn.on { border-color: var(--ink); background: var(--ink); color: var(--paper); }
.langbtn:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }

/* -------------------------------------------------------- wider than a phone */

/* Above a laptop's width the video and the transcript sit side by side. It is
   the one layout change worth making: on a phone they compete for the same
   scarce column, and on a monitor they do not have to. */
@media (min-width: 1000px) {
  .app.has-video {
    grid-template-areas: "bar bar" "stage caps" "hint hint" "ctl ctl";
    grid-template-columns: minmax(0, 1.2fr) minmax(23rem, 0.8fr);
    grid-template-rows: auto minmax(0, 1fr) auto auto;
  }
  .app.has-video .stage {
    display: grid;
    place-items: center;
    background: #000;
  }
  .app.has-video .stage .stage-media { max-height: 100%; max-width: 100%; }
  .app.has-video .caps { border-left: 1px solid var(--line); }

  /* With no video the transcript has the whole window, and a line of Spanish
     running 1400 px wide is a line nobody can find the start of. */
  .app:not(.has-video) .caps > * { max-width: 46rem; margin-inline: auto; }

  /* Chat stays docked to the right edge on a monitor too — it is a column of
     conversation, and floating it in the middle of the screen over the
     transcript hides the thing the reply is about. Only the levels panel
     becomes a card, and it is defined with its own rules above. */
  .grip { display: none; }

  .ctl { max-width: 88px; }
  .controls { gap: 14px; }
}

@media (pointer: coarse) { #popOut { display: none; } }

/* A pointer that can hover gets hover states; a thumb does not, and a sticky
   :hover on a tapped button is the oldest bug on the mobile web. */
@media (hover: hover) {
  .ctl:hover { background: var(--line); color: var(--ink); }
  .ctl.mic[aria-pressed="true"]:hover { color: var(--on-live); }
  .iconbtn:hover { background: var(--line); color: var(--ink); }
}

input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 6px;
  border-radius: 3px;
  background: var(--line-strong);
  outline: none;
  cursor: pointer;
}
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 24px; height: 24px;
  border-radius: 50%;
  background: var(--accent);
  border: 3px solid var(--bg);
  box-shadow: 0 1px 3px rgb(0 0 0 / 0.25);
  cursor: pointer;
}
input[type="range"]::-moz-range-thumb {
  width: 24px; height: 24px;
  border-radius: 50%;
  background: var(--accent);
  border: 3px solid var(--bg);
  box-shadow: 0 1px 3px rgb(0 0 0 / 0.25);
  cursor: pointer;
}
input[type="range"]:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }

/* ----------------------------------------------------------------- button */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 10px 20px;
  border: 1px solid var(--rule);
  /* A pill. The brand book calls this a Pinch signature and it is the single
     cheapest way for a page to read as ours rather than as a default. */
  border-radius: 999px;
  background: var(--card);
  font-size: clamp(13px, 1.3vw, 14.5px);
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  text-decoration: none;
  color: inherit;
}
.btn:hover { border-color: var(--ink-faint); text-decoration: none; }
.btn:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }
.btn.primary {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
  font-weight: 600;
}
.btn.primary:hover { filter: brightness(1.08); }
.btn.big {
  padding: clamp(13px, 1.8vw, 16px) clamp(26px, 3vw, 34px);
  font-size: clamp(15px, 1.7vw, 17px);
  border-radius: 999px;
}
.btn:disabled { opacity: 0.5; cursor: not-allowed; }

/* ------------------------------------------------------------------- gate */

/* Browsers will not play audio before a gesture, and a silent page with no
   explanation is the worst possible first impression. This doubles as the
   one place the listener picks their language. */
.gate {
  position: fixed; inset: 0;
  display: grid;
  place-items: center;
  padding: var(--pad);
  background: var(--bg);
  z-index: 50;
}
.gate[hidden] { display: none; }
.gate-inner { max-width: 34rem; text-align: center; }
.gate h1 {
  margin: 0 0 12px;
  font-size: clamp(24px, 3.6vw, 38px);
  line-height: 1.15;
  letter-spacing: -0.028em;
  font-weight: 600;
  text-wrap: balance;
}
.gate p {
  margin: 0 0 clamp(20px, 3vw, 30px);
  font-size: clamp(15px, 1.7vw, 18px);
  line-height: 1.55;
  color: var(--ink-soft);
  text-wrap: pretty;
}
.gate .field {
  display: grid;
  gap: 8px;
  justify-items: center;
  margin: 0 0 clamp(20px, 3vw, 28px);
}
.gate .field span {
  font-size: clamp(13px, 1.3vw, 14px);
  color: var(--ink-soft);
  font-weight: 500;
}
.gate select {
  padding: 11px 14px;
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  background: var(--bg);
  font-size: clamp(15px, 1.6vw, 17px);
  min-width: 15rem;
  cursor: pointer;
}
.gate input[type="text"] {
  padding: 12px 16px;
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  background: var(--bg);
  font-size: clamp(20px, 2.4vw, 26px);
  letter-spacing: 0.24em;
  text-align: center;
  width: 9ch;
}
.gate input[type="text"]:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
.gate .pass-error { color: var(--warn); font-weight: 500; }
.gate .field[hidden] { display: none; }

.gate .tip {
  margin: clamp(18px, 2.6vw, 26px) 0 0;
  font-size: clamp(13px, 1.3vw, 15px);
  color: var(--ink-faint);
  text-wrap: pretty;
}

/* ------------------------------------------------------------------ toast */

.toasts {
  position: fixed;
  left: 50%;
  bottom: clamp(90px, 14vh, 130px);
  transform: translateX(-50%);
  display: grid;
  gap: 8px;
  width: min(30rem, calc(100vw - 2 * var(--pad)));
  z-index: 40;
}
.toast {
  padding: 12px 16px;
  border-radius: 8px;
  background: var(--ink);
  color: var(--bg);
  font-size: 14px;
  line-height: 1.45;
  box-shadow: 0 6px 20px rgb(0 0 0 / 0.18);
}

/* ------------------------------------------------- picture-in-picture doc */

/* Captions popped out into a floating always-on-top window, so they can sit
   over the meeting instead of in a tab you have to look away to read. The
   PiP window is a separate document, so these rules are copied into it. */
body.pip-doc {
  display: grid;
  align-content: center;
  gap: 6px;
  padding: 18px 20px;
  margin: 0;
  height: 100%;
  background: #0d1014;
  color: #f2f5f8;
  overflow: hidden;
  font-family: var(--sans);
}
body.pip-doc .pip-who {
  font-size: clamp(12px, 3.4vw, 16px);
  font-weight: 600;
  color: #7cb6e4;
}
body.pip-doc .pip-text {
  font-size: clamp(20px, 7.2vw, 40px);
  line-height: 1.2;
  font-weight: 500;
  letter-spacing: -0.02em;
  text-wrap: balance;
  margin: 0;
}
body.pip-doc .pip-text.draft { color: #b9c3ce; }
body.pip-doc .pip-source {
  font-size: clamp(12px, 3.6vw, 18px);
  color: #97a2ae;
  margin: 0;
}


/* Language multi-select on the console. Same chip shape the listener sees
   when picking one, so "what the host offered" and "what I can choose" look
   like the same thing. */
.picker {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.picker .pick {
  font: inherit;
  font-size: 14px;
  padding: 7px 12px;
  border-radius: 8px;
  border: 1px solid var(--line-strong);
  background: var(--surface);
  color: var(--ink-soft);
  cursor: pointer;
}
.picker .pick:hover { border-color: var(--ink-faint); color: var(--ink); }
.picker .pick.on {
  border-color: var(--live);
  background: var(--live);
  color: var(--on-live);
  font-weight: 600;
}


/* The setup step. The only thing on the page until it is done, because
   until it is done everything else is playing over the listener's meeting. */
.setup {
  padding: clamp(20px, 3vw, 32px) var(--pad);
  border-bottom: 1px solid var(--line);
  display: grid;
  gap: 12px;
  justify-items: start;
}
.setup[hidden] { display: none; }
.setup h2 {
  margin: 0;
  font-size: clamp(19px, 2.2vw, 24px);
  font-weight: 600;
  letter-spacing: -0.02em;
}
.setup p { margin: 0; color: var(--ink-soft); font-size: clamp(14px, 1.5vw, 16px); max-width: 46ch; }
.setup .linkbtn {
  background: none; border: 0; padding: 0; font: inherit; font-size: 14px;
  color: var(--ink-faint); text-decoration: underline; cursor: pointer;
}
.setup .linkbtn:hover { color: var(--ink); }
.setup-alt[hidden] { display: none; }
.setup-alt .linkbtn { color: var(--accent); }

.gate-sub {
  margin: 8px 0 20px;
  color: var(--ink-soft);
  font-size: clamp(14px, 1.5vw, 16px);
}

.setup p.warn { color: var(--warn); font-weight: 500; }

/* Between the header's own items and the link off the site. */
.sep { color: var(--line-strong); font-size: 14px; }

/* Numbered steps. Shared, because the host and the listener share the
   mechanic — the same picker, the same checkbox to miss. */
.steps { display: grid; gap: 14px; margin: 0 0 clamp(20px, 3vw, 28px); padding: 0; list-style: none; }
.step { display: grid; grid-template-columns: 26px 1fr; gap: 12px; align-items: start; }
.step .n {
  width: 26px; height: 26px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--surface); color: var(--ink-soft);
  font-size: 13px; font-weight: 600;
}
.step.done .n { background: var(--live); color: var(--on-live); }
.step b { display: block; font-size: clamp(14px, 1.5vw, 16px); font-weight: 600; }
.step small { display: block; color: var(--ink-faint); font-size: clamp(12px, 1.3vw, 14px); line-height: 1.5; }
.step-action { display: flex; align-items: center; gap: 10px; margin-top: 12px; flex-wrap: wrap; }
.step-action .hint { font-size: 13px; color: var(--ink-faint); }

/* The recording, offered after the meeting rather than during it. */
.recording { margin-top: 22px; }
.rec-actions { display: flex; flex-wrap: wrap; gap: 10px; margin: 12px 0 8px; }
.rec-actions .btn { text-decoration: none; }
/* A download that is not ready yet stays visible but inert: hiding it would
   make the host wonder whether they missed it. */
.rec-actions .btn.disabled { opacity: .45; pointer-events: none; }

/* A checkbox that only means anything while the one above it is ticked. The
   indent is the whole point: unindented it read as an independent fourth
   option rather than a condition of "Record the call". */
.check-group { display: contents; }
.check-group .consent { margin-left: 28px; }

/* The meeting is over: the dot stops claiming to be live. */
.live-dot.off { background: var(--mute); box-shadow: none; animation: none; }

/* The two ways in, offered as a choice rather than a mode buried in options. */
.source-choice { display: grid; gap: 10px; margin: 4px 0 24px; }
/* Side by side where there is room: the two options are alternatives to weigh
   against each other, and stacked they read as a list to work through. */
@media (min-width: 640px) { .source-choice { grid-template-columns: 1fr 1fr; } }
.source-option { display: flex; gap: 10px; align-items: flex-start;
                 padding: 12px 14px; border: 1px solid var(--line);
                 border-radius: 10px; cursor: pointer; }
.source-option:has(input:checked) { border-color: var(--accent);
                                    background: var(--accent-soft)); }
.source-option small { display: block; color: var(--mute); margin-top: 2px; }

/* The first screen: two doors, nothing else.
 *
 * Given the room a first screen has rather than the density of a form row —
 * an icon, a name, one line — so the two products read as the page's subject
 * rather than as a setting somebody added to it later. */
.chooser { display: grid; gap: 16px; margin: 10px 0 0; }
@media (min-width: 700px) { .chooser { grid-template-columns: 1fr 1fr; } }
.chooser .source-option {
  display: grid; gap: 10px; text-align: left; cursor: pointer;
  /* start, not stretch: otherwise the card with the shorter description
     spreads its rows out and the two headings sit at different heights. */
  align-content: start;
  padding: 26px 24px 24px;
  border: 1px solid var(--line); border-radius: 14px;
  background: var(--card); font: inherit; color: inherit;
  transition: border-color .14s ease, box-shadow .14s ease, transform .14s ease;
}
.chooser .source-option:hover {
  border-color: var(--accent);
  box-shadow: 0 6px 22px rgba(16, 24, 40, .07);
  transform: translateY(-2px);
}
.chooser .source-option:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
.opt-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 46px; height: 46px; border-radius: 11px;
  background: var(--accent-soft));
  color: var(--accent);
}
.chooser .source-option b { font-size: 1.06rem; letter-spacing: -.01em; }
.chooser .source-option small {
  color: var(--mute); font-size: .93rem; line-height: 1.5;
}

/* Back to the first screen. A link, not a button: it undoes rather than acts. */
.link-back {
  background: none; border: 0; padding: 0 0 14px; cursor: pointer;
  color: var(--mute); font: inherit; font-size: .9rem;
}
.link-back:hover { color: var(--ink); }
.tab-hint { margin: -4px 0 18px; }

/* The account menu. */
.account { position: relative; }
.who {
  display: inline-flex; align-items: center; gap: 6px;
  background: none; border: 1px solid transparent; padding: 5px 9px;
  cursor: pointer; font: inherit; font-size: .9rem; color: var(--mute);
  border-radius: 8px;
}
.who:hover { border-color: var(--line); }
.who[aria-expanded="true"] { border-color: var(--accent); }
.who:hover { color: var(--ink); background: var(--hover)); }
.account-menu {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 40;
  min-width: 190px; padding: 6px; display: grid; gap: 2px;
  background: var(--card); border: 1px solid var(--line);
  border-radius: 10px; box-shadow: 0 10px 30px rgba(16,24,40,.12);
}
.account-menu[hidden] { display: none; }
.account-menu a, .account-menu button {
  display: block; width: 100%; text-align: left; padding: 8px 10px;
  border: 0; background: none; font: inherit; color: inherit;
  text-decoration: none; border-radius: 7px; cursor: pointer;
}
.account-menu a:hover, .account-menu button:hover { background: var(--hover)); }

/* The page's language, and the way in.
 *
 * The label and caret are drawn; the select lies over them, transparent and
 * full-size, so it still opens the browser's own picker. Styling the select
 * directly does not work here: it sizes to its widest option, which left the
 * caret floating a word away from the language selected. */
.lang {
  position: relative; display: inline-flex; align-items: center; gap: 6px;
  /* The topbar is a flex row: without this the group stretches and the caret
     drifts away from the word it belongs to. */
  flex: 0 0 auto;
  padding: 5px 9px; border: 1px solid transparent; border-radius: 8px;
}
.lang:hover { border-color: var(--line); }
.lang-label { font-size: .9rem; color: var(--mute); white-space: nowrap; }
.lang:hover .lang-label { color: var(--ink); }
.lang-caret { color: var(--mute); flex: none; }
.lang-pick {
  position: absolute; inset: 0; width: 100%; height: 100%;
  opacity: 0; cursor: pointer; font: inherit;
}
.lang:focus-within { border-color: var(--accent); }

/* Kept apart from the language, which is a setting rather than an action. */
.signin-btn { margin-left: 14px; }
.btn.small { padding: 6px 12px; font-size: .88rem; }

/* --- Push to talk ------------------------------------------------------- */

/* The rule, above the control it governs. A microphone that answers a held
   key and ignores a click is correct and completely unguessable, and a
   listener who clicks it once and gets nothing concludes the feature is
   broken rather than that they are holding it wrong. */
.talk-hint {
  /* Its own row, directly above the control it explains. Without a named
     area it was auto-placed into an implicit row at the top of the grid,
     where it sat under the header and over the first caption line — a
     rule about the microphone, as far from the microphone as the page
     allows. */
  grid-area: hint;
  margin: 0 auto;
  padding: 8px 12px 4px;
  max-width: 30rem;
  text-align: center;
  /* Two short lines rather than a long one and an orphan, which is what a
     centred sentence looks wrong as. */
  text-wrap: balance;
  font-size: 12.5px;
  line-height: 1.35;
  color: var(--ink-soft);
}
.talk-hint[hidden] { display: none; }

/* The key, drawn as a key. */
.talk-hint kbd {
  display: inline-block;
  margin: 0 2px;
  padding: 1px 6px 2px;
  border: 1px solid var(--line);
  border-bottom-width: 2px;
  border-radius: 5px;
  background: var(--panel, rgba(127, 127, 127, 0.12));
  font-family: var(--mono);
  font-size: 11.5px;
  font-weight: 500;
  line-height: 1.4;
}

/* On a keyboard the button reports; it does not accept. Clicking it was the
   confusion: a control that looks pressable in a mode where pressing is not
   how it works. */
.ctl.mic.readonly { cursor: default; }
.ctl.mic.readonly:active { background: none; }
.ctl.mic.readonly[aria-pressed="true"]:active { background: var(--live); }

/* On a touch screen it *is* the control, and it is held. Bigger, because a
   thumb holds it for the length of a sentence, and inert to the browser's own
   gestures so a hold does not scroll the captions or pop a selection. */
.ctl.mic.holdable {
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}
@media (hover: none) and (pointer: coarse) {
  .ctl.mic.holdable { max-width: 96px; padding: 10px 2px 8px; }
  .ctl.mic.holdable svg { width: 27px; height: 27px; }
  .talk-hint { font-size: 13px; }
}

/* Mobile listener: readable captions and controls clear of browser chrome. */
@media (max-width: 600px) {
  .controls .ctl { min-height: 52px; min-width: 44px; }
  .caps { padding-bottom: 24px; overflow-wrap: anywhere; }
  .cap-text { font-size: 20px; line-height: 1.5; }
  .cap-src { font-size: 15px; line-height: 1.5; }
  .join input[type="text"], .sheet input, .sheet select, .sheet textarea { font-size: 16px; }
  .sheet { padding-bottom: max(16px, env(safe-area-inset-bottom)); }
}
