/* Self-hosted fonts; licences are in public/fonts. Atkinson Hyperlegible
   Next, designed for low-vision readers, is also the font of the buttons and
   labels. OpenDyslexic is only downloaded if a viewer picks it. */
@font-face {
  font-family: "Atkinson Hyperlegible Next";
  src: url("/fonts/atkinson-hyperlegible-next.woff2") format("woff2");
  font-display: swap;
}

@font-face {
  font-family: "OpenDyslexic";
  src: url("/fonts/opendyslexic.woff2") format("woff2");
  font-display: swap;
}

/* --bg, --text, --muted and --line are what viewers' colour schemes replace
   (public/display.js), so everything else is mixed from them and follows the
   chosen scheme. */
:root {
  color-scheme: light dark;
  --bg: #ffffff;
  --text: #111111;
  --muted: #595959;
  --line: #d0d0d0;
  --panel: color-mix(in srgb, var(--text) 5%, var(--bg));
  --raised: color-mix(in srgb, var(--text) 10%, var(--bg));
  /* The on-air colour, used only while live. White on it is 7.3:1. */
  --tally: #b00020;
  /* Brand colours (docs/logo-concepts/README.md). Rise sits only on Ink or
     a dark page; Rise Deep is the word "rise" on a light one. */
  --ink: #101828;
  --rise: #ff5a36;
  --rise-deep: #e4401c;
  --ui-font: "Atkinson Hyperlegible Next", system-ui, sans-serif;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #000000;
    --text: #ffffff;
    --muted: #a6a6a6;
    --line: #333333;
  }
}

* {
  box-sizing: border-box;
}

html,
body {
  height: 100%;
  margin: 0;
}

body {
  display: flex;
  flex-direction: column;
  background: var(--bg);
  color: var(--text);
  font-family: var(--ui-font);
  font-size: 1rem;
  line-height: 1.4;
}

a {
  color: inherit;
  text-underline-offset: 0.2em;
}

:focus-visible {
  outline: 2px solid var(--text);
  outline-offset: 2px;
}

/* One arrow shape for drop-downs and things that open, drawn from two
   borders so it's crisp at any size and takes the scheme's colour. */
label:has(> select)::after,
#display-toggle::after,
#help-toggle::after,
#setup-toggle::after,
.select-wrap::after,
.terms summary::after {
  content: "";
  width: 0.4rem;
  height: 0.4rem;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: translateY(-25%) rotate(45deg);
  transition: transform 0.15s ease;
}

/* Header: one or two bars of controls above the captions. */

header {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  padding: 0.75rem 1rem;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
}

.bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1rem;
}

/* The name in the brand colours: "Text" in Ink and "rise" in Rise Deep on a
   light page, White and Rise on a dark one (a viewer's colour scheme sets
   color-scheme, so light-dark() follows it). It was a solid subtitle box
   until 2026-10-26. On the viewer page the same span carries the event name
   instead, once there is one. */
.wordmark {
  color: light-dark(var(--ink), #ffffff);
  font-size: 1.05rem;
  letter-spacing: 0.01em;
  white-space: nowrap;
}

.wordmark .rise,
.sign-in-brand .rise {
  color: light-dark(var(--rise-deep), var(--rise));
}

/* On the transcripts and Accounts pages the name is a link home (Mark,
   2026-10-27), with the look of the plain name. */
a.wordmark {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem; /* a 44 px tap target, like the buttons beside it */
  text-decoration: none;
  border-radius: 0.25rem;
}

/* A Done link at the right-hand end of a page's top bar (transcripts), the
   same word as the sheets' Done. */
.bar .bar-done {
  margin-left: auto;
}

h1 {
  margin: 0;
  font-size: 1.05rem;
  font-weight: 400;
  color: var(--muted);
}

#status {
  flex: 1 1 12rem;
  margin: 0;
  color: var(--muted);
}

/* The viewer's Display and Help buttons, kept together at the bar's end. */
.bar-actions {
  display: flex;
  gap: 0.5rem;
  margin-left: auto;
}

/* Viewer status: a dot that's red while captions are live. It's short, so
   Display can sit beside it even on a phone. */
#status[data-state] {
  flex-basis: 4rem;
}

#status[data-state]::before {
  content: "";
  display: inline-block;
  width: 0.6em;
  height: 0.6em;
  margin-right: 0.45em;
  border-radius: 50%;
  background: var(--muted);
  vertical-align: 0.05em;
}

#status[data-state="live"] {
  color: var(--text);
}

#status[data-state="live"]::before {
  background: var(--tally);
}

#status[data-state="lost"]::before {
  background: transparent;
  box-shadow: inset 0 0 0 2px var(--muted);
}

/* Buttons and fields */

button,
a.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5em;
  min-height: 2.75rem;
  padding: 0.5rem 1.25rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--bg);
  color: var(--text);
  font: inherit;
  cursor: pointer;
  touch-action: manipulation;
}

a.button {
  text-decoration: none;
}

button:hover,
a.button:hover {
  background: var(--raised);
  border-color: color-mix(in srgb, var(--text) 35%, var(--bg));
}

button:active,
a.button:active {
  background: color-mix(in srgb, var(--text) 16%, var(--bg));
}

button:disabled {
  cursor: default;
  opacity: 0.6;
}

button.quiet,
a.button.quiet {
  background: transparent;
}

button.quiet:hover,
a.button.quiet:hover,
button[aria-expanded="true"] {
  background: var(--raised);
}

#display-toggle,
#help-toggle,
#setup-toggle {
  gap: 0.6rem;
}

/* Points down when closed, up when open. */
#display-toggle[aria-expanded="true"]::after,
#help-toggle[aria-expanded="true"]::after,
#setup-toggle[aria-expanded="true"]::after {
  transform: translateY(25%) rotate(225deg);
}

/* Start works like a studio's on-air light: a red dot when ready, and the
   whole button turns red, with a stop square, while live. */
#toggle:hover {
  border-color: var(--text);
  background: color-mix(in srgb, var(--text) 85%, var(--bg));
}

#toggle {
  min-width: 7.5rem;
  border-color: var(--text);
  background: var(--text);
  color: var(--bg);
  font-size: 1.1rem;
}

#toggle::before {
  content: "";
  width: 0.7em;
  height: 0.7em;
  border-radius: 50%;
  background: var(--tally);
  box-shadow: 0 0 0 2px var(--bg);
}

#live-banner:not([hidden]) ~ .dock #toggle {
  border-color: var(--tally);
  background: var(--tally);
  color: #ffffff;
}

#live-banner:not([hidden]) ~ .dock #toggle::before {
  border-radius: 2px;
  background: #ffffff;
  box-shadow: none;
}

label {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  color: var(--muted);
}

select,
input[type="text"],
input[type="email"],
input[type="number"],
input[type="password"],
textarea {
  min-height: 2.75rem;
  padding: 0.4rem 0.75rem;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--bg);
  color: var(--text);
  font: inherit;
  transition: border-color 0.15s ease;
}

select:hover,
input[type="text"]:hover,
input[type="email"]:hover,
input[type="number"]:hover,
input[type="password"]:hover,
textarea:hover {
  border-color: color-mix(in srgb, var(--text) 35%, var(--bg));
}

/* The browser's own arrow sits against the edge, so it's replaced by the
   label's arrow, drawn over the right-hand end of the drop-down. */
select {
  appearance: none;
  padding-right: 2.5rem;
  cursor: pointer;
}

select:disabled {
  cursor: default;
  opacity: 0.6;
}

label:has(> select),
.select-wrap {
  position: relative;
}

label:has(> select)::after,
.select-wrap::after {
  position: absolute;
  right: 1rem;
  top: 50%;
  margin-top: -0.2rem;
  color: var(--text);
  pointer-events: none;
}

textarea {
  display: block;
  width: 100%;
  resize: vertical;
}

/* Viewer display settings, folded away until Display is pressed: rows of
   chips (styled with the broadcaster's, below), side by side where they fit. */
.controls {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 0.75rem 1.5rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--line);
}

.controls[hidden] {
  display: none;
}

/* The viewer's Help note: a few short paragraphs in the reading size. */
.help-panel {
  display: grid;
  gap: 0.5rem;
  max-width: 44rem;
}

.help-panel p {
  margin: 0;
  font-size: 1.0625rem;
  line-height: 1.45;
}

/* Broadcaster's names and terms list, and the caption text and colours: two
   fold-out sections in the More sheet (.sheet .terms, below, fits them into
   the sheet's grouped cards). The row's words line up with the other rows'
   and the chevron sits at the right-hand end, as in iOS Settings. */
.terms {
  padding: 0.25rem 0.75rem 0.75rem;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--bg);
}

.terms summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  width: 100%;
  min-height: 2.75rem;
  color: var(--muted);
  cursor: pointer;
  list-style: none;
  touch-action: manipulation;
}

.terms summary::-webkit-details-marker {
  display: none;
}

.terms summary:hover {
  color: var(--text);
}

/* Points right when closed, down when open. */
.terms summary::after {
  flex: none;
  transform: rotate(-45deg);
}

.terms[open] summary::after {
  transform: translateY(-25%) rotate(45deg);
}

.terms label {
  display: block;
  margin: 0 0 0.5rem;
}

/* Saved lists: the picker, the name and the buttons, stacked, each field with
   its label above it and the three buttons sharing one row. Stacked at every
   width: the sheet is at most 30rem wide, and a side-by-side layout overflowed
   it on a laptop (fixed 2026-10-25). */
.terms .list-bar {
  display: grid;
  gap: 0.75rem;
  margin: 0.5rem 0 0.75rem;
}

.terms .list-bar label {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0.3rem;
  margin: 0;
  font-size: 0.9rem;
}

.terms .list-bar select,
.terms .list-bar input[type="text"] {
  width: 100%;
  min-width: 0;
  font-size: 1.0625rem;
}

/* The arrow is drawn from the label's centre, so with the caption above the
   drop-down it is lowered to the drop-down's own centre. */
.terms .list-bar label:has(> select)::after {
  top: calc(100% - 1.375rem);
}

.list-actions {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.5rem;
}

.list-actions button {
  padding-inline: 0.75rem;
}

/* Phone width: the name, status and Display button spread across the top bar. */
@media (max-width: 39.99rem) {
  header .bar:first-child {
    justify-content: space-between;
  }
}
/* Captions: left-aligned in a column that widens with the text size, so
   lines stay a comfortable length on big screens. */
#captions {
  flex: 1;
  overflow-y: auto;
  padding: 1.25rem max(1rem, calc((100% - 64ch) / 2));
  font-family: var(--caption-font, system-ui, sans-serif);
  font-size: var(--caption-size, 2rem);
  line-height: var(--caption-line-height, 1.5);
  /* Explicit, so a colour scheme set on the box itself (the broadcaster's own
     choice) takes effect; viewers set theirs on the page, which is the same. */
  color: var(--text);
}

/* Text still being heard; AssemblyAI may revise it. */
.partial {
  color: var(--muted);
}

/* A change of speaker (captions.js): a small muted heading on its own line,
   starting a new paragraph; the first has no gap above it. */
.speaker-change {
  display: block;
  margin-top: 0.75em;
  font-size: 0.6em;
  line-height: 1.6;
  font-weight: 600;
  color: var(--muted);
}

.speaker-change:first-child {
  margin-top: 0;
}

/* Sign-in page */
.page h1 {
  margin: 0 0 1rem;
  font-size: 1.4rem;
  font-weight: 400;
  color: var(--text);
}

/* Sign-in: one card, centred on the broadcaster's grey page (Mark,
   2026-10-25, the review's third proposal; it was a bare form top-left). */
.sign-in-page {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  padding: 1.5rem max(1rem, env(safe-area-inset-right)) calc(1.5rem + env(safe-area-inset-bottom))
    max(1rem, env(safe-area-inset-left));
}

/* The sign-in and setup pages have no top bar: the logo sits above the name,
   centred over the card (Mark, 2026-10-27: the name and logo should be more
   prominent on the front door). The logo is the full drawing, as on the
   home-screen icon; the tab icon's simpler one is made for 32 px and below.
   At 36 px bold the name is large text, so "rise" needs 3:1 (about 3.7:1 on
   the grey page). */
.sign-in-brand {
  margin-bottom: 0.25rem;
  padding: 0.25rem 0.5rem; /* room for the focus ring */
  text-align: center;
  color: light-dark(var(--ink), #ffffff);
  font-size: 2.25rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1;
  text-decoration: none;
  border-radius: 0.25rem;
}

.sign-in-brand::before {
  content: "";
  display: block;
  width: 4.5rem;
  height: 4.5rem;
  margin: 0 auto 0.75rem;
  border-radius: 22%;
  background: url("/logo.svg") center / cover;
}

/* A short screen (a phone on its side): the logo beside the name at the top
   bar's size, so the form stays in view. */
@media (max-height: 30rem) {
  .sign-in-brand {
    margin-bottom: 0;
    font-size: 1.375rem;
  }

  .sign-in-brand::before {
    display: inline-block;
    width: 2.25rem;
    height: 2.25rem;
    margin: 0 0.5rem 0 0;
    vertical-align: middle;
  }
}

.sign-in-card {
  display: grid;
  gap: 1rem;
  width: min(100%, 24rem);
  padding: 1.5rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--cell);
}

.sign-in-card h1 {
  margin: 0;
  font-size: 1.25rem;
  font-weight: 600;
  color: var(--text);
}

.sign-in-card input,
.sign-in-card button {
  width: 100%;
}

.sign-in-card #status,
.sign-in-page .hint {
  margin: 0;
  color: var(--muted);
}

.sign-in-card #status:empty {
  display: none;
}

.sign-in-page > .hint {
  text-align: center;
}

.hint {
  color: var(--muted);
}

/* A tick box with its words beside it. */
.check {
  gap: 0.6rem;
  min-height: 2.75rem;
  font-size: 0.9rem;
  cursor: pointer;
  touch-action: manipulation;
}

input[type="checkbox"] {
  flex: none; /* a flex row never squeezes it narrower */
  width: 1.375rem;
  height: 1.375rem;
  margin: 0;
  accent-color: var(--text);
  cursor: pointer;
  touch-action: manipulation;
}

/* Event code box on the viewer page: read out and typed in capitals. */
.code {
  text-transform: uppercase;
  letter-spacing: 0.15em;
}

/* Transcripts page */
.page {
  width: 100%;
  max-width: 44rem;
  padding: 1.5rem 1rem;
}

.page #status {
  color: var(--muted);
}

/* Transcripts: a grouped card of rows on the broadcaster's grey page, as in
   the More sheet (Mark, 2026-10-25, the review's fourth proposal; it was a
   bare list on a white page). Each row: a tick box, the date with the event
   name under it, its size, and View, Download and Delete with small icons
   (Mark, 2026-10-27). Above the card: Select all, Delete selected and the
   sort order. */
.transcripts-page .page {
  /* Wider than the other pages, so a row's date fits on one line. */
  max-width: 52rem;
  margin-inline: auto;
}

.transcripts-page #status {
  margin: 0 0 0.75rem;
  font-size: 0.9375rem;
  color: var(--muted);
}

.transcripts-tools {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.75rem;
  margin-bottom: 0.75rem;
}

.transcripts-tools[hidden] {
  display: none;
}

.transcripts-tools .pick-all {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 2.75rem;
  padding-inline: 0.25rem;
  font-size: 1.0625rem;
}

.transcripts-tools #order {
  margin-left: auto;
}

/* Tick boxes big enough for a thumb, in the text colour. */

.transcripts {
  margin: 0;
  padding: 0;
  list-style: none;
  overflow: clip;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--cell);
}

.transcripts:empty {
  display: none;
}

.transcripts li {
  display: grid;
  grid-template-columns: auto 1fr auto;
  grid-template-areas:
    "pick title size"
    "actions actions actions";
  align-items: center;
  gap: 0.25rem 0.5rem;
  min-height: 3.5rem;
  padding: 0.625rem 0.75rem 0.5rem 0.5rem;
}

.transcripts li + li {
  border-top: 1px solid var(--line);
}

/* The tick box sits in a 44 px cell, so the whole cell is its target. */
.transcripts .pick-cell {
  grid-area: pick;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
}

.transcripts .title {
  grid-area: title;
  display: grid;
  min-width: 0;
  font-size: 1.0625rem;
}

.transcripts .event {
  overflow: hidden;
  font-size: 0.9375rem;
  color: var(--muted);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.transcripts .size {
  grid-area: size;
  color: var(--muted);
  font-size: 0.9375rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.transcripts .actions {
  grid-area: actions;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

/* The row's buttons are the standard quiet pills (17 px, hairline), only
   narrower so View, Download and Delete fit one row at 375 px. */
.broadcast .transcripts button,
.broadcast .transcripts a.button {
  padding-inline: 0.5rem;
  gap: 0.375rem;
}

.row-icon {
  width: 1.125rem;
  height: 1.125rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Delete, in the destructive red. */
.broadcast button.destructive {
  color: #ff3b30;
}

.broadcast button.destructive:disabled {
  color: var(--muted);
}

@media (prefers-color-scheme: dark) {
  .broadcast button.destructive {
    color: #ff453a;
  }
}

/* From tablet width the row is one line: tick box, date and name, size, then
   the three buttons at the right. */
@media (min-width: 40rem) {
  .transcripts li {
    grid-template-columns: auto 1fr auto auto;
    grid-template-areas: "pick title size actions";
    column-gap: 0.75rem;
    padding-block: 0.5rem;
  }

  .broadcast .transcripts button,
  .broadcast .transcripts a.button {
    padding-inline: 0.75rem;
  }
}

/* The View sheet: the transcript's own text, scrolling inside the sheet. */
.viewer-card {
  gap: 0.75rem;
}

.viewer-meta {
  margin: -0.5rem 0 0;
  font-size: 0.9375rem;
  color: var(--muted);
}

.viewer-meta:empty {
  display: none;
}

.transcript-text {
  margin: 0;
  padding: 0.875rem 1rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--cell);
  font: inherit;
  font-size: 1.0625rem;
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.viewer-card a.button {
  justify-self: start;
}

@media (prefers-reduced-motion: reduce) {
  * {
    transition: none !important;
  }
}

/* Appears when a viewer has scrolled up to re-read; the page then stays put. */
#jump {
  position: fixed;
  right: 1rem;
  bottom: 1rem;
  box-shadow: 0 2px 8px rgb(0 0 0 / 35%);
}

#jump[hidden] {
  display: none;
}

/* Captions-only view (/view?plain=1#CODE): no header while connected. */
.plain .wordmark,
.plain .bar-actions,
.plain #display,
.plain #help {
  display: none;
}

.plain header:has(#status[data-state="live"]) {
  display: none;
}
/* Broadcaster page. It is in one of two modes (data-mode on <body>, set by
   app.js):
   - setup: the name, microphone and share card, with Go live in a dock at the
     bottom, where a thumb reaches;
   - live: the captions get the screen. On a phone everything else folds away
     into a slim red strip and a floating dock; More opens the rest.
   Everything not needed all the time is in the sheet behind More.

   The look follows iOS: the system font; grouped surfaces (a grey page with
   white cells, black with dark-grey cells in dark mode); hairline borders;
   one corner radius for fields and cards; 44 px touch targets; safe areas
   respected (the page asks for them with viewport-fit=cover). Only this page:
   viewers keep Atkinson Hyperlegible Next. */

.broadcast {
  --ui-font: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro", system-ui, "Segoe UI", Roboto, sans-serif;
  --group-bg: #f2f2f7;
  --cell: #ffffff;
  --line: rgb(0 0 0 / 8%);
  --panel: var(--group-bg);
  --raised: color-mix(in srgb, var(--text) 7%, var(--cell));
  --radius: 16px;
  height: 100vh;
  height: 100dvh;
  background: var(--group-bg);
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
}

@media (prefers-color-scheme: dark) {
  .broadcast {
    --group-bg: #000000;
    --cell: #1c1c1e;
    --line: rgb(255 255 255 / 14%);
  }
}

html {
  -webkit-text-size-adjust: 100%;
}

/* Type scale: 17 px for what you read and type (never under 16, so iOS
   doesn't zoom into a field) and 15 px for labels, notes and hints (13 px
   small print was dropped on 2026-10-27: too small to read on a phone).
   Buttons are white cells (see Surfaces, below). */
.broadcast button,
.broadcast a.button {
  background: var(--cell);
  font-size: 1.0625rem;
  font-weight: 500;
  -webkit-tap-highlight-color: transparent;
}

.broadcast button.primary,
.broadcast #toggle {
  font-weight: 600;
}

.broadcast input,
.broadcast select,
.broadcast textarea {
  font-size: 1.0625rem;
}

.broadcast .field,
.broadcast .share-label,
.broadcast label {
  font-size: 0.9375rem;
}

.broadcast .hint {
  font-size: 0.9375rem;
}

/* Surfaces: white cells (dark grey in dark mode) on the grey page, with a
   hairline border and one corner radius. */
.broadcast input[type="text"],
.broadcast input[type="email"],
.broadcast input[type="number"],
.broadcast input[type="password"],
.broadcast select,
.broadcast textarea {
  border-radius: var(--radius);
  background: var(--cell);
}

/* The transcripts and Accounts pages' headers keep their top padding on a
   phone (the broadcaster page's moves to its sticky top bar, below). */
body.admin-page header,
body.transcripts-page header {
  padding-top: calc(env(safe-area-inset-top) + 0.75rem);
}

.broadcast button:hover,
.broadcast a.button:hover {
  background: var(--raised);
}

.broadcast button.quiet,
.broadcast a.button.quiet {
  background: var(--cell);
}

.broadcast button.quiet:hover,
.broadcast a.button.quiet:hover {
  background: var(--raised);
}

.broadcast header {
  padding: 0.75rem max(1rem, env(safe-area-inset-right)) 0.75rem max(1rem, env(safe-area-inset-left));
  border-bottom-color: var(--line);
}

/* Top bar: the name, how many are watching, and More. */
.broadcast .top {
  gap: 0.5rem;
}

.broadcast .top .watching {
  margin-left: auto;
}

.broadcast .top button {
  min-height: 2.75rem;
  padding-inline: 1rem;
}

/* "3 watching" in the top bar. */
.watching {
  margin: 0;
  color: var(--muted);
  font-size: 0.9375rem;
}

/* The same size and type as the Setup and More buttons beside it (Mark,
   2026-10-25); muted, because it is information, not a button. */
.top .watching {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  min-height: 2.75rem;
  padding: 0.4rem 1rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--cell);
  font-size: 1.0625rem;
  font-weight: 500;
  white-space: nowrap;
}

/* The eye is for the phone's compact pill (below); a laptop has the words. */
.top .watching svg {
  display: none;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* The red strip shown while live: a LIVE badge, then (on a phone) the event
   code, and over on the right the viewer count and Share. */
#live-banner {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: calc(env(safe-area-inset-top) + 0.375rem) max(1rem, env(safe-area-inset-right)) 0.375rem
    max(1rem, env(safe-area-inset-left));
  background: var(--tally);
  color: #ffffff;
  font-size: 1rem;
}

#live-banner[hidden] {
  display: none;
}

.live-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.3rem 0.7rem;
  border-radius: 999px;
  background: rgb(255 255 255 / 20%);
  font-size: 0.8125rem;
  letter-spacing: 0.08em;
}

.live-dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: #ffffff;
  animation: live-blink 1.6s ease-in-out infinite;
}

@keyframes live-blink {
  50% {
    opacity: 0.35;
  }
}

.live-code {
  min-width: 0;
  overflow: hidden;
  font-size: 0.9375rem;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.06em;
  text-overflow: ellipsis;
  white-space: nowrap;
  opacity: 0.92;
}

.live-viewers {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  margin-left: auto;
  font-size: 0.9375rem;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

/* Round, icon-only buttons (Share, More). */
.broadcast button.icon-button {
  width: 2.75rem;
  min-width: 2.75rem;
  padding: 0;
  border-radius: 50%;
}

/* After copying, a tick for a moment in place of the icon. */
.icon-button[data-done] svg {
  display: none;
}

.icon-button[data-done]::after {
  content: "✓";
  font-size: 1.25rem;
}

.broadcast button.on-red {
  border: 0;
  background: rgb(255 255 255 / 20%);
  color: #ffffff;
}

.broadcast button.on-red:hover {
  background: rgb(255 255 255 / 30%);
}

/* Only wanted in the phone's live view (below). */
#live-banner .live-code,
#live-banner #live-share,
.dock #live-more,
.dock .listening {
  display: none;
}

/* While live on a laptop (tablet width and up, below), the top bar carries
   the LIVE badge, the event code and the viewer count in place of the watching
   pill, and the red strip is not shown: one bar, not two (Mark, 2026-10-25).
   The badge is a red pill the height of the buttons beside it; with the red
   Stop button and the "● LIVE" tab title it is the forgotten-tab reminder on
   a laptop. */
.top .top-live {
  display: none;
  align-items: center;
  gap: 0.5rem;
  margin-left: auto;
}

.top .top-live .live-badge {
  min-height: 2.75rem;
  padding: 0 1rem;
  background: var(--tally);
  color: #ffffff;
  font-size: 0.9375rem;
}

.top .top-live .live-code {
  padding-inline: 0.25rem;
  color: var(--text);
  font-size: 1.0625rem;
  font-weight: 600;
  opacity: 1;
}

.top .top-live .live-viewers {
  min-height: 2.75rem;
  margin-left: 0;
  padding: 0 1rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--cell);
  color: var(--muted);
  font-size: 1.0625rem;
  font-weight: 500;
}

/* The QR code and the caption text size, at hand in the top bar on a laptop
   (Mark, 2026-10-25); not on a phone, where the More sheet has both. A− and A+
   are one joined pair. */
.top .top-tools {
  display: none;
  align-items: center;
  gap: 0.5rem;
}

.top .size-pair {
  display: inline-flex;
}

.broadcast .top .size-pair button {
  min-width: 2.75rem;
  padding-inline: 0.75rem;
}

.top .size-pair button:first-child {
  border-radius: 999px 0 0 999px;
}

.top .size-pair button:last-child {
  margin-left: -1px;
  border-radius: 0 999px 999px 0;
}

/* The event name in the top bar, once there is one: read-only, one line. It
   stands well clear of the logo and name (Mark, 2026-10-26: the two names
   sat too close), so it reads as the event's, not part of the wordmark, and
   is in the reading size and text colour, not the muted label style, so it
   reads as the event's title and not a caption on the bar (Mark, the same
   day). Regular weight, so the bold wordmark stays the louder of the two. */
.top .event-title {
  flex: 1 1 0;
  min-width: 0;
  margin-left: 2.5rem;
  overflow: hidden;
  color: var(--text);
  font-size: 1.0625rem;
  font-weight: 400;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.top .event-title:empty {
  display: none;
}

/* Setup (the event name and microphone) and the share card, folded away
   behind the Setup button so the captions get the screen. */
.setup-panel {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.setup-panel[hidden] {
  display: none;
}

.setup {
  display: grid;
  gap: 0.75rem;
}

.field {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0.375rem;
  color: var(--muted);
}

.field input[type="text"],
.field input[type="email"],
.field input[type="number"],
.field select {
  width: 100%;
}

/* The setup card's tick box (Label speaker changes) is a control, so it is in
   the text colour, not the field's muted label colour. */
.setup .check {
  color: var(--text);
}

/* The cost note under the tick box (stage 2). */
.setup .speakers-cost {
  margin: -0.25rem 0 0;
}

/* Speech-to-text (stage 3 of docs/admin-and-own-key-plan.md): whose key the
   captions use and the time left or used (usage.js), with the own-key form.
   A cell like the share card, across the whole setup. */
.stt {
  grid-column: 1 / -1;
  display: grid;
  gap: 0.625rem;
  padding: 1rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--cell);
}

.stt h2 {
  margin: 0;
  font-size: 0.9375rem;
  font-weight: 400;
}

.stt-line,
.stt .hint {
  margin: 0;
}

.stt-line {
  font-size: 1.0625rem;
  overflow-wrap: anywhere;
}

.stt-line:empty {
  display: none;
}

.stt-note,
.stt-error {
  color: light-dark(#c4001a, #ff6961);
}

.stt-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.625rem;
}

.stt-actions[hidden],
.stt-form[hidden],
.stt button[hidden],
.stt p[hidden] {
  display: none;
}

.stt-form {
  display: grid;
  gap: 0.625rem;
}

.stt .check {
  color: var(--text);
  align-items: flex-start;
}

/* The drop-down, its level meter and Test, on one line. */
.mic-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 0.625rem;
}

.select-wrap {
  min-width: 0;
}

.select-wrap select {
  text-overflow: ellipsis;
}

/* Microphone level: five bars that rise with the sound (--level, 0 to 1, is
   set by app.js). The same element is used by the live view's dock. */
.meter {
  --level: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 3px;
  width: 1.75rem;
  height: 1.5rem;
}

.meter i {
  --gain: 1;
  width: 4px;
  height: 100%;
  border-radius: 2px;
  background: var(--text);
  opacity: calc(0.3 + var(--level) * 0.7);
  transform: scaleY(calc(0.15 + var(--level) * var(--gain) * 0.85));
  transition: transform 0.08s linear, opacity 0.08s linear;
}

.meter i:nth-child(1),
.meter i:nth-child(5) {
  --gain: 0.5;
}

.meter i:nth-child(2),
.meter i:nth-child(4) {
  --gain: 0.8;
}

/* Share: what viewers need, and one button to send it. */
.share {
  display: grid;
  gap: 0.875rem;
  padding: 1rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--cell);
}

.share-info {
  display: grid;
  gap: 0.75rem;
}

.share-item {
  display: grid;
  gap: 0.125rem;
  min-width: 0;
}

.share-label {
  color: var(--muted);
}

.share-value {
  min-width: 0;
  overflow-wrap: anywhere;
}

#share-link {
  font-size: 0.9375rem;
}

.share-code {
  font-size: 1.5rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.share-actions {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0.5rem;
}

.broadcast button.primary {
  border-color: var(--text);
  background: var(--text);
  color: var(--bg);
}

.broadcast button.primary:hover {
  border-color: var(--text);
  background: color-mix(in srgb, var(--text) 85%, var(--bg));
}

/* The captions, as viewers will see them, until the first words arrive. */
.broadcast #captions {
  background: var(--bg);
}

/* Jump to latest sits just above the dock, wherever the dock is (its height is
   measured into --dock-space by app.js), clear of the screen's right edge. */
.broadcast #jump {
  right: max(1rem, env(safe-area-inset-right));
  bottom: calc(var(--dock-space, 7rem) + 0.5rem);
  z-index: 5;
}

/* New words glide into view rather than jumping (public/captions.js keeps
   following while the glide is in progress). */
@media (prefers-reduced-motion: no-preference) {
  .broadcast #captions {
    scroll-behavior: smooth;
  }
}

/* Nothing said yet: a muted note in the middle of the pane, with what to do
   next (Mark, 2026-10-25, the review's fifth proposal; it sat small in the
   top-left corner). While live it says the microphone is listening instead,
   since on a phone the dock's "Listening" is for screen readers only. */
.broadcast #captions:empty {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
}

.broadcast #captions:empty::before {
  content: "Captions will appear here.\A Press Go live, then speak.";
  max-width: 28ch;
  color: var(--muted);
  font-size: 1.0625rem;
  line-height: 1.5;
  white-space: pre-line;
}

.broadcast[data-mode="live"] #captions:empty::before {
  content: "Listening.\A Words appear here as they're heard.";
}

/* Go live (and Stop), with the status: at the bottom, in the thumb's reach.
   Above the button on a phone; beside it on a wider screen. */
.dock {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-top: auto;
  padding: 0.75rem max(1rem, env(safe-area-inset-right)) max(0.75rem, env(safe-area-inset-bottom))
    max(1rem, env(safe-area-inset-left));
  background: var(--panel);
  border-top: 1px solid var(--line);
}

.dock #status {
  flex: 0 1 auto;
  max-height: 6em;
  overflow-y: auto;
  font-size: 0.9375rem;
}

/* A button inside a status message (Copy message, when sharing failed). */
.dock #status button {
  min-height: 2.25rem;
  margin-left: 0.25rem;
  padding: 0.2rem 0.75rem;
  font-size: 0.9375rem;
  vertical-align: middle;
}

.dock #toggle {
  width: 100%;
  min-height: 3.5rem;
  font-size: 1.1875rem;
}

/* A short confirmation above the dock ("Screens cleared"). It fades in and out
   by itself (app.js), and leaves the status line for what stays true. */
.toast {
  position: fixed;
  top: auto;
  right: auto;
  bottom: var(--toast-bottom, 7rem);
  left: 50%;
  z-index: 5;
  width: max-content;
  max-width: min(22rem, calc(100vw - 2rem));
  margin: 0;
  padding: 0.5rem 0.875rem;
  overflow: visible;
  border: 1px solid rgb(255 255 255 / 15%);
  border-radius: 999px;
  background: rgb(28 28 30 / 92%);
  color: #ffffff;
  font-family: var(--ui-font);
  font-size: 0.9375rem;
  font-weight: 500;
  text-align: center;
  pointer-events: none;
  opacity: 0;
  transform: translate(-50%, 0.375rem);
  transition: opacity 0.25s ease, transform 0.25s ease;
}

.toast::before {
  content: "✓ ";
}
.toast.problem::before {
  content: none;
}

.toast.show {
  opacity: 1;
  transform: translate(-50%, 0);
}

/* The speech indicator: a dot that lights up and pulses when speech is heard,
   beside the level bars. */
.listening {
  align-items: center;
  gap: 0.625rem;
}

.listening .dot {
  width: 0.625rem;
  height: 0.625rem;
  border-radius: 50%;
  background: var(--muted);
  opacity: 0.6;
  transition: background 0.15s ease, opacity 0.15s ease;
}

.listening[data-speaking] .dot {
  background: var(--tally);
  opacity: 1;
  animation: speak-pulse 1.1s ease-out infinite;
}

/* The level bars in the dock: red and following the voice while speech is
   heard; otherwise grey and gently breathing, so the dock never looks frozen
   or dead. (Individual `scale` composes with the level-driven `transform`.) */
/* Bigger than the setup meter, so it reads as a waveform and not as dots. */
.listening .meter {
  gap: 4px;
  width: 2.5rem;
  height: 1.75rem;
}

.listening .meter i {
  width: 5px;
  background: var(--muted);
  transform: scaleY(calc(0.25 + var(--level) * var(--gain) * 0.75));
}

.listening[data-speaking] .meter i {
  background: var(--tally);
}

.listening:not([data-speaking]) .meter i {
  animation: bar-idle 1.8s ease-in-out infinite;
}

.listening .meter i:nth-child(2) {
  animation-delay: 0.15s;
}

.listening .meter i:nth-child(3) {
  animation-delay: 0.3s;
}

.listening .meter i:nth-child(4) {
  animation-delay: 0.45s;
}

.listening .meter i:nth-child(5) {
  animation-delay: 0.6s;
}

@keyframes bar-idle {
  0%,
  100% {
    scale: 1 1;
  }
  50% {
    scale: 1 2.4;
  }
}

@keyframes speak-pulse {
  from {
    box-shadow: 0 0 0 0 color-mix(in srgb, var(--tally) 55%, transparent);
  }
  to {
    box-shadow: 0 0 0 0.6rem transparent;
  }
}

/* The sheet behind More, and the QR code: native dialogs, which keep focus
   inside, close on Esc and return focus when closed (public/sheet.js). On a
   phone they rise from the bottom edge. */
.sheet {
  /* The sticky title bar's height: what the pinned caption preview sits under. */
  --sheet-head: 3.75rem;
  /* Scrolling something into view leaves the sticky title bar clear of it. */
  scroll-padding-block: 4rem 0.5rem;
  width: min(100%, 30rem);
  /* dvh follows the visible screen as Safari's toolbars come and go; vh is the
     fallback for browsers without it. */
  max-height: min(85vh, 44rem);
  max-height: min(85dvh, 44rem);
  padding: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  border: 1px solid var(--line);
  border-radius: 28px;
  background: var(--group-bg);
  color: var(--text);
}

/* The dimmed, blurred screen behind a sheet. */
.sheet::backdrop {
  background: rgb(0 0 0 / 40%);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
}

.sheet-card {
  display: grid;
  gap: 1rem;
  /* The bottom keeps 16 px clear of the home bar, not just level with it. */
  padding: 0.5rem max(1rem, env(safe-area-inset-right)) calc(1rem + env(safe-area-inset-bottom))
    max(1rem, env(safe-area-inset-left));
}

.sheet-head {
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  box-sizing: border-box;
  min-height: var(--sheet-head);
  /* Room under the title bar, so what scrolls past disappears 12 px below
     Done rather than touching it (Mark, 2026-10-27, from his phone). The
     negative margin keeps the layout's gap to the first group as it was. */
  padding-block: 0.25rem 0.75rem;
  margin-bottom: -0.5rem;
  background: var(--group-bg);
}

.sheet h2 {
  margin: 0;
  font-size: 1.25rem;
  font-weight: 600;
}

/* The title takes focus when a sheet opens (sheet.js); it can't be tabbed
   to, so it needs no ring. */
.sheet h2:focus {
  outline: none;
}

/* Grouped lists, as in iOS Settings: rows in a rounded card with hairlines
   between them. */
.sheet .group {
  min-width: 0; /* never wider than the sheet, whatever a row holds */
  overflow: hidden;
  /* clip keeps the rounded corners without making the card a scroll box, so
     the caption preview can stick below the sheet's title bar. */
  overflow: clip;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--cell);
}

.sheet div.group > * + * {
  border-top: 1px solid var(--line);
}

/* The Help sheet: short sections, each in its own card. */
.sheet .help-section {
  padding: 0.75rem 1rem 0.875rem;
}

.help-section h3 {
  margin: 0 0 0.25rem;
  font-size: 1.0625rem;
  font-weight: 600;
}

.help-section p {
  margin: 0;
  font-size: 1.0625rem;
  line-height: 1.45;
}

.help-section p + p {
  margin-top: 0.5rem;
}

/* A line under a group saying what one of its rows is for, as iOS Settings
   does; the 15 px label size, not the 13 px small print (Mark, 2026-10-27). */
.sheet .group-note {
  margin: -0.5rem 1rem 0;
  font-size: 0.9375rem;
  line-height: 1.4;
  color: var(--muted);
}

.sheet .row,
.sheet a.button.row {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 0.75rem;
  width: 100%;
  min-height: 3rem;
  padding: 0.5rem 1rem;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--text);
  font-size: 1.0625rem;
  font-weight: 400;
  text-align: left;
}

.sheet .row:hover,
.sheet a.button.row:hover {
  background: transparent;
}

.sheet button.row:active,
.sheet a.row:active {
  background: var(--raised);
}

.qr-card {
  gap: 0.875rem; /* a little tighter than the More sheet, so the code stays in view */
  justify-items: center;
  text-align: center;
}

/* The title bar is the same as the other sheets' (title left, Done right),
   so it isn't centred with the rest. */
.qr-card .sheet-head {
  justify-self: stretch;
  text-align: left;
}

.qr-card .hint {
  margin: 0;
  /* Instructions, so the 17 px reading size and the text colour, not the
     13 px muted small print the hint class gives elsewhere on this page. */
  font-size: 1.0625rem;
  line-height: 1.4;
  color: var(--text);
}

/* The two side by side: New code | Clear screens. */
.sheet .row-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
}

.sheet .row-pair .row {
  justify-content: center;
  text-align: center;
}

.sheet .row-pair .row + .row {
  border-left: 1px solid var(--line);
}

.sheet .terms {
  padding: 0 1rem;
  border: 0;
  border-radius: 0;
  background: transparent;
}

.sheet .terms summary {
  width: 100%;
  min-height: 3rem;
  color: var(--text);
  font-size: 1.0625rem;
}

/* A row's worth of room under the box, so it isn't pressed against the
   hairline below (the sheet's scroll-padding keeps it clear of the edge). */
.names-terms-content {
  padding-bottom: 1rem;
}

/* Speaking pace (live only): the pace in words per minute as a thin row of steps
   filling left to right (green to 120, amber to 160, red above, each 10 wpm its
   own shade), on the dock's own background. Quiet on purpose (Mark, 2026-10-25:
   the dark box it had was too bold): flat colours, no glow, unlit steps a faint
   grey. It can be put away from the More sheet (hidden). */
.pace,
.pace[hidden] {
  display: none;
}

[data-mode="live"] .pace:not([hidden]) {
  display: grid;
  gap: 4px;
  flex: 1 1 100%;
  order: -3;
  padding: 0.125rem 0;
  color: var(--text);
}

.pace-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
  font-size: 0.8125rem;
}

.pace-value {
  font-size: 1rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.pace-unit,
.pace-word {
  color: var(--muted);
}

.pace-word {
  font-weight: 500;
}

.pace[data-band="comfortable"] .pace-word {
  color: light-dark(hsl(125 60% 30%), hsl(125 50% 60%));
}

.pace[data-band="quick"] .pace-word {
  color: light-dark(hsl(38 90% 32%), hsl(42 80% 60%));
}

.pace[data-band="fast"] .pace-word {
  color: light-dark(hsl(6 70% 42%), hsl(6 80% 66%));
}

.pace-leds {
  display: grid;
  grid-template-columns: repeat(24, 1fr);
  gap: 2px;
}

.pace-leds i {
  display: block;
  height: 6px;
  border-radius: 1px;
  background: color-mix(in srgb, var(--text) 10%, transparent);
  transition: background 0.15s;
}

.pace-leds i.on {
  background: light-dark(hsl(var(--h) 60% 44%), hsl(var(--h) 55% 54%));
}

/* Where 120 and 160 fall along the bar. */
.pace-scale {
  position: relative;
  height: 0.75rem;
  font-size: 0.6875rem;
  line-height: 0.75rem;
  color: var(--muted);
}

.pace-scale span {
  position: absolute;
}

.pace-scale span:nth-child(1) {
  left: 0;
}

.pace-scale span:nth-child(2) {
  left: 50%;
  transform: translateX(-50%);
}

.pace-scale span:nth-child(3) {
  left: 66.667%;
  transform: translateX(-50%);
}

.pace-scale span:nth-child(4) {
  right: 0;
}

@media (min-width: 40rem) {
  [data-mode="live"] .pace:not([hidden]) {
    flex: 1 1 14rem;
    order: 0;
    max-width: 22rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  .pace-leds i {
    transition: none;
  }
}

/* Apply now: sends the box to the running session. */
.sheet .apply {
  width: 100%;
  min-height: 44px;
  margin-top: 0.75rem;
}

.sheet .apply[hidden] {
  display: none;
}

/* Caption text and colours: a preview that stays in view under the title bar
   while the chips scroll, then rows of tappable chips. */
.caption-preview {
  position: sticky;
  top: var(--sheet-head); /* directly under the sheet's title bar */
  /* A 12 px band of the card's own background all round, so chips sliding
     under it vanish clear of its edge instead of peeking out beside it
     (Mark, 2026-10-27, from his phone: "looks messy on scroll"). */
  box-shadow: 0 0 0 0.75rem var(--cell);
  margin: 0.75rem 0 1.25rem;
  padding: 0.75rem 1rem;
  /* The last few whole lines of the sample, as the caption box keeps the latest
     words in view: the text sits at the bottom of a box exactly --preview-lines
     lines tall (3, or 2 at the largest sizes; app.js sets it) and the earlier
     lines are clipped above it, never a line cut in half. The 1px keeps a
     rounding sliver of the line above from showing. The real size, so A− and
     A+ show their effect here while the captions behind are dimmed. */
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  box-sizing: content-box;
  height: calc(var(--preview-lines, 3) * var(--caption-line-height, 1.5) * var(--caption-size, 2rem) - 1px);
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--bg);
  color: var(--text);
  font-family: var(--caption-font, system-ui, sans-serif);
  font-size: var(--caption-size, 2rem);
  line-height: var(--caption-line-height, 1.5);
}

.caption-preview p {
  margin: 0;
}

/* In the Display sheet the preview sits on the sheet itself, not in a white
   cell, so its band is the sheet's colour. */
#display-dialog .caption-preview {
  box-shadow: 0 0 0 0.75rem var(--group-bg);
}

/* The same chips serve the viewer's Display panel (.controls). */
.sheet .option {
  margin-bottom: 1rem;
}

.sheet .option-label,
.controls .option-label {
  display: block;
  margin-bottom: 0.375rem;
  font-size: 0.9375rem;
  color: var(--muted);
}

.sheet .chips,
.controls .chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.sheet .chip,
.controls .chip {
  min-width: 44px;
  min-height: 44px;
  padding: 0 0.875rem;
  border: 1px solid var(--line);
  border-radius: 22px;
  background: var(--cell, var(--bg));
  color: var(--text);
  font-size: 0.9375rem;
  font-weight: 400;
  touch-action: manipulation;
}

.sheet .chip.swatch,
.controls .chip.swatch {
  background: var(--chip-bg);
  color: var(--chip-text);
}

.sheet .chip[aria-checked="true"],
.controls .chip[aria-checked="true"] {
  border-color: var(--text);
  box-shadow: inset 0 0 0 1px var(--text);
  font-weight: 600;
}

.sheet .chip:disabled,
.controls .chip:disabled {
  opacity: 0.4;
}

/* Text size and line spacing as sliders (Mark, 2026-10-27): text size
   between A− and A+, spacing with its word beside the track. The browser's
   own range control, drawn as a thin track in the muted colour with a 28 px
   thumb in the text colour, inside a 44 px row so it is easy to catch with a
   finger. A vertical swipe over it still scrolls the panel (pan-y). */
.slider-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  width: 100%;
  max-width: 28rem;
}

/* Its own row in the viewer's wrapping panel, so the track gets the width. */
.controls .option:has(> .slider-row) {
  flex: 1 1 100%;
}

/* The spacing slider's word, wide enough for "Compact" so the track never
   changes length as the word does. */
.slider-readout {
  flex: none;
  min-width: 4.5rem;
  font-size: 0.9375rem;
}

.slider-row input[type="range"] {
  flex: 1;
  min-width: 0;
  height: 44px;
  margin: 0;
  background: transparent;
  appearance: none;
  touch-action: pan-y;
  cursor: pointer;
}

.slider-row input[type="range"]::-webkit-slider-runnable-track {
  height: 6px;
  border-radius: 3px;
  background: color-mix(in srgb, var(--text) 25%, var(--bg));
}

.slider-row input[type="range"]::-webkit-slider-thumb {
  appearance: none;
  width: 28px;
  height: 28px;
  margin-top: -11px; /* centres the thumb on the 6 px track */
  border: 2px solid var(--bg);
  border-radius: 50%;
  background: var(--text);
}

.slider-row input[type="range"]::-moz-range-track {
  height: 6px;
  border-radius: 3px;
  background: color-mix(in srgb, var(--text) 25%, var(--bg));
}

.slider-row input[type="range"]::-moz-range-thumb {
  width: 24px;
  height: 24px;
  border: 2px solid var(--bg);
  border-radius: 50%;
  background: var(--text);
}

.slider-row input[type="range"]:focus-visible {
  outline: 2px solid var(--text);
  outline-offset: 2px;
  border-radius: 22px;
}

/* The custom colour picker under the colour chips (Mark, 2026-10-27): a
   background and a text colour, the contrast between them, and a name with
   Save. Shown only while Custom or a saved set is chosen. */
.colour-picker {
  display: grid;
  gap: 0.75rem;
  width: 100%;
  flex-basis: 100%; /* its own row in the viewer's panel */
}

.colour-picker[hidden] {
  display: none;
}

.colour-fields {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
}

/* Named to outrank the .terms label rule in the broadcaster's sheet. */
.colour-picker label.colour-field {
  display: inline-flex;
  align-items: center;
  gap: 0.625rem;
  margin: 0;
  min-height: 44px;
  font-size: 1.0625rem;
}

/* The colour wells: round swatches the size of a chip. */
.colour-picker input[type="color"] {
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: none;
  cursor: pointer;
  touch-action: manipulation;
}

.colour-picker input[type="color"]::-webkit-color-swatch-wrapper {
  padding: 0;
}

.colour-picker input[type="color"]::-webkit-color-swatch,
.colour-picker input[type="color"]::-moz-color-swatch {
  border: 0;
  border-radius: 50%;
}

.contrast-note {
  margin: 0;
  font-size: 0.9375rem;
  color: var(--muted);
}

.contrast-note[data-bad] {
  color: var(--text);
  font-weight: 600;
}

.colour-save {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.colour-save input[type="text"] {
  flex: 1 1 10rem;
  min-height: 44px;
  padding: 0 0.875rem;
  border: 1px solid var(--line);
  border-radius: var(--radius, 12px);
  background: var(--cell, var(--bg));
  color: var(--text);
  font: inherit;
  font-size: 1.0625rem;
}

.colour-save button {
  min-width: 5rem;
}

/* Remove only shows for a saved set (buttons are inline-flex otherwise). */
.colour-save button[hidden] {
  display: none;
}

/* The picker for saved lists: a plain <select> with iOS form styling. The list
   it opens is the phone's own (Apple's picker); a page can't change that. */
.sheet select.ios-picker {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 44px;
  padding: 0 40px 0 16px; /* the right side leaves room for the chevron */
  font-size: 1.0625rem; /* the field size; 16 px or more, so iOS doesn't zoom in */
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background-color: var(--cell);
}

/* On a short screen the names box is shorter, so the section fits the sheet's
   room and its buttons and box are all in view. (It can still be pulled taller.) */
@media (max-height: 40rem) {
  .sheet .terms textarea {
    height: 5rem;
  }
}

/* Destructive, and in an account group of its own. */
.sheet .row.destructive {
  justify-content: center;
  color: #ff3b30;
  text-align: center;
}

@media (prefers-color-scheme: dark) {
  .sheet .row.destructive {
    color: #ff453a;
  }
}

/* The event code to type, as a badge so it's easy to read out. */
.qr-code-line {
  display: grid;
  justify-items: center;
  gap: 0.375rem;
  margin: 0;
}

.qr-card .share-code {
  padding: 0.375rem 1rem;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--cell);
  color: var(--text);
}

/* The picture is no bigger than 240 px, and smaller on a short screen: what's
   left of the sheet's height (85% of the screen) after the title bar, text
   and code (about 22rem), so the code stays in view without scrolling. */
#qr-image {
  width: 100%;
  max-width: 240px;
  max-width: min(240px, max(9rem, calc(85dvh - 22rem)));
}

#qr-image svg {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius);
}

@keyframes sheet-up {
  from {
    transform: translateY(100%);
  }
}

/* The laptop's Setup card dropping in under its button. */
@keyframes setup-pop {
  from {
    opacity: 0;
    transform: translateY(-0.5rem);
  }
}

@media (prefers-reduced-motion: reduce) {
  .sheet,
  .live-dot,
  .listening .meter i,
  .listening[data-speaking] .dot {
    animation: none !important;
  }

  .toast {
    transition: none;
  }
}

/* Phone */
@media (max-width: 39.99rem) {
  .sheet {
    width: 100%;
    max-width: 100%;
    margin: auto 0 0;
    border-bottom: 0;
    border-radius: 28px 28px 0 0;
    animation: sheet-up 0.25s ease-out;
  }

  /* The grabber pill at the top centre, as on a native iOS sheet. It stays put
     while the content scrolls. */
  .sheet::before {
    content: "";
    position: sticky;
    top: 0;
    z-index: 2;
    display: block;
    width: 36px;
    height: 5px;
    margin: 6px auto -5px;
    border-radius: 3px;
    background: rgb(0 0 0 / 20%);
  }

  /* Room under the pill. In More, the title bar owns that room, so it stays
     clear of the pill when the content scrolls. */
  .sheet-card {
    padding-top: 0;
  }

  .sheet-head {
    padding-top: 1.25rem;
  }

  .sheet {
    --sheet-head: 4.75rem; /* the title bar with the pill's room above it */
  }

  /* If setup is taller than the screen, the header scrolls (the name and More
     stay put) and the captions and Go live stay in view. */
  .broadcast header {
    min-height: 0;
    padding-top: 0;
    overflow: hidden auto;
    overscroll-behavior: contain;
  }

  .broadcast .top {
    position: sticky;
    top: 0;
    z-index: 1;
    padding-block: calc(env(safe-area-inset-top) + 0.75rem) 0.375rem;
    background: var(--panel);
  }

  /* The top bar must hold the wordmark, the viewer count, Setup and More in
     375 px: the count is an eye and a number, and the buttons are a little
     narrower. The event name beside the wordmark doesn't fit; it's in the
     setup. */
  .broadcast .top .event-title,
  .top .watching .watching-word {
    display: none;
  }

  .top .watching svg {
    display: block;
  }

  .broadcast .top button,
  .top .watching {
    padding-inline: 0.75rem;
  }

  .broadcast .top #setup-toggle {
    gap: 0.45rem;
  }

  .broadcast {
    /* Room to scroll clear of the dock floating over the bottom: at least
       110 px plus the home-bar inset, or the dock's measured height if that's
       taller (a long status). Used by the captions and by the setup. */
    --dock-clear: max(var(--dock-space, 0px), calc(110px + env(safe-area-inset-bottom)));
  }

  /* Never squeezed to nothing: with a long setup above it (after Stop, say),
     the captions keep a few lines of their own above the dock, and the setup
     scrolls in the rest. */
  .broadcast #captions {
    flex: 1 0 calc(var(--dock-clear) + 6rem);
    padding-bottom: var(--dock-clear);
  }

  /* Before going live there's nothing to read yet, so an open setup has the
     screen; folded, the empty pane shows its note instead. */
  [data-mode="setup"]:has(.setup-panel:not([hidden])) #captions:empty {
    display: none;
  }

  [data-mode="setup"]:has(.setup-panel:not([hidden])):has(#captions:empty) header {
    padding-bottom: var(--dock-clear);
    border-bottom: 0;
  }

  /* The dock floats over the bottom of the screen as a frosted-glass card:
     the captions scroll underneath and show through, blurred. 12 px clear of
     the home bar. */
  .dock {
    position: fixed;
    z-index: 100;
    right: max(16px, env(safe-area-inset-right));
    bottom: calc(12px + env(safe-area-inset-bottom));
    left: max(16px, env(safe-area-inset-left));
    margin: 0;
    padding: 0.75rem;
    border: 1px solid rgb(0 0 0 / 8%);
    border-radius: 24px;
    background: rgb(255 255 255 / 96%);
    box-shadow: 0 10px 30px rgb(0 0 0 / 8%);
  }

  @supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
    .dock {
      background: rgb(255 255 255 / 82%);
      -webkit-backdrop-filter: blur(20px) saturate(180%);
      backdrop-filter: blur(20px) saturate(180%);
    }
  }

  @media (prefers-color-scheme: dark) {
    .dock {
      border-color: rgb(255 255 255 / 12%);
      background: rgb(28 28 30 / 96%);
      box-shadow: 0 8px 32px rgb(0 0 0 / 40%);
    }

    @supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
      .dock {
        background: rgb(28 28 30 / 78%);
      }
    }
  }

  /* Live: a slim red strip, the captions, and the floating dock with Stop, the
     speech indicator and More. Everything else is behind More. */
  [data-mode="live"] header {
    display: none;
  }

  [data-mode="live"] #live-banner {
    gap: 0.625rem;
  }

  [data-mode="live"] #live-banner .more {
    display: none;
  }

  [data-mode="live"] #live-banner .live-code,
  [data-mode="live"] #live-banner #live-share {
    display: inline-flex;
  }

  [data-mode="live"] .dock {
    flex-flow: row wrap;
    align-items: center;
    gap: 0.5rem 0.75rem;
    padding: 0.5rem;
  }

  [data-mode="live"] .dock #toggle {
    order: -2;
    flex: 0 0 auto;
    width: auto;
    min-width: 7rem;
    min-height: 3rem;
    font-size: 1.0625rem;
  }

  [data-mode="live"] .dock .listening {
    display: inline-flex;
    flex: 1 1 auto;
    justify-content: center;
    order: -1;
  }

  [data-mode="live"] .dock #live-more {
    display: inline-flex;
    order: 1;
    width: 3rem;
    min-width: 3rem;
    min-height: 3rem;
    background: color-mix(in srgb, var(--text) 8%, transparent);
  }

  /* A message that matters (a problem, say) gets the top of the dock. The
     usual "Listening" is left to the indicator and to screen readers. */
  [data-mode="live"] .dock #status {
    order: -3;
    flex: 1 1 100%;
    padding: 0.25rem 0.5rem 0;
  }

  [data-mode="live"] .dock #status[data-quiet] {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
  }
}

@media (max-width: 39.99rem) and (prefers-color-scheme: dark) {
  .sheet::before {
    background: rgb(255 255 255 / 30%);
  }
}

/* Tablet and up */
@media (min-width: 40rem) {
  .setup {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    max-width: 48rem;
  }

  /* A sheet that uses a laptop's width: 48rem, the same as the setup, with a
     margin either side (it is the full width on a phone). The five colour
     chips then sit on one row. */
  .sheet {
    width: min(100% - 4rem, 48rem);
    margin: auto;
  }

  /* The QR sheet holds one picture and a code, so it stays the narrow size. */
  #qr-dialog {
    width: min(100% - 4rem, 30rem);
  }

  /* Setup floats over the captions under its button, as a card, so the text
     never moves when it opens (app.js closes it on a click outside, Esc and
     Go live). */
  .broadcast header {
    position: relative;
  }

  .setup-panel {
    position: absolute;
    z-index: 20;
    top: calc(100% + 0.5rem);
    right: max(1rem, env(safe-area-inset-right));
    width: min(48rem, 100% - 2rem);
    padding: 1rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--group-bg);
    box-shadow: 0 12px 32px rgb(0 0 0 / 14%);
  }

  .setup-panel .share {
    align-self: stretch;
  }

  @media (prefers-reduced-motion: no-preference) {
    .setup-panel {
      animation: setup-pop 0.15s ease-out;
    }
  }

  /* Live: the red strip's badge, code and count move into the top bar. */
  [data-mode="live"] #live-banner {
    display: none;
  }

  [data-mode="live"] .top .top-live {
    display: inline-flex;
  }

  [data-mode="live"] .top .watching {
    display: none;
  }

  .top .top-tools {
    display: inline-flex;
  }

  /* With Display in the bar (Mark, 2026-11-06) a tablet's bar is full: the
     A− / A+ shortcut waits for a laptop's width, since the sheet has the size
     slider one tap away. */
  .top .size-pair {
    display: none;
  }
}

@media (min-width: 60rem) {
  .top .size-pair {
    display: inline-flex;
  }

  /* One route to the QR code at this width (Mark, 2026-10-27): the top bar
     icon. The share card's button and the sheet's row are for a phone, where
     the bar has no room for the icon. The row after the hidden one loses the
     divider it would otherwise draw. */
  #show-qr,
  #sheet-qr,
  #sheet-help-group,
  #sheet-display-group {
    display: none;
  }

  #sheet-qr + .row {
    border-top: 0;
  }

  .share {
    align-self: flex-start;
    grid-template-columns: auto auto;
    align-items: center;
    gap: 1rem 2rem;
  }

  .share-info {
    grid-template-columns: auto auto;
    gap: 0.75rem 2rem;
  }

  /* One button at this width: Share event (the QR code is in the top bar). */
  .share-actions {
    grid-template-columns: auto;
  }

  .dock {
    flex-direction: row;
    align-items: center;
    gap: 1rem;
  }

  /* Go live (Stop while live) at the right-hand end, with the status just to
     its left, right-aligned (Mark, 2026-10-25); while live, the level sits
     between them and the pace gauge takes the left. */
  .dock #status {
    flex: 0 1 auto;
    order: 1;
    margin-left: auto;
    text-align: right;
  }

  .dock #toggle {
    order: 3;
    width: auto;
    min-width: 10rem;
  }

  [data-mode="live"] .dock .listening {
    display: inline-flex;
    order: 2;
  }
}

/* The logo before the name, on the broadcaster's pages only (home,
   transcripts, Accounts; sign-in and setup have .sign-in-brand). CSS only, so the name's own markup and colours stay with
   whatever sets them. The icon file is a full-bleed square, rounded here. The
   viewer page's name is the event name and gets no logo. Sized to the 44 px
   top bar (Mark, 2026-10-26: at 28 px and 20 px the brand read lighter than
   the pills beside it): the logo fills most of the line and the name is a
   clear step above the 17 px button text. */
.broadcast .wordmark {
  font-size: 1.375rem;
  font-weight: 700;
  letter-spacing: -0.01em;
}

.broadcast .wordmark::before {
  content: "";
  display: inline-block;
  width: 2.25rem;
  height: 2.25rem;
  margin-right: 0.5rem;
  border-radius: 22%;
  background: url("/icon.svg") center / cover;
  vertical-align: middle;
}

/* On a phone the top bar has to hold the logo and name, the viewer count,
   Setup and More in 375 px, so the logo and name are a little smaller. */
@media (max-width: 39.99rem) {
  .broadcast .wordmark {
    font-size: 1.125rem;
  }

  .broadcast .wordmark::before {
    width: 1.5rem;
    height: 1.5rem;
    margin-right: 0.375rem;
  }
}

/* Accounts page (admin only, docs/accounts-plan.md step 2): a card to add
   someone, the setup link it makes, and a grouped card of the accounts, in the
   transcripts page's style. */
.admin-page .page {
  max-width: 52rem;
  margin-inline: auto;
}

.admin-page #status {
  margin: 0 0 0.75rem;
  color: var(--muted);
}

.admin-page #status:empty {
  display: none;
}

.admin-card {
  display: grid;
  gap: 0.75rem;
  margin-bottom: 1rem;
  padding: 1rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--cell);
}

.admin-card[hidden] {
  display: none;
}

.admin-card h2 {
  margin: 0;
  font-size: 1.0625rem;
  font-weight: 600;
  color: var(--text);
}

.admin-card p {
  margin: 0;
  color: var(--muted);
}

.admin-form {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.75rem;
}

.admin-form .field input {
  width: 100%;
}

@media (min-width: 40rem) {
  .admin-form {
    grid-template-columns: 1fr 1fr 8rem auto;
    align-items: end;
  }
}

.setup-link-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.setup-link-row input {
  flex: 1 1 16rem;
  min-width: 0;
}

.accounts {
  margin: 0;
  padding: 0;
  list-style: none;
  overflow: clip;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--cell);
}

.accounts:empty {
  display: none;
}

.accounts li {
  display: grid;
  gap: 0.25rem 0.75rem;
  padding: 0.75rem;
}

.accounts li + li {
  border-top: 1px solid var(--line);
}

.accounts .who {
  display: grid;
  min-width: 0;
  font-size: 1.0625rem;
}

.accounts .email,
.accounts .usage-line {
  overflow-wrap: anywhere;
  font-size: 0.9375rem;
  color: var(--muted);
}

.accounts .badge {
  margin-left: 0.5rem;
  padding: 0.0625rem 0.5rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: 0.8125rem;
  color: var(--muted);
}

.accounts .actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.broadcast .accounts button {
  padding-inline: 0.75rem;
  border-color: transparent;
  font-size: 0.9375rem;
}

#sheet-accounts[hidden] {
  display: none;
}

/* The Waiting card on the Accounts page (requests for an account): the list
   sits inside the card, so it drops its own border and side padding. */
.admin-card .accounts {
  border: 0;
  border-radius: 0;
  background: none;
}

.admin-card .accounts li {
  padding-inline: 0;
}

#thanks {
  margin: 0;
  font-size: 1.0625rem;
  color: var(--text);
}

/* The strip at the top of the Accounts page (docs/admin-and-own-key-plan.md,
   stage 2): one line of totals and a muted note under it. */
.admin-strip {
  margin-bottom: 1rem;
}

.admin-strip[hidden] {
  display: none;
}

.admin-strip p {
  margin: 0;
}

#strip-line {
  font-size: 1.0625rem;
  color: var(--text);
}

#strip-note {
  font-size: 0.9375rem;
}

/* The red LIVE pill (as in the broadcaster's top bar) on a live account's row. */
.accounts .badge.live {
  border-color: transparent;
  background: #ff3b30;
  font-weight: 600;
  color: #fff;
}
