@import url('https://fonts.googleapis.com/css2?family=Newsreader:ital,opsz,wght@0,6..72,400;0,6..72,500;1,6..72,400&family=Noto+Sans:wght@400;500;600;700&display=swap');

/* Palette taken from a chapel interior: hymnbook cloth, plaster, brass fittings. */
:root {
  --ink:       #16233a;
  --ink-deep:  #0c1424;
  --ink-soft:  #22334f;
  --paper:     #f1f2ee;
  --card:      #ffffff;
  --rule:      #d9dbd4;
  --slate:     #626d7e;
  --slate-dim: #8b94a1;
  --brass:     #a8823c;
  --brass-lit: #d6af6a;
  --brass-dim: rgba(168, 130, 60, 0.16);
  --alarm:     #9c3b32;
  --ok:        #3f6b52;

  --display: 'Newsreader', Georgia, serif;
  --ui: 'Noto Sans', system-ui, -apple-system, 'Segoe UI', sans-serif;

  --step: clamp(0.9rem, 0.85rem + 0.2vw, 1rem);
  --radius: 3px;
  --shadow: 0 1px 2px rgba(22, 35, 58, 0.06), 0 8px 24px -16px rgba(22, 35, 58, 0.4);
}

*, *::before, *::after { box-sizing: border-box; }

/*
 * `hidden` has to beat every display rule below it. Without this, a button set
 * to display:inline-flex stays visible even when hidden, which is how video
 * controls turned up in a Meeting.
 */
[hidden] { display: none !important; }

html, body { height: 100%; }
body {
  margin: 0;
  font-family: var(--ui);
  font-size: var(--step);
  line-height: 1.55;
  color: var(--ink);
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
}

body.stage { background: var(--ink-deep); color: #e8eaee; }

h1, h2, h3 { font-family: var(--display); font-weight: 400; letter-spacing: -0.01em; margin: 0 0 0.4em; }
h1 { font-size: clamp(1.7rem, 1.3rem + 1.6vw, 2.4rem); line-height: 1.12; }
h2 { font-size: 1.35rem; }
h3 { font-size: 1.05rem; font-family: var(--ui); font-weight: 600; }
p { margin: 0 0 1em; }
a { color: var(--ink); text-decoration-color: var(--brass); text-underline-offset: 3px; }

.eyebrow {
  font-size: 0.7rem; font-weight: 600; letter-spacing: 0.13em;
  text-transform: uppercase; color: var(--brass); margin-bottom: 0.7em;
}
.muted { color: var(--slate); }
.small { font-size: 0.82rem; }
.mono { font-variant-numeric: tabular-nums; }

/* ------------------------------- structure -------------------------------- */

.shell { max-width: 1120px; margin: 0 auto; padding: 0 clamp(1rem, 4vw, 2.5rem); }
.narrow { max-width: 480px; }

.topbar {
  display: flex; align-items: center; gap: .5rem 1rem;
  /* Controls drop onto a second line on a phone rather than running off the
     side of the screen where nobody can reach them. */
  flex-wrap: wrap;
  padding: .8rem clamp(.9rem, 4vw, 2.5rem);
  border-bottom: 1px solid var(--rule);
  background: var(--card);
}
/* The controls live in .topbar-actions now, not directly in the bar. */
.topbar-actions {
  display: flex; align-items: center; gap: .5rem .8rem;
  /* The whole point: the group never breaks internally, so translated labels
     change only its width, never which line a control lands on. */
  flex-wrap: nowrap;
  margin-left: auto; min-width: 0;
}
.topbar-actions > select,
.topbar-actions > button,
.topbar-actions > .btn { flex: 0 0 auto; min-width: 0; }

/* index, join, about and 404 write their own bar and put controls straight
   into it rather than through mountTopbar. They still need the base sizing. */
.topbar > select,
.topbar > button,
.topbar > .btn { flex: 0 1 auto; min-width: 0; max-width: 100%; }

/*
 * Below this width the group takes a line of its own — always, in every
 * language. Measured rather than guessed: the brand is 171px and the widest
 * group (Tamil) is 733px, so one line needs about 984px. The breakpoint sits
 * above that with headroom for a longer name than mine, which is the other
 * thing that can widen the group.
 *
 * The result is two shapes and only two, chosen by the window: everything on
 * one line, or the brand above the controls. Switching language never moves a
 * button now.
 */
@media (max-width: 1099px) {
  .topbar-actions { flex: 1 0 100%; margin-left: 0; justify-content: flex-end; }
}

/* Truncate rather than push: a long name must not be able to force a wrap
   above the breakpoint, or the shape would depend on who is signed in. */
.topbar-actions .whoami {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  min-width: 0; flex: 0 1 auto;
}

@media (max-width: 640px) {
  .topbar { gap: .4rem; align-items: stretch; }
  .topbar .brand { flex: 1 0 100%; font-size: 1rem; align-items: baseline; }
  .topbar .spacer { display: none; }
  /* Equal-width controls on a shared row read as a set rather than a jumble,
     and every one stays big enough to tap. */
  /*
     A phone cannot fit one unbroken row, and letting it wrap put the controls
     on three lines in Chinese and four in Tamil. A grid instead of a wrap: the
     cells are fixed, so every control keeps its position whatever the labels
     say. Long words are clipped rather than allowed to move anything.
  */
  .topbar-actions {
    display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: .4rem; width: 100%; justify-content: stretch;
  }
  .topbar-actions > select, .topbar-actions > button, .topbar-actions > .btn {
    width: 100%; font-size: .82rem; padding: .45rem .4rem; min-height: 2.2rem;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  /* The picker's fixed width is for the one-line layout; in the grid the cell
     decides, or the columns stop being equal. */
  .topbar-actions > .topbar-lang { width: 100%; min-width: 0; max-width: none; }
  /* Same treatment for the hand-written bars. */
  .topbar > select, .topbar > button, .topbar > .btn {
    flex: 1 1 auto; font-size: .82rem; padding: .45rem .5rem;
    text-overflow: ellipsis; min-height: 2.2rem;
  }
  .topbar > button, .topbar > .btn { flex: 0 0 auto; }
  /* Name and ward take a row of their own, below the controls. */
  .topbar-actions .whoami { grid-column: 1 / -1; order: 9; text-align: right; }
}
.stage .topbar { background: transparent; border-bottom-color: rgba(255,255,255,0.08); }

.brand { display: flex; align-items: baseline; gap: 0.6rem; font-family: var(--display); font-size: 1.15rem; }
.brand b { font-weight: 500; }
.brand span { font-family: var(--ui); font-size: 0.66rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--brass); }
.spacer { flex: 1; }
/* The entry for the page you are already on. Still a link — the host page has
   a room list behind the same address — but it should not look like somewhere
   else to go. */
.topbar .here { border-color: transparent; background: transparent; font-weight: 600; }
/* Secondary reading rather than navigation. */
.topbar .quiet {
  border-color: transparent; font-size: 0.72rem;
  color: var(--slate); font-weight: 400;
}
.topbar .quiet:hover { color: var(--ink); border-color: transparent; text-decoration: underline; }
/* On a narrow screen the name and ward are the first thing worth dropping —
   the buttons still have to be reachable. */
/* Handled by the grid above at phone widths; this remains for the band between
   560px and 640px where the row layout still applies. */
@media (max-width: 560px) { .topbar .whoami { order: 9; } }

.card {
  background: var(--card); border: 1px solid var(--rule);
  border-radius: var(--radius); padding: clamp(1.1rem, 3vw, 1.75rem);
  box-shadow: var(--shadow);
}
.stack > * + * { margin-top: 1rem; }
.row { display: flex; gap: 0.6rem; align-items: center; flex-wrap: wrap; }
.grid { display: grid; gap: 1rem; }
@media (min-width: 860px) { .grid.two { grid-template-columns: 1fr 1fr; } }
/* The terminology screen: the form keeps a usable minimum and the wide
   saved-terms table scrolls in place instead of squeezing it to a sliver. */
@media (min-width: 860px) { .grid.termsplit { grid-template-columns: minmax(24rem, 2fr) minmax(0, 3fr); } }

/* --------------------------------- forms ---------------------------------- */

label { display: block; font-size: 0.78rem; font-weight: 600; letter-spacing: 0.02em; margin-bottom: 0.3rem; }
input, select, textarea, button { font: inherit; }
input, select, textarea {
  width: 100%; padding: 0.6rem 0.7rem;
  border: 1px solid var(--rule); border-radius: var(--radius);
  background: var(--card); color: inherit;
}
input:focus-visible, select:focus-visible, textarea:focus-visible, button:focus-visible, [tabindex]:focus-visible {
  outline: 2px solid var(--brass); outline-offset: 2px;
}
.field + .field { margin-top: 0.85rem; }
.field-error { color: var(--alarm); font-size: 0.78rem; margin-top: 0.25rem; }
.pair { display: grid; gap: 0.85rem; align-items: start; }
/* Side by side, the gap does the spacing. Without this the stacked-field
   margin above still applies and knocks the second column down by 0.85rem. */
.pair .field + .field { margin-top: 0; }
@media (min-width: 560px) { .pair { grid-template-columns: 1fr 1fr; } }

button, .btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.45rem;
  padding: 0.6rem 1.1rem; border-radius: var(--radius); cursor: pointer;
  border: 1px solid var(--ink); background: var(--ink); color: #fff;
  font-weight: 600; font-size: 0.9rem; text-decoration: none;
  transition: background 120ms ease, border-color 120ms ease, color 120ms ease;
}
button:hover, .btn:hover { background: var(--ink-soft); border-color: var(--ink-soft); }
/* Anchors styled as buttons need this too. Without the `.btn` half, every link
   in the top bar fell through to the solid dark base style, so About and
   Administration shouted while Sign out sat quietly beside them. */
button.ghost, .btn.ghost { background: transparent; color: var(--ink); border-color: var(--rule); }
button.ghost:hover, .btn.ghost:hover { border-color: var(--ink); background: transparent; }
button.brass { background: var(--brass); border-color: var(--brass); }
button.brass:hover { background: #96742f; border-color: #96742f; }
button.danger { background: transparent; color: var(--alarm); border-color: rgba(156,59,50,0.4); }
button.danger:hover { background: rgba(156,59,50,0.08); }
button[disabled] { opacity: 0.45; cursor: not-allowed; }
button.tiny { padding: 0.28rem 0.6rem; font-size: 0.76rem; }

.stage button.ghost { color: #e8eaee; border-color: rgba(255,255,255,0.22); }
.stage button.ghost:hover { border-color: var(--brass-lit); }

.notice {
  padding: 0.7rem 0.9rem; border-left: 3px solid var(--brass);
  background: var(--brass-dim); font-size: 0.86rem; border-radius: 0 var(--radius) var(--radius) 0;
}
.notice.bad { border-left-color: var(--alarm); background: rgba(156,59,50,0.08); }
.notice.good { border-left-color: var(--ok); background: rgba(63,107,82,0.09); }

/* ------------------------- signature: caption stage ------------------------ */
/*
   One line at a time, centred, with the original above it in a whisper.
   The brass rule is the only moving element: it fills while a phrase is being
   interpreted and rests when the room is quiet.
*/

/*
   The stage keeps a constant height whatever arrives in it.
   
   Every new phrase used to resize the live line and add a row to the log, so
   the whole page shifted under the reader — and on a phone that means the words
   move out from under where they were looking, and under their thumb. Fixed
   regions mean nothing below the caption ever moves.
*/
.captionstage {
  display: flex; flex-direction: column;
  padding: clamp(1rem, 4vw, 2rem) 0;
  overflow-anchor: none;
}

.sourceline {
  font-size: max(0.8rem, calc(var(--caption-size, 2.1rem) * 0.38));
  color: var(--slate-dim); letter-spacing: 0.01em;
  /* Fixed, and clipped to one line: the original is a reference, not the point,
     and letting it wrap to two lines moves the caption below it. */
  height: 1.6em; line-height: 1.6em; margin-bottom: .9rem;
  overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
}
.stage .sourceline { color: rgba(232,234,238,0.42); }

.workrule { position: relative; height: 2px; background: rgba(255,255,255,0.09); overflow: hidden; margin-bottom: 1.6rem; }
body:not(.stage) .workrule { background: var(--rule); }
.workrule::after {
  content: ''; position: absolute; inset: 0; width: 32%;
  background: linear-gradient(90deg, transparent, var(--brass-lit), transparent);
  transform: translateX(-110%);
}
.workrule.working::after { animation: sweep 1.15s linear infinite; }
@keyframes sweep { to { transform: translateX(320%); } }
@media (prefers-reduced-motion: reduce) {
  .workrule.working::after { animation: none; transform: none; width: 100%; opacity: 0.5; }
}

.caption {
  /* The caption is in the spoken language, which is not necessarily the
     language of the screen around it. Falls back to the interface face when no
     separate one has been loaded. */
  font-family: var(--caption-font, var(--ui));
  font-size: var(--caption-size, 2.1rem);
  line-height: 1.42;
  font-weight: 500;
  letter-spacing: -0.005em;
  max-width: 26ch;
  text-wrap: pretty;
  /* Reserved space, not minimum space. A long phrase scrolls inside this box
     rather than pushing everything under it down the page. */
  height: 4.3em;
  overflow-y: auto;
  overscroll-behavior: contain;
  overflow-anchor: none;
}
.caption.empty { color: var(--slate-dim); font-weight: 400; font-style: italic; }

.captionlog {
  margin-top: 1.4rem; padding-top: 1rem;
  border-top: 1px solid rgba(255,255,255,0.08);
  /* Fixed height, its own scroll. Growing from empty to full used to change
     the page height on every phrase. */
  height: 34vh; overflow-y: auto;
  overscroll-behavior: contain;
  overflow-anchor: none;
}
body:not(.stage) .captionlog { border-top-color: var(--rule); }
/*
   Earlier lines scale with the size control too. Someone who needed larger text
   for the live caption needs it for the lines they are still catching up on —
   but kept smaller than the live line, so it stays obvious which is current.
*/
.captionlog li {
  list-style: none;
  margin: 0 0 0.85rem;
  font-size: max(0.78rem, calc(var(--caption-size, 2.1rem) * 0.42));
  line-height: 1.45;
  opacity: 0.62;
}
.captionlog li .src { display: block; font-size: 0.78em; opacity: 0.6; margin-top: 0.15rem; }
.captionlog ul { margin: 0; padding: 0; }

/* --------------------------- language rail (host) -------------------------- */

.rail { margin: 0; padding: 0; list-style: none; }
.rail li { display: grid; grid-template-columns: 1fr auto; gap: 0.4rem 0.8rem; padding: 0.5rem 0; border-bottom: 1px solid var(--rule); }
.rail .lname { font-weight: 600; font-size: 0.88rem; }
.rail .lnative { font-weight: 400; color: var(--slate); }
.rail .bar { grid-column: 1 / -1; height: 3px; background: var(--rule); }
.rail .bar i { display: block; height: 100%; background: var(--brass); }
.rail .count { font-size: 0.8rem; color: var(--slate); font-variant-numeric: tabular-nums; }

/* --------------------------------- roster --------------------------------- */

.people { margin: 0; padding: 0; list-style: none; max-height: 46vh; overflow-y: auto; }
.people li { display: flex; align-items: center; gap: 0.6rem; padding: 0.55rem 0; border-bottom: 1px solid var(--rule); }
.people .who { flex: 1; min-width: 0; }
.people .who b { font-weight: 600; font-size: 0.9rem; display: block; }
.people .who span { font-size: 0.76rem; color: var(--slate); }
.tag {
  font-size: 0.66rem; font-weight: 700; letter-spacing: 0.09em; text-transform: uppercase;
  padding: 0.16rem 0.4rem; border: 1px solid var(--rule); color: var(--slate); border-radius: 2px;
}
.tag.host { border-color: var(--brass); color: var(--brass); }
.tag.guest { border-style: dashed; }
/* Marks a language that produces captions but no spoken voice. Deliberately
   visible rather than a footnote: people should know before they choose. */
.tag.warn { border-color: var(--brass); color: #7d6023; background: var(--brass-dim); }
/* Glossary confidence. Deliberately legible at a glance: an unconfirmed term
   overrides the model everywhere it appears, so the risk should be visible. */
.tag.ok   { border-color: var(--ok); color: var(--ok); background: rgba(63,107,82,.08); }
.tag.mid  { border-color: var(--slate-dim); color: var(--slate); }
.tag.low  { border-color: var(--alarm); color: var(--alarm); background: rgba(156,59,50,.06); }
.cov { display: flex; height: 4px; border-radius: 2px; overflow: hidden; background: var(--rule); margin-top: .3rem; }
.cov i { display: block; height: 100%; }

/* Mode picker: the choice between Meeting and Class shapes everything else,
   so it gets real estate rather than a dropdown. */
.modepick { display: grid; gap: .5rem; }
@media (min-width: 620px) { .modepick { grid-template-columns: 1fr 1fr; } }
.modeopt { display: block; margin: 0; cursor: pointer; }
.modeopt input { position: absolute; opacity: 0; width: 0; }
.modeopt span {
  display: block; padding: .7rem .85rem; border: 1px solid var(--rule);
  border-radius: var(--radius); transition: border-color 120ms ease, background 120ms ease;
}
.modeopt b { display: block; font-size: .95rem; }
.modeopt em { display: block; font-style: normal; font-size: .78rem; color: var(--slate); margin-top: .15rem; line-height: 1.4; }
.modeopt input:checked + span { border-color: var(--brass); background: var(--brass-dim); }
.modeopt input:focus-visible + span { outline: 2px solid var(--brass); outline-offset: 2px; }

/* Who is talking right now, in a class. */
.speakingnow { font-size: .78rem; color: var(--brass); min-height: 1.3em; }
.caption .who {
  display: block; font-size: .58em; letter-spacing: .04em; text-transform: uppercase;
  color: var(--brass); margin-bottom: .35rem; font-weight: 700;
}
.captionlog li .who { color: var(--brass); font-weight: 700; }

@media (max-width: 640px) {
  /* Roomier for the live line, shorter history: a phone shows one phrase at a
     time and the reader is following that, not scrolling back. */
  .caption { height: 4.8em; }
  .captionlog { height: 26vh; }
}
@media (prefers-reduced-motion: no-preference) {
  .caption { scroll-behavior: smooth; }
}

/* ---------------------------------- about --------------------------------- */
/*
   The verse is set apart and in the display serif: it is the reason the rest of
   the page exists, so it should read as an epigraph rather than a pull quote
   competing with the prose.
*/
.verse {
  margin: 0 0 2rem; padding: 0 0 0 1.1rem;
  border-left: 2px solid var(--brass);
}
.verse p {
  font-family: var(--display); font-size: 1.25rem; line-height: 1.5;
  font-style: italic; margin: 0 0 .5rem; text-wrap: pretty;
}
.verse cite {
  font-style: normal; font-size: .72rem; font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase; color: var(--brass);
}
.lede { font-size: 1.05rem; line-height: 1.65; }
main h2 { font-size: 1rem; font-family: var(--ui); font-weight: 600; margin-top: 1.8rem; }

/* --------------------------- display language ----------------------------- */
/*
   Given its own card above everything else. Someone who reads no English has to
   find this before the rest of the page means anything, so it cannot be a small
   control tucked into a corner.
*/
.langcard { border-left: 3px solid var(--brass); }
.langcard-head { display: flex; align-items: flex-start; gap: .75rem; margin-bottom: .9rem; }
.langcard-head h2 { font-size: 1.1rem; font-family: var(--ui); font-weight: 600; margin: 0 0 .15rem; }
.langcard-head p { margin: 0; }

/* Reads as "language" without depending on any one script. */
.globe {
  flex: none; width: 2.4rem; height: 2.4rem; border-radius: 50%;
  background: var(--brass-dim); color: var(--brass);
  display: flex; align-items: center; justify-content: center;
  font-size: .95rem; font-weight: 700; line-height: 1; letter-spacing: -.02em;
}
.globe i { font-style: normal; font-size: .8em; margin-left: .05em; opacity: .75; }
.globe-sm { width: 1.7rem; height: 1.7rem; font-size: .72rem; }

.biglang {
  font-size: 1.05rem; padding: .75rem .8rem; font-weight: 500;
  background-image: none;
}

/* ---------------------------- video call grid ----------------------------- */
/*
   Tiles stay square-ish and equal. Nobody is made bigger for talking: in a
   gospel discussion the person listening matters as much as the person
   speaking, and an auto-switching "active speaker" view is jumpy when captions
   are what people are actually reading.
*/
.grid-video {
  display: grid; gap: .5rem; margin-bottom: 1rem;
  grid-template-columns: repeat(var(--cols, 2), minmax(0, 1fr));
}
.grid-video.empty { display: none; }

.tile {
  position: relative; aspect-ratio: 4 / 3; overflow: hidden;
  background: #0a1220; border: 1px solid rgba(255,255,255,.08);
  border-radius: var(--radius);
}
.tile video { width: 100%; height: 100%; object-fit: cover; display: block; }
.tile.novideo video { display: none; }

.tile .initials {
  position: absolute; inset: 0; display: none;
  align-items: center; justify-content: center;
  font-size: clamp(1.2rem, 6vw, 2.2rem); font-weight: 600;
  color: var(--brass-lit); letter-spacing: .05em;
}
.tile.novideo .initials { display: flex; }

.tile .label {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: .3rem .5rem; font-size: .74rem; color: #fff;
  background: linear-gradient(transparent, rgba(6,12,22,.85));
  display: flex; align-items: center; gap: .35rem;
}
.tile.speaking { border-color: var(--brass); box-shadow: inset 0 0 0 1px var(--brass); }
.tile.speaking .label::before {
  content: ''; width: 6px; height: 6px; border-radius: 50%;
  background: var(--brass-lit); flex: none;
}
.tile.muted .label::after { content: 'muted'; opacity: .6; margin-left: auto; }

.callbar { display: flex; gap: .5rem; flex-wrap: wrap; align-items: center; margin-bottom: .8rem; }
@media (prefers-reduced-motion: reduce) { .tile { transition: none; } }

/* --------------------------------- tables --------------------------------- */

table { width: 100%; border-collapse: collapse; font-size: 0.86rem; }
th, td { text-align: left; padding: 0.55rem 0.7rem; border-bottom: 1px solid var(--rule); vertical-align: top; }
th { font-size: 0.7rem; letter-spacing: 0.09em; text-transform: uppercase; color: var(--slate); font-weight: 700; }
.tablewrap { overflow-x: auto; }
/* The terminology table fits its card instead of nudging past it: long
   approved wordings wrap in place rather than widening the whole table. */
#termTable { width: 100%; }
#termTable td { overflow-wrap: anywhere; }

/* ---------------------------------- misc ---------------------------------- */

.tabs { display: flex; gap: 0.2rem; border-bottom: 1px solid var(--rule); margin-bottom: 1.4rem; }
.tabs button {
  background: none; border: none; border-bottom: 2px solid transparent;
  color: var(--slate); padding: 0.6rem 0.9rem; border-radius: 0;
}
.tabs button:hover { background: none; color: var(--ink); }
.tabs button[aria-selected="true"] { color: var(--ink); border-bottom-color: var(--brass); }

.codechip {
  font-family: var(--ui); font-weight: 700; letter-spacing: 0.06em;
  background: var(--brass-dim); padding: 0.2rem 0.5rem; border-radius: 2px;
}
.linkbox { display: flex; gap: 0.5rem; }
.linkbox input { font-size: 0.82rem; }

.dot { width: 7px; height: 7px; border-radius: 50%; background: var(--slate-dim); display: inline-block; }
.dot.live { background: var(--brass); box-shadow: 0 0 0 3px var(--brass-dim); }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.footnote { font-size: 0.76rem; color: var(--slate); max-width: 62ch; }
.stage .footnote { color: rgba(232,234,238,0.45); }

/* Display language lives in the banner, because it belongs to the person
   reading the screen rather than to any one room. */
/*
 * A fixed width, not a maximum.
 *
 * Two things were wrong. The rule was written as `.topbar-lang`, which loses
 * to `.topbar > select` on specificity, so the max-width never applied at all.
 * And a maximum would not have been enough anyway: a <select> is as wide as
 * its selected option, so the banner still changed width with every language
 * and shuffled the buttons onto different rows. One width, always.
 */
.topbar-actions > .topbar-lang {
  flex: 0 0 auto;
  width: 9.5rem; max-width: 9.5rem; min-width: 9.5rem;
  font-size: 0.76rem; padding: 0.28rem 0.5rem; border-color: var(--rule);
  text-overflow: ellipsis;
}
@media (max-width: 560px) {
  .topbar-actions > .topbar-lang { width: 7.5rem; max-width: 7.5rem; min-width: 7.5rem; }
}

/* ---------------------------------------------------------------------------
   Explanation bubbles.

   For controls whose name does not carry their consequence. "Pause meeting"
   and "End meeting" sit next to each other and read as near-synonyms, while
   one of them keeps the link people have saved and the other does not — that
   difference cannot live in a two-word label.

   Shown on hover and on keyboard focus, and on tap: phones have no hover, and
   this is exactly the person who most needs to know what the button does
   before pressing it, so the wrapper is focusable and .tip-open is toggled
   by script.
--------------------------------------------------------------------------- */
.tipwrap { position: relative; display: inline-flex; }

.tip {
  /* --tip-shift is set by enableTips() when the bubble would run off the
     screen. Centred under its button by default; on a narrow phone a button
     near the edge would put half the bubble past the viewport and give the
     whole page a horizontal scrollbar. */
  position: absolute; top: calc(100% + 0.5rem); left: 50%;
  translate: calc(-50% + var(--tip-shift, 0px)) 0;
  z-index: 30; width: max-content; max-width: min(22rem, 78vw);
  padding: 0.6rem 0.75rem;
  background: var(--ink-deep); color: #eef0f3;
  border-radius: 0.5rem; font-size: 0.78rem; line-height: 1.45;
  text-align: left; font-family: var(--ui);
  box-shadow: 0 6px 22px rgba(12, 20, 36, 0.28);
  opacity: 0; visibility: hidden; transform: translateY(-0.15rem);
  /* visibility is not a fading property, it flips. Given a duration it stayed
     hidden for the whole transition, which meant the bubble never appeared at
     all — it faded in invisibly. So: flip it instantly when opening, and delay
     the flip until the fade has finished when closing. */
  transition: opacity 0.14s ease, transform 0.14s ease, visibility 0s linear 0.14s;
}

/* The little arrow, so it reads as belonging to the button above it. It has to
   move back by whatever the bubble was shifted, or it stops pointing at the
   button it explains. */
.tip::before {
  content: ''; position: absolute; bottom: 100%;
  left: calc(50% - var(--tip-shift, 0px)); translate: -50% 0;
  border: 0.4rem solid transparent; border-bottom-color: var(--ink-deep);
}

.tipwrap:hover .tip,
.tipwrap:focus-within .tip,
.tipwrap.tip-open .tip {
  opacity: 1; visibility: visible; transform: none;
  transition: opacity 0.14s ease, transform 0.14s ease, visibility 0s;
}

/* There was a .tip-right variant here that pinned the bubble to the right of
   its button. It set `translate: none`, which silently cancelled the measured
   --tip-shift, so the one bubble that most needed clamping — the End button,
   hard against the right edge — was the one that still hung off the screen.
   Two mechanisms for one job, and the static one won. The measurement handles
   every position, so it is now the only mechanism. */

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

/* ---------------------------------------------------------------------------
   The front page, on a phone versus a wide screen.

   Document order is: pitch, then the sign-in and meeting-link cards, then the
   list of languages. That is the order a phone should read in — the list runs
   to sixty-odd rows and used to bury both of the things a visitor came to do.

   On a wide screen there is room for two columns, so the list goes back under
   the pitch on the left and the cards take the right, which is how it looked
   before and still reads best when nothing has to be scrolled past.
--------------------------------------------------------------------------- */
@media (min-width: 860px) {
  .grid.two > .home-cards { grid-column: 2; grid-row: 1 / span 2; }
  .grid.two > .home-langs { grid-column: 1; grid-row: 2; }
}

/* ---------------------------------------------------------------------------
   Editing a name where it is written.

   The alternative was a browser prompt(): a grey dialog carrying the site's
   hostname, thrown over the page, in the browser's language rather than the
   reader's, and unstyleable. Editing in place keeps the reader where they were
   and shows the name in the same type they were just reading.
--------------------------------------------------------------------------- */
.renamable { cursor: text; border-bottom: 1px dashed transparent; }
.renamable:hover, .renamable:focus-visible { border-bottom-color: var(--brass); }

.renameinput {
  width: 100%; max-width: 16rem;
  font: inherit; font-weight: 700;
  padding: 0.15rem 0.3rem;
}

/* The room title on the host console is display type at heading size; the
   field it turns into has to match, or renaming looks like a different page. */
.titleinput {
  font-family: var(--display);
  font-size: clamp(1.7rem, 1.3rem + 1.6vw, 2.4rem);
  line-height: 1.12; font-weight: 400; letter-spacing: -0.01em;
  padding: 0 0.2rem; width: 100%; max-width: 22ch;
}
