/* ===========================================================================
 * TalkLive UI — the redesigned panels and dialogs
 * ---------------------------------------------------------------------------
 * Loaded last on / , /call and /chat. It covers four surfaces and nothing
 * else: the side-panel shell, Settings, the Friends/History panels, and the
 * log-in / sign-up dialog. The top bar and the landing screen are deliberately
 * untouched and keep the styling they ship with.
 *
 * Everything here uses its own `tl-` class names, so nothing in the older
 * sheets can reach it and it never has to out-shout them with !important. The
 * handful of rules that do talk to old markup are in LEGACY BRIDGES at the
 * bottom, kept deliberately short.
 *
 * Three rules the whole file follows:
 *   1. Nothing tappable is smaller than 44px, ever.
 *   2. One spacing scale (4 / 8 / 12 / 16 / 24 / 32) - no stray magic numbers.
 *   3. One primary action per screen, and it is the biggest thing on it.
 * ======================================================================== */

:root {
  --tl-s1: 4px;
  --tl-s2: 8px;
  --tl-s3: 12px;
  --tl-s4: 16px;
  --tl-s5: 24px;
  --tl-s6: 32px;

  --tl-r-sm: 10px;
  --tl-r-md: 14px;
  --tl-r-lg: 20px;
  --tl-r-pill: 999px;

  --tl-tap: 48px;
  /* The phone tab bar's height. Everything that has to sit clear of it - the
     page's bottom padding, the adhesive ad - measures from this, so they
     cannot drift apart when the bar changes. */
  --tl-navbar: 67px;
  /* What the call screen has to keep clear at the bottom: the tab bar on a
     phone, nothing at all once the nav becomes a sidebar. Set here so the
     first-screen height in section 14 cannot drift from the bar itself. */
  --tl-callbar: var(--tl-navbar);

  --tl-card: color-mix(in srgb, var(--bg-2) 92%, transparent);
  --tl-line: var(--line);
  --tl-line-strong: color-mix(in srgb, var(--text) 18%, transparent);
  --tl-shadow: 0 12px 34px rgba(0, 0, 0, 0.35);
}

/* ===========================================================================
 * 1. THE PANEL SHELL - one header and one scrolling body for Settings,
 *    Friends and History alike.
 * ======================================================================== */

.tl-panel-head {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: var(--tl-s3);
  padding: var(--tl-s3) var(--tl-s4);
  min-height: 60px;
  background: var(--bg-2);
  border-bottom: 1px solid var(--tl-line);
}
.tl-panel-head h2 {
  margin: 0;
  font-size: 18px;
  font-weight: 800;
  letter-spacing: -0.3px;
}
.tl-panel-close {
  margin-inline-start: auto;
  width: 44px;
  height: 44px;
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: var(--tl-r-md);
  background: transparent;
  color: var(--muted);
  font-size: 26px;
  line-height: 1;
  cursor: pointer;
}
.tl-panel-close:hover { background: color-mix(in srgb, var(--text) 8%, transparent); color: var(--text); }

.tl-panel-body {
  padding: var(--tl-s4);
  display: flex;
  flex-direction: column;
  gap: var(--tl-s4);
}

.tl-hint {
  margin: 0;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--muted);
}

.tl-empty {
  margin: 0;
  padding: var(--tl-s5) var(--tl-s4);
  text-align: center;
  font-size: 14px;
  color: var(--muted);
}

/* ===========================================================================
 * 2. SETTINGS
 *    Every preference it had, none of the four collapsed accordions. Labelled
 *    groups you can read top to bottom, so "which drawer is Theme in" stops
 *    being a question.
 * ======================================================================== */

.tl-set-group {
  border: 1px solid var(--tl-line);
  border-radius: var(--tl-r-lg);
  background: var(--tl-card);
  overflow: hidden;
}

.tl-set-legend {
  margin: 0 0 var(--tl-s2);
  padding: 0 var(--tl-s1);
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.7px;
  text-transform: uppercase;
  color: var(--muted);
}

.tl-set-row {
  width: 100%;
  min-height: 56px;
  display: flex;
  align-items: center;
  gap: var(--tl-s3);
  padding: var(--tl-s3) var(--tl-s4);
  border: 0;
  border-bottom: 1px solid var(--tl-line);
  background: transparent;
  color: var(--text);
  font-size: 15px;
  text-align: start;
  text-decoration: none;
  cursor: pointer;
}
.tl-set-row:last-child { border-bottom: 0; }
.tl-set-row:hover { background: color-mix(in srgb, var(--text) 5%, transparent); }

.tl-set-row-icon {
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  border-radius: var(--tl-r-sm);
  display: grid;
  place-items: center;
  background: color-mix(in srgb, var(--text) 8%, transparent);
  color: var(--muted);
  overflow: hidden;
}
.tl-set-row-icon svg { width: 19px; height: 19px; }

.tl-set-row-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1 1 auto;
}
.tl-set-row-text strong { font-size: 15px; font-weight: 700; }
.tl-set-row-text small { font-size: 12.5px; color: var(--muted); }

.tl-set-row-go { flex: 0 0 auto; color: var(--muted); display: grid; place-items: center; }
.tl-set-row-go svg { width: 18px; height: 18px; }

.tl-set-row-badge {
  flex: 0 0 auto;
  padding: 3px 9px;
  border-radius: var(--tl-r-pill);
  background: color-mix(in srgb, var(--accent) 22%, transparent);
  color: var(--text);
  font-size: 11px;
  font-weight: 700;
}

/* A control row: label on the left, control on the right. */
.tl-set-control {
  min-height: 56px;
  display: flex;
  align-items: center;
  gap: var(--tl-s3);
  padding: var(--tl-s3) var(--tl-s4);
  border-bottom: 1px solid var(--tl-line);
}
.tl-set-control:last-child { border-bottom: 0; }
.tl-set-control > span:first-child { flex: 1 1 auto; font-size: 15px; }
.tl-set-control-stack {
  display: block;
  padding: var(--tl-s3) var(--tl-s4);
}
.tl-set-control-stack > .tl-set-label { display: block; margin-bottom: var(--tl-s2); }

.tl-set-label { font-size: 13px; font-weight: 700; color: var(--muted); }

.tl-set-field { display: flex; gap: var(--tl-s2); }
.tl-set-field input {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 46px;
  padding: 0 var(--tl-s3);
  border-radius: var(--tl-r-md);
  border: 1px solid var(--tl-line-strong);
  background: color-mix(in srgb, var(--bg-1) 70%, transparent);
  color: var(--text);
  font-size: 15px;
}
.tl-set-field button {
  flex: 0 0 auto;
  min-height: 46px;
  padding: 0 var(--tl-s4);
  border-radius: var(--tl-r-md);
  border: 1px solid var(--tl-line-strong);
  background: transparent;
  color: var(--text);
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
}
.tl-set-field button:disabled { opacity: 0.45; cursor: default; }

/* Segmented options (gender, theme). Still `.pill` underneath, so the existing
   initPillGroup / setPillGroupValue wiring keeps working untouched. */
.tl-seg {
  display: flex;
  flex-wrap: wrap;
  gap: var(--tl-s2);
}
.tl-seg .tl-seg-opt {
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  gap: var(--tl-s2);
  padding: 0 var(--tl-s3);
  border-radius: var(--tl-r-pill);
  border: 1px solid var(--tl-line-strong);
  background: transparent;
  color: var(--muted);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}
.tl-seg .tl-seg-opt.selected {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  color: var(--text);
}
.tl-seg .tl-seg-opt svg { width: 17px; height: 17px; }

.tl-set-links { display: grid; grid-template-columns: 1fr; }
.tl-set-links > * {
  min-height: 50px;
  display: flex;
  align-items: center;
  padding: 0 var(--tl-s4);
  border: 0;
  border-bottom: 1px solid var(--tl-line);
  background: transparent;
  color: var(--text);
  font-size: 14.5px;
  text-align: start;
  text-decoration: none;
  cursor: pointer;
}
.tl-set-links > *:last-child { border-bottom: 0; }
.tl-set-links > *:hover { background: color-mix(in srgb, var(--text) 5%, transparent); }

.tl-set-foot { display: flex; gap: var(--tl-s2); }
.tl-set-foot > * { flex: 1 1 0; }

/* ===========================================================================
 * 3. FRIENDS - one panel, two tabs
 * ======================================================================== */

.tl-tabs {
  display: flex;
  gap: var(--tl-s1);
  padding: var(--tl-s1);
  border-radius: var(--tl-r-pill);
  background: color-mix(in srgb, var(--text) 7%, transparent);
}
.tl-tab {
  flex: 1 1 0;
  min-height: 42px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  border: 0;
  border-radius: var(--tl-r-pill);
  background: transparent;
  color: var(--muted);
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
}
.tl-tab.selected {
  background: var(--bg-2);
  color: var(--text);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.22);
}
.tl-tab-count {
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: var(--tl-r-pill);
  background: color-mix(in srgb, var(--text) 12%, transparent);
  font-size: 11px;
  font-weight: 800;
  line-height: 20px;
}
/* Red is for "something needs you". The Friends tab's number is how many
   friends you have - it is never something to act on, so painting it the
   same red as an unread badge made every added friend look like an alert
   that would not clear. Only Requests, which really is a queue of things
   waiting on you, gets the red. */
.tl-tab.selected .tl-tab-count {
  background: color-mix(in srgb, var(--text) 20%, transparent);
  color: var(--text);
}
.tl-tab[data-tab="requests"] .tl-tab-count {
  background: var(--danger);
  color: #fff;
}
.tl-tab-count.hidden { display: none; }

.tl-tabpanel.hidden { display: none; }

.tl-subhead {
  margin: var(--tl-s4) 0 var(--tl-s2);
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.7px;
  text-transform: uppercase;
  color: var(--muted);
}
.tl-subhead:first-child { margin-top: 0; }

/* Requests you sent - the half of the story that used to be invisible: you
   asked someone to be your friend and nothing on screen ever said so again. */
.tl-sent-item {
  display: flex;
  align-items: center;
  gap: var(--tl-s3);
  min-height: 56px;
  padding: var(--tl-s2) var(--tl-s3);
  border-radius: var(--tl-r-md);
  border: 1px solid var(--tl-line);
  background: var(--tl-card);
  margin-bottom: var(--tl-s2);
  cursor: pointer;
}
.tl-sent-avatar {
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: color-mix(in srgb, var(--text) 8%, transparent);
  font-weight: 700;
  overflow: hidden;
}
.tl-sent-avatar svg, .tl-sent-avatar img { width: 30px; height: 30px; }
.tl-sent-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
.tl-sent-name { font-size: 14.5px; font-weight: 700; }
.tl-sent-sub { font-size: 12px; color: var(--muted); }
.tl-sent-chip {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 6px 11px;
  border-radius: var(--tl-r-pill);
  background: color-mix(in srgb, var(--text) 9%, transparent);
  color: var(--muted);
  font-size: 12px;
  font-weight: 700;
}
.tl-sent-chip svg { width: 13px; height: 13px; }

/* ===========================================================================
 * 4. LOG IN / SIGN UP
 *    Two tabs, two fields, one button. Recovery e-mail, Google and the reset
 *    flow all sit after the primary action rather than in front of it.
 * ======================================================================== */

/* The dialog opens inside a centred stage, so the form has to say it is a
   form: labels and helper text read left, only the tabs and button centre. */
.tl-auth-card {
  padding: 0;
  text-align: start;
}

.tl-auth-tabs {
  display: flex;
  gap: var(--tl-s1);
  padding: var(--tl-s1);
  margin-bottom: var(--tl-s5);
  border-radius: var(--tl-r-pill);
  background: color-mix(in srgb, var(--text) 7%, transparent);
}
.tl-auth-tab {
  flex: 1 1 0;
  min-height: 46px;
  border: 0;
  border-radius: var(--tl-r-pill);
  background: transparent;
  color: var(--muted);
  font-size: 15px;
  font-weight: 700;
  cursor: pointer;
}
.tl-auth-tab.selected {
  background: var(--bg-2);
  color: var(--text);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.22);
}

.tl-field { margin-bottom: var(--tl-s4); }
.tl-field > label {
  display: block;
  margin-bottom: 6px;
  font-size: 13px;
  font-weight: 700;
  color: var(--muted);
}
.tl-field > input {
  width: 100%;
  min-height: 52px;
  padding: 0 var(--tl-s4);
  border-radius: var(--tl-r-md);
  border: 1px solid var(--tl-line-strong);
  background: color-mix(in srgb, var(--bg-1) 70%, transparent);
  color: var(--text);
  font-size: 16px; /* 16px stops iOS zooming the page on focus */
}
.tl-field > input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
}
.tl-field-note {
  margin: 6px 0 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--muted);
}

.tl-submit {
  width: 100%;
  min-height: 54px;
  border: 0;
  border-radius: var(--tl-r-md);
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  color: #fff;
  font-size: 16px;
  font-weight: 800;
  cursor: pointer;
  transition: filter 0.15s ease, transform 0.12s ease;
}
.tl-submit:hover { filter: brightness(1.07); }
.tl-submit:active { transform: scale(0.985); }
.tl-submit:disabled { opacity: 0.5; cursor: default; filter: none; }

.tl-linkline {
  display: block;
  width: 100%;
  min-height: 44px;
  margin-top: var(--tl-s2);
  border: 0;
  background: none;
  color: var(--accent-2);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}

.tl-or {
  display: flex;
  align-items: center;
  gap: var(--tl-s3);
  margin: var(--tl-s4) 0;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--muted);
}
.tl-or::before,
.tl-or::after {
  content: "";
  flex: 1 1 0;
  height: 1px;
  background: var(--tl-line);
}
.tl-or.hidden { display: none; }

/* ===========================================================================
 * 5. LEGACY BRIDGES
 *    Short rules so the redesigned shells sit correctly inside the older
 *    containers that still do the open/close work.
 * ======================================================================== */

/* The redesigned panels carry their own sticky header and padded body, so the
   shell's padding and gap would double up on both. */
.side-panel.tl-panel {
  padding: 0;
  gap: 0;
}
.tl-panel > .side-panel-body:not(.tl-panel-body),
.tl-panel > .side-body:not(.tl-panel-body) {
  padding: 0;
  gap: 0;
}
/* The scrolling region is the body, so the header can stick to its top. */
.tl-panel > .side-panel-body,
.tl-panel > .side-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}
.tl-panel > .side-body.tl-panel-body {
  padding-bottom: calc(var(--tl-s4) + env(safe-area-inset-bottom, 0px));
}

/* A closed panel is parked off-screen with transform, so its drop shadow still
   paints - a dark vertical band down the edge of every page, on every theme.
   Only an open panel casts one. */
.side-panel:not(.open) { box-shadow: none; }

/* Lists inside a panel are plain blocks; the shell used to make them flex
   columns with their own gap, which fought with each row's own spacing. */
.tl-panel .history-list,
.tl-panel .friends-list,
.tl-panel .notif-list,
.tl-panel .requests-list { display: block; }

/* Keep the redesigned account dialog readable on a phone. */
.modal-box.tl-auth-box {
  width: min(430px, 94vw);
  border-radius: var(--tl-r-lg);
}
.modal-box.tl-auth-box .modal-body { padding: var(--tl-s5); }

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

/* Light theme: the translucent fills above are mixed from --text, so they
   invert on their own. Only the shadows need toning down. */
[data-theme="light"] .tl-tab.selected,
[data-theme="light"] .tl-auth-tab.selected { box-shadow: 0 2px 8px rgba(20, 30, 70, 0.12); }

/* ===========================================================================
 * THE "UNDER DEVELOPMENT" NOTICE
 * ---------------------------------------------------------------------------
 * A single card, shown once per browser on the landing screen: what the state
 * of the product is, and a box to say what would make it better. It is a
 * dialog rather than a strip along the top because a strip is read as an ad
 * and scrolled past - and because the feedback box needs room to be typed in.
 *
 * Its own overlay, not .modal-overlay, so nothing in the older sheets can
 * reach it and the z-index stays below the call surfaces (the notice never
 * outranks a live call; app.js also refuses to open it once one starts).
 * ======================================================================== */

.tl-devnotice-overlay {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--tl-s4);
  padding-bottom: calc(var(--tl-s4) + env(safe-area-inset-bottom, 0px));
  background: rgba(5, 7, 15, 0.66);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  overflow-y: auto;
  overscroll-behavior: contain;
  animation: tl-devnotice-fade 180ms ease-out both;
}
.tl-devnotice-overlay.hidden { display: none; }

.tl-devnotice {
  position: relative;
  width: min(460px, 100%);
  margin: auto;
  box-sizing: border-box;
  padding: var(--tl-s5);
  border: 1px solid var(--tl-line-strong);
  border-radius: var(--tl-r-lg);
  background: var(--bg-2);
  box-shadow: var(--tl-shadow);
  animation: tl-devnotice-rise 200ms ease-out both;
}

.tl-devnotice-close {
  position: absolute;
  top: var(--tl-s2);
  inset-inline-end: var(--tl-s2);
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: var(--tl-r-md);
  background: transparent;
  color: var(--muted);
  font-size: 26px;
  line-height: 1;
  cursor: pointer;
}
.tl-devnotice-close:hover { background: color-mix(in srgb, var(--text) 8%, transparent); color: var(--text); }

.tl-devnotice-head {
  display: flex;
  align-items: flex-start;
  gap: var(--tl-s3);
  /* Room for the close button, so a long title never runs under it. */
  padding-inline-end: var(--tl-s6);
}
.tl-devnotice-icon {
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  border-radius: var(--tl-r-md);
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--accent-2);
}
.tl-devnotice-icon svg { width: 22px; height: 22px; }
.tl-devnotice-headtext { min-width: 0; }
.tl-devnotice-eyebrow {
  margin: 0 0 2px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
.tl-devnotice-headtext h2 {
  margin: 0;
  font-size: 19px;
  font-weight: 800;
  letter-spacing: -0.3px;
  line-height: 1.25;
}

.tl-devnotice-body {
  margin: var(--tl-s3) 0 0;
  font-size: 14px;
  line-height: 1.55;
  color: var(--muted);
}

.tl-devnotice-form {
  margin-top: var(--tl-s4);
  padding-top: var(--tl-s4);
  border-top: 1px solid var(--tl-line);
}
.tl-devnotice-label {
  display: block;
  margin-bottom: var(--tl-s2);
  font-size: 13px;
  font-weight: 700;
  color: var(--text);
}
.tl-devnotice-input {
  display: block;
  width: 100%;
  box-sizing: border-box;
  padding: var(--tl-s3);
  border: 1px solid var(--tl-line-strong);
  border-radius: var(--tl-r-md);
  background: color-mix(in srgb, var(--text) 5%, transparent);
  color: var(--text);
  /* 16px so iOS Safari does not zoom the page when the box is focused. */
  font: inherit;
  font-size: 16px;
  line-height: 1.5;
  resize: vertical;
  min-height: 84px;
}
.tl-devnotice-input::placeholder { color: var(--muted); }
.tl-devnotice-input:focus-visible {
  outline: 2px solid var(--accent-2);
  outline-offset: 1px;
  border-color: transparent;
}

.tl-devnotice-status {
  margin: var(--tl-s2) 0 0;
  min-height: 18px;
  font-size: 13px;
  color: var(--muted);
}
.tl-devnotice-status.is-error { color: var(--danger); }
.tl-devnotice-status.is-done { color: var(--green); }

.tl-devnotice-actions {
  display: flex;
  gap: var(--tl-s2);
  margin-top: var(--tl-s3);
}
.tl-devnotice-btn {
  flex: 1 1 0;
  min-height: var(--tl-tap);
  padding: 0 var(--tl-s4);
  border: 1px solid transparent;
  border-radius: var(--tl-r-md);
  font: inherit;
  font-size: 15px;
  font-weight: 700;
  cursor: pointer;
}
.tl-devnotice-btn-ghost {
  border-color: var(--tl-line-strong);
  background: transparent;
  color: var(--muted);
}
.tl-devnotice-btn-ghost:hover { background: color-mix(in srgb, var(--text) 7%, transparent); color: var(--text); }
.tl-devnotice-btn-primary {
  background: var(--accent);
  color: #fff;
}
.tl-devnotice-btn-primary:hover { filter: brightness(1.08); }
.tl-devnotice-btn-primary:disabled { opacity: 0.6; cursor: default; filter: none; }

@keyframes tl-devnotice-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes tl-devnotice-rise {
  from { opacity: 0; transform: translateY(10px) scale(0.985); }
  to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .tl-devnotice-overlay,
  .tl-devnotice { animation: none; }
}

@media (max-width: 420px) {
  .tl-devnotice { padding: var(--tl-s4); }
  .tl-devnotice-headtext h2 { font-size: 17px; }
  .tl-devnotice-actions { flex-direction: column-reverse; }
}

/* The card takes focus when it opens (see openDevNotice), so a screen reader
   lands inside the dialog - but it is a container, not a control, and should
   not be ringed like one. */
.tl-devnotice:focus { outline: none; }

/* ===========================================================================
 * THE APP FRAME - nav rail, stage, activity rail
 * ---------------------------------------------------------------------------
 * The home screen used to be one centred 560px column with every tool hidden
 * behind glyphs in the header. On anything wider than a tablet that column sat
 * in the middle of a very large empty page, and "where are my friends / my
 * calls / my filters" had no answer you could see - only one you could guess.
 *
 * So the page is three columns now, and it degrades by dropping columns rather
 * than by reflowing:
 *
 *   >= 1180px   nav rail | stage | activity rail
 *   >= 860px    nav rail | stage
 *   <  860px    stage, with the nav as a bottom tab bar
 *
 * The nav is the same markup at every size. The buttons in it are the real
 * ones - moved out of the header rail, not copied - so every handler, badge
 * and aria label in app.js still finds exactly what it found before.
 * ======================================================================== */

.tl-frame {
  flex: 1;
  width: 100%;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
  /* The stage is the only column that scrolls the page; the rails are sticky
     inside their tracks, so a long SEO section below never drags them out of
     view on the way past. */
  min-height: 0;
}

.tl-nav,
.tl-rail { display: none; }

/* --- The stage keeps its own centring, inside whatever track it is given. -- */
.tl-frame > .stage {
  width: 100%;
  min-width: 0;
}

/* ===========================================================================
 * 1. THE NAV - a labelled column on a desktop, a tab bar on a phone.
 * ======================================================================== */

.tl-nav-items {
  display: flex;
  flex-direction: column;
  gap: var(--tl-s1);
}

.tl-nav-item {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--tl-s3);
  width: 100%;
  min-height: var(--tl-tap);
  padding: 0 var(--tl-s3);
  border: 0;
  border-radius: var(--tl-r-md);
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 14.5px;
  font-weight: 600;
  text-align: start;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}
.tl-nav-item:hover { background: color-mix(in srgb, var(--text) 7%, transparent); color: var(--text); }
.tl-nav-item.is-current {
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  color: var(--text);
}
/* The one thing a rail has that a row of glyphs never did: a marker saying
   which of these you are looking at. */
.tl-nav-item.is-current::before {
  content: "";
  position: absolute;
  inset-inline-start: -10px;
  top: 50%;
  width: 3px;
  height: 22px;
  transform: translateY(-50%);
  border-radius: 999px;
  background: var(--accent);
}
.tl-nav-icon {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
}
.tl-nav-icon svg { width: 21px; height: 21px; }
.tl-nav-label {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.tl-nav-label-short { display: none; }

.tl-nav-soon {
  flex: 0 0 auto;
  padding: 3px 8px;
  border-radius: var(--tl-r-pill);
  background: color-mix(in srgb, var(--accent-2) 16%, transparent);
  color: var(--accent-2);
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* Unread messages. The badge markup is the one that used to sit on the header
   glyph, so it keeps its ID and app.js keeps writing to it. */
.tl-nav-item .notif-badge {
  position: static;
  flex: 0 0 auto;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  display: grid;
  place-items: center;
  border-radius: var(--tl-r-pill);
  background: var(--danger);
  color: #fff;
  font-size: 11px;
  font-weight: 800;
}
.tl-nav-item .notif-badge.hidden { display: none; }

.tl-nav-promo {
  display: block;
  margin-top: var(--tl-s4);
  padding: var(--tl-s4);
  border: 1px solid var(--tl-line);
  border-radius: var(--tl-r-lg);
  background: color-mix(in srgb, var(--accent) 12%, var(--bg-2));
  color: var(--text);
  text-decoration: none;
}
.tl-nav-promo-icon {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  margin-bottom: var(--tl-s2);
  border-radius: var(--tl-r-md);
  background: color-mix(in srgb, var(--accent) 26%, transparent);
  color: var(--accent-2);
}
.tl-nav-promo-icon svg { width: 20px; height: 20px; }
.tl-nav-promo strong { display: block; font-size: 14px; font-weight: 800; }
.tl-nav-promo small {
  display: block;
  margin-top: 2px;
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--muted);
}
.tl-nav-promo-cta {
  display: block;
  margin-top: var(--tl-s3);
  padding: 9px var(--tl-s3);
  border-radius: var(--tl-r-md);
  background: var(--accent);
  color: #fff;
  font-size: 13px;
  font-weight: 700;
  text-align: center;
}
.tl-nav-promo:hover .tl-nav-promo-cta { filter: brightness(1.08); }

/* ===========================================================================
 * 2. THE ACTIVITY RAIL
 * ======================================================================== */

.tl-rail-card {
  padding: var(--tl-s4);
  border: 1px solid var(--tl-line);
  border-radius: var(--tl-r-lg);
  background: var(--tl-card);
}
.tl-rail-card + .tl-rail-card { margin-top: var(--tl-s3); }

.tl-rail-head {
  display: flex;
  align-items: center;
  gap: var(--tl-s2);
  margin-bottom: var(--tl-s3);
}
.tl-rail-head h2 {
  margin: 0;
  font-size: 14px;
  font-weight: 800;
  letter-spacing: -0.2px;
}
.tl-rail-more {
  margin-inline-start: auto;
  padding: 4px 8px;
  border: 0;
  border-radius: var(--tl-r-sm);
  background: transparent;
  color: var(--accent-2);
  font: inherit;
  font-size: 12.5px;
  font-weight: 700;
  cursor: pointer;
}
.tl-rail-more:hover { background: color-mix(in srgb, var(--text) 8%, transparent); }

/* --- Live now: the headcount, said plainly and once. ---------------------- */
.tl-rail-live { text-align: center; }
.tl-rail-live-label {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  margin: 0;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
.tl-rail-live-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--green);
  animation: tl-rail-pulse 2.4s ease-in-out infinite;
}
.tl-rail-live-num {
  margin: var(--tl-s2) 0 0;
  font-size: 34px;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -1px;
}
.tl-rail-live-sub {
  margin: var(--tl-s1) 0 0;
  font-size: 12.5px;
  color: var(--muted);
}
.tl-rail-live-cta {
  width: 100%;
  min-height: 42px;
  margin-top: var(--tl-s3);
  border: 0;
  border-radius: var(--tl-r-md);
  background: var(--green);
  color: #06210f;
  font: inherit;
  font-size: 14px;
  font-weight: 800;
  cursor: pointer;
}
.tl-rail-live-cta:hover { filter: brightness(1.06); }
@keyframes tl-rail-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }

/* --- The two lists. One row shape, whatever is in it. --------------------- */
.tl-rail-list { display: block; }
.tl-rail-row {
  display: flex;
  align-items: center;
  gap: var(--tl-s3);
  width: 100%;
  min-height: 44px;
  padding: var(--tl-s2);
  border: 0;
  border-radius: var(--tl-r-md);
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: start;
  cursor: pointer;
}
.tl-rail-row:hover { background: color-mix(in srgb, var(--text) 6%, transparent); }
.tl-rail-row-icon {
  position: relative;
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--text) 9%, transparent);
  color: var(--muted);
}
.tl-rail-row-icon svg { width: 17px; height: 17px; }
/* Presence, as a dot on the avatar rather than a word beside it. */
.tl-rail-row-icon .tl-rail-presence {
  position: absolute;
  right: -1px;
  bottom: -1px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  border: 2px solid var(--bg-2);
  background: var(--muted);
}
.tl-rail-row-icon .tl-rail-presence.is-online { background: var(--green); }
.tl-rail-row-text { min-width: 0; flex: 1 1 auto; }
.tl-rail-row-text strong {
  display: block;
  font-size: 13.5px;
  font-weight: 700;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.tl-rail-row-text small {
  display: block;
  margin-top: 1px;
  font-size: 12px;
  color: var(--muted);
}
.tl-rail-row-text img { vertical-align: -2px; }
.tl-rail-empty {
  margin: 0;
  padding: var(--tl-s2) var(--tl-s2) var(--tl-s3);
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--muted);
}

.tl-rail-invite h2 { margin: 0; font-size: 14px; font-weight: 800; }
.tl-rail-invite p {
  margin: var(--tl-s2) 0 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--muted);
}
.tl-rail-invite-btn {
  width: 100%;
  min-height: 42px;
  margin-top: var(--tl-s3);
  border: 1px solid var(--tl-line-strong);
  border-radius: var(--tl-r-md);
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 13.5px;
  font-weight: 700;
  cursor: pointer;
}
.tl-rail-invite-btn:hover { background: color-mix(in srgb, var(--text) 8%, transparent); }

/* ===========================================================================
 * 3. BREAKPOINTS
 * ======================================================================== */

/* --- Phone: the nav becomes the bottom bar. ------------------------------
 * Rebuilt for legibility. It was five 21px outlines with 10.5px grey labels
 * and a 3px line over the current one: at arm's length that is a row of grey
 * smudges, and which one you are on is a detail you have to look for. On the
 * screen where a first-time visitor decides whether this app is simple, a
 * navigation that reads as noise is a navigation that reads as complicated.
 *
 * So: bigger icons, bigger labels, and the current item wears a filled pill
 * behind its icon - the one active state that is legible in a glance, in
 * sunlight, on a cheap screen.
 * ---------------------------------------------------------------------- */
@media (max-width: 859px) {
  .tl-nav {
    display: block;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 56;
    padding: 6px 4px calc(4px + env(safe-area-inset-bottom, 0px));
    background: color-mix(in srgb, var(--bg-2) 97%, transparent);
    -webkit-backdrop-filter: blur(16px);
    backdrop-filter: blur(16px);
    border-top: 1px solid var(--tl-line);
  }
  .tl-nav-items {
    flex-direction: row;
    gap: 0;
  }
  .tl-nav-item {
    flex: 1 1 0;
    min-width: 0;
    flex-direction: column;
    justify-content: flex-start;
    gap: 3px;
    padding: 4px 2px 2px;
    min-height: 56px;
    border-radius: var(--tl-r-md);
    background: transparent;
    /* Brighter than --muted: an unselected tab still has to be readable, and
       the palette's muted grey was chosen for body copy, not for 11px
       all-caps-sized labels on a dark bar. */
    color: color-mix(in srgb, var(--text) 62%, transparent);
    font-size: 11.5px;
    font-weight: 600;
    text-align: center;
  }
  .tl-nav-item:hover { background: transparent; }

  /* The icon lives in its own pill, which is what fills in when selected. */
  .tl-nav-icon {
    width: 100%;
    max-width: 58px;
    height: 30px;
    border-radius: var(--tl-r-pill);
    transition: background-color 0.16s ease, color 0.16s ease;
  }
  .tl-nav-icon svg { width: 23px; height: 23px; }

  .tl-nav-item.is-current {
    background: transparent;
    color: var(--text);
    font-weight: 700;
  }
  .tl-nav-item.is-current .tl-nav-icon {
    background: color-mix(in srgb, var(--accent) 34%, transparent);
    color: var(--text);
  }
  /* The hairline over the current tab is gone - the filled pill says it. */
  .tl-nav-item.is-current::before { display: none; }

  .tl-nav-label {
    flex: 0 0 auto;
    width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    line-height: 1.2;
  }
  .tl-nav-label-long { display: none; }
  .tl-nav-label-short { display: block; }

  /* No room for a "Soon" chip or a promo card on a tab bar - and no room for
     a sixth tab either. Shop is the one destination nobody can act on yet, so
     it is the one that goes; it is still in the drawer behind More. */
  .tl-nav-soon,
  .tl-nav-promo,
  .tl-nav-item-wide { display: none; }

  .tl-nav-item .notif-badge {
    position: absolute;
    top: 2px;
    inset-inline-end: 50%;
    margin-inline-end: -22px;
    min-width: 18px;
    height: 18px;
    font-size: 10.5px;
    border: 2px solid var(--bg-2);
  }
  /* Clear the bar, so the last thing on the page is never parked under it. */
  .app { padding-bottom: calc(var(--tl-navbar) + var(--tl-s2) + env(safe-area-inset-bottom, 0px)); }
}

/* --- Desktop: the nav is a column. --------------------------------------- */
@media (min-width: 860px) {
  :root { --tl-callbar: 0px; }
  /* The frame spans the window. It used to be a 1440px block centred in it,
     which on anything wider left the nav floating in the middle of the page
     with a hand's width of empty desktop to its left - a sidebar that is not
     beside anything. The nav is pinned to the left edge and the rail to the
     right, the way every app with a sidebar does it; what gets centred is
     the column of content between them, not the furniture around it. */
  .tl-frame {
    grid-template-columns: 224px minmax(0, 1fr);
    gap: var(--tl-s5);
    width: 100%;
    max-width: none;
    margin: 0;
    padding: var(--tl-s5) var(--tl-s5) 0;
    box-sizing: border-box;
  }
  .tl-nav {
    display: block;
    position: sticky;
    /* Under the sticky header, not behind it. */
    top: calc(64px + var(--tl-s4));
  }
  .tl-frame > .stage {
    justify-content: flex-start;
    padding: 0 0 var(--tl-s5);
  }
}

/* --- Wide: the activity rail joins. -------------------------------------- */
@media (min-width: 1180px) {
  .tl-frame {
    grid-template-columns: 224px minmax(0, 1fr) 300px;
  }
  .tl-rail {
    display: block;
    position: sticky;
    top: calc(64px + var(--tl-s4));
  }
  /* The rail now carries Premium and the invite, so the pair of cards under
     the animal picker would be saying both things twice on the same screen. */
  .setup-growth { display: none; }
}

/* ===========================================================================
 * 4. THE HOME SCREEN - hero, the two action cards, the animal picker
 * ---------------------------------------------------------------------------
 * The two choices used to be glowing circles. A circle holds a label and
 * nothing else, so everything that actually made the choice - "live voice
 * call", "no mic needed" - had to be printed underneath as loose caption text,
 * and the whole decision came to three stacked pieces per option. These are
 * cards: icon, name, explanation and the arrow that says it goes somewhere,
 * one object per decision, and a far bigger target at the same width.
 * ======================================================================== */

.tl-hero-sub {
  margin: var(--tl-s3) 0 0;
  font-size: 15px;
  line-height: 1.55;
  /* Brighter than --muted. This one line is the whole pitch - "real people,
     no sign-up" - and it was sitting at body-copy grey on top of a tinted
     sky, which is the lowest-contrast text on the highest-stakes screen. */
  color: color-mix(in srgb, var(--text) 78%, transparent);
  text-wrap: balance;
}

.tl-action {
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: center;
  gap: var(--tl-s3);
  width: 100%;
  min-height: 84px;
  padding: var(--tl-s4);
  box-sizing: border-box;
  border: 1px solid transparent;
  border-radius: var(--tl-r-lg);
  color: #fff;
  font: inherit;
  text-align: start;
  text-decoration: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
  transition: transform 0.15s ease, filter 0.15s ease;
}
.tl-action:hover { filter: brightness(1.06); }
.tl-action:active { transform: scale(0.99); }

/* Flat fills from the theme's own tokens. The old orbs carried three stacked
   box-shadows each and a breathing animation, which on a mid-range phone was
   two continuously repainting 158px circles above the fold. */
.tl-action-voice { background: var(--green); color: #05230f; }
.tl-action-chat { background: var(--accent); color: #fff; }

.tl-action-icon {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  border-radius: var(--tl-r-md);
  background: rgba(255, 255, 255, 0.18);
}
.tl-action-icon svg { width: 26px; height: 26px; }
.tl-action-body { min-width: 0; flex: 1 1 auto; }
.tl-action-body strong {
  display: block;
  font-size: 18px;
  font-weight: 800;
  letter-spacing: -0.3px;
}
.tl-action-body small {
  display: block;
  margin-top: 2px;
  font-size: 13px;
  font-weight: 500;
  opacity: 0.82;
}
.tl-action-go {
  flex: 0 0 auto;
  margin-inline-start: auto;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  opacity: 0.8;
}
.tl-action-go svg { width: 20px; height: 20px; }

/* The connecting state app.js sets on the voice card while it looks for a
   match. The card stops taking taps and says why, in place. */
.tl-action.is-connecting { cursor: default; filter: saturate(0.7); }
.tl-action.is-connecting .tl-action-go svg { display: none; }
.tl-action.is-connecting .tl-action-go::after {
  content: "";
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-top-color: transparent;
  animation: tl-action-spin 0.7s linear infinite;
}
@keyframes tl-action-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .tl-action.is-connecting .tl-action-go::after { animation-duration: 2s; }
}

/* The ripple app.js spawns on tap (see spawnRipple) is a child span, and the
   card clips it to its own rounded corners. */
.tl-action .tap-ripple { background: rgba(255, 255, 255, 0.35); }

/* --- The row that holds them. On a phone they stack, with OR between. ----- */
.setup-panel .tap-choice-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: stretch;
  gap: var(--tl-s3);
}
/* The old divider was a 34px circle nudged up by a magic 26px to sit level
   with a caption that no longer exists. */
.setup-panel .tap-choice-divider {
  width: auto;
  height: auto;
  margin: 0;
  border: 0;
  border-radius: 0;
  background: none;
  color: var(--muted);
  font-size: 11.5px;
  letter-spacing: 0.1em;
}
/* A rule with the word set into it, rather than a word floating in space. */
.setup-panel .tap-choice-divider::before,
.setup-panel .tap-choice-divider::after {
  content: "";
  flex: 1 1 auto;
  height: 1px;
  background: var(--tl-line);
}
.setup-panel .tap-choice-divider span { padding: 0 var(--tl-s3); }

@media (min-width: 720px) {
  .setup-panel .tap-choice-row {
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    align-items: center;
  }
  .setup-panel .tap-choice-divider {
    flex-direction: column;
    width: 34px;
    align-self: stretch;
  }
  .setup-panel .tap-choice-divider::before,
  .setup-panel .tap-choice-divider::after {
    width: 1px;
    height: auto;
  }
  .setup-panel .tap-choice-divider span { padding: var(--tl-s2) 0; }
}

/* --- The panel itself. --------------------------------------------------- */
@media (min-width: 860px) {
  /* Wider than the old 560px column, and no longer centred inside an empty
     page: the nav rail holds the left edge and the hero starts at it. */
  .setup-panel {
    max-width: 760px;
    margin-inline: 0;
    text-align: start;
  }
  .setup-panel .setup-title { margin-inline: 0; font-size: clamp(2.1rem, 3.4vw, 2.9rem); }
  .setup-panel .animal-picker { max-width: none; margin-inline: 0; text-align: start; }
  .setup-panel .animal-grid { grid-template-columns: repeat(8, minmax(0, 1fr)); }
  /* The panel is a centring flex column, which is what still pulled the two
     narrow items below the picker into the middle of a left-aligned page -
     their own margins and text-align had already been cleared. */
  .setup-panel .tap-to-talk { align-items: stretch; }
  .setup-panel .consent-line {
    margin-inline: 0;
    text-align: start;
  }
  .setup-panel .setup-growth { margin-inline: 0; }
  .setup-panel .tl-hero-sub { max-width: 62ch; }

  /* The stage centres its children; the home column starts at the rail. The
     call screen keeps the centring it was built with. */
  .tl-frame:not(.is-call) > .stage { align-items: stretch; text-align: start; }
}

/* Mid-call the frame collapses to a single full-width track. */
@media (min-width: 1180px) {
  .tl-frame.is-call { grid-template-columns: 224px minmax(0, 1fr); }
  .tl-frame.is-call .tl-rail { display: none; }
}

/* The green tokens are not the same green in every theme: dark ships a bright
   #2ed47a that wants dark ink on it, light ships a much deeper #17a862 that
   does not. Anything sitting on --green flips with the theme rather than
   carrying one ink colour into both. */
[data-theme="light"] .tl-action-voice,
[data-theme="light"] .tl-rail-live-cta { color: #fff; }

/* The picker's last tile: opens the rest of the animals, and closes them
   again. It is deliberately the same size and shape as a real animal tile, so
   the row reads as one strip of choices with a door at the end of it. */
.animal-option-more {
  color: var(--muted);
  border-style: dashed;
}
.animal-option-more:hover { border-color: var(--accent); color: var(--text); }
.animal-more-glyph {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  font-size: 24px;
  font-weight: 300;
  line-height: 1;
}

/* --- Online now: the rail's own header furniture and person rows. --------- */
.tl-rail-head-dot {
  flex: 0 0 auto;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--green);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--green) 22%, transparent);
  animation: tl-rail-pulse 2.4s ease-in-out infinite;
}
.tl-rail-head-icon {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 16px;
  height: 16px;
  color: var(--muted);
}
.tl-rail-head-icon svg { width: 16px; height: 16px; }
.tl-rail-count {
  margin-inline-start: auto;
  padding: 2px 9px;
  border-radius: var(--tl-r-pill);
  background: color-mix(in srgb, var(--green) 16%, transparent);
  color: var(--green);
  font-size: 12px;
  font-weight: 800;
}
.tl-rail-row-go {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: var(--tl-r-sm);
  color: var(--muted);
}
.tl-rail-row-go svg { width: 15px; height: 15px; }
/* Someone actually waiting in the queue gets the phone glyph and the green:
   they are the rows where searching their country is most likely to land. */
.tl-rail-person .tl-rail-row-go svg[fill="currentColor"] { color: var(--green); }
.tl-rail-person:hover .tl-rail-row-go { background: color-mix(in srgb, var(--text) 10%, transparent); color: var(--text); }
.tl-rail-foot {
  margin: var(--tl-s2) 0 0;
  padding: 0 var(--tl-s2);
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--muted);
}
.tl-rail-person .tl-rail-row-icon { background: color-mix(in srgb, var(--text) 7%, transparent); }
.tl-rail-person .tl-animal-icon { width: 22px; height: 22px; }

/* ===========================================================================
 * 5. THE TOP BAR
 * ---------------------------------------------------------------------------
 * Moved out to public/nav.css, which both / and /chat load after this file, so
 * one stylesheet decides what the header looks like on every screen.
 * ======================================================================== */

/* ===========================================================================
 * 6. THE HERO SKY
 * ---------------------------------------------------------------------------
 * Drawn from the theme tokens rather than photographed: no extra request on
 * the one screen that must paint instantly on a weak phone, no licence to
 * track, and it is the right picture on all four themes instead of on one.
 * Sits behind the hero text only, and never takes a pointer event.
 * ======================================================================== */

.setup-panel { position: relative; }

.tl-hero-sky {
  position: absolute;
  inset: calc(var(--tl-s5) * -1) calc(var(--tl-s5) * -1) auto;
  /* Ends just below the two action cards. Taken any further down it becomes a
     wash behind the animal picker, where it competes with twenty small
     coloured icons and wins - and its own bottom corners start showing as a
     rectangle drawn across the page. */
  height: 286px;
  z-index: 0;
  overflow: hidden;
  border-radius: var(--tl-r-lg);
  pointer-events: none;
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--accent) 42%, var(--bg-1)) 0%,
      color-mix(in srgb, var(--accent-2) 22%, var(--bg-1)) 52%,
      var(--bg-1) 100%);
  -webkit-mask-image:
    linear-gradient(180deg, #000 0%, #000 58%, transparent 100%),
    linear-gradient(90deg, transparent 0%, #000 12%, #000 88%, transparent 100%);
  -webkit-mask-composite: source-in;
  mask-image:
    linear-gradient(180deg, #000 0%, #000 58%, transparent 100%),
    linear-gradient(90deg, transparent 0%, #000 12%, #000 88%, transparent 100%);
  mask-composite: intersect;
}
/* The low sun, just off the left edge, where the reference photograph has it. */
.tl-hero-glow {
  position: absolute;
  left: 22%;
  top: 30%;
  width: 460px;
  height: 460px;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: radial-gradient(circle,
    color-mix(in srgb, var(--accent-2) 52%, transparent) 0%,
    color-mix(in srgb, var(--accent-2) 14%, transparent) 42%,
    transparent 70%);
}
/* The ridges sit above the fade rather than inside it, so they read as a
   skyline against the sky rather than dissolving into the page. */
.tl-hero-range {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 30px;
  width: 100%;
  height: 150px;
}
.tl-hero-far { fill: color-mix(in srgb, var(--bg-1) 55%, var(--accent)); opacity: 0.75; }
.tl-hero-near { fill: color-mix(in srgb, var(--bg-1) 78%, var(--accent)); }

/* Everything the hero says sits above the sky. */
.setup-panel > .setup-title,
.setup-panel > .tl-hero-sub,
.setup-panel > .tl-hero-actions,
.setup-panel > #tapToTalk { position: relative; z-index: 1; }

/* Light theme: the same sky, but a bright one - a dark wash over a white
   page reads as a rendering fault rather than as weather. */
[data-theme="light"] .tl-hero-sky {
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--accent) 18%, #ffffff) 0%,
      color-mix(in srgb, var(--accent-2) 12%, #ffffff) 48%,
      var(--bg-1) 100%);
}
[data-theme="light"] .tl-hero-far { fill: color-mix(in srgb, var(--accent) 26%, #ffffff); opacity: 0.8; }
[data-theme="light"] .tl-hero-near { fill: color-mix(in srgb, var(--accent) 40%, #ffffff); opacity: 0.9; }

@media (max-width: 859px) {
  .tl-hero-sky {
    inset: -24px -24px auto;
    height: 300px;
  }
  .tl-hero-glow { width: 300px; height: 300px; }
}

/* ===========================================================================
 * 7. MATCH FILTERS
 * ---------------------------------------------------------------------------
 * The panel used to ask four questions in four near-identical boxes and never
 * say what the answers added up to - two unlabelled country searches that
 * looked exactly alike, so people put a country in the wrong one and then
 * waited for a match that could not come. The colour, the mark and the count
 * on each list are what make them two different questions at a glance; the
 * sentence at the top is what makes the pair of them checkable.
 * ======================================================================== */

.tl-filter-summary {
  display: flex;
  align-items: flex-start;
  gap: var(--tl-s3);
  padding: var(--tl-s3) var(--tl-s4);
  border: 1px solid var(--tl-line);
  border-radius: var(--tl-r-md);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.tl-filter-summary-icon {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  margin-top: 2px;
  color: var(--accent-2);
}
.tl-filter-summary-icon svg { width: 20px; height: 20px; }
.tl-filter-summary-text { min-width: 0; }
.tl-filter-summary-text small {
  display: block;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted);
}
.tl-filter-summary-text strong {
  display: block;
  margin-top: 3px;
  font-size: 14.5px;
  font-weight: 700;
  line-height: 1.45;
}

.tl-filter-block { margin-top: var(--tl-s5); }
.tl-filter-legend {
  display: flex;
  align-items: center;
  gap: var(--tl-s2);
  margin: 0 0 var(--tl-s2);
  font-size: 14px;
  font-weight: 800;
  letter-spacing: -0.2px;
}
.tl-filter-optional { font-size: 12.5px; font-weight: 500; color: var(--muted); }
.tl-filter-premium { margin-inline-start: auto; font-size: 11.5px; text-decoration: none; }

/* The mark is what stops the two country lists being twins: a green tick on
   one, a red cross on the other, before a single word is read. */
.tl-filter-mark {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
}
.tl-filter-mark svg { width: 13px; height: 13px; }
.tl-filter-only .tl-filter-mark {
  background: color-mix(in srgb, var(--green) 20%, transparent);
  color: var(--green);
}
.tl-filter-never .tl-filter-mark {
  background: color-mix(in srgb, var(--danger) 20%, transparent);
  color: var(--danger);
}

.tl-filter-count {
  margin-inline-start: auto;
  padding: 2px 9px;
  border-radius: var(--tl-r-pill);
  background: color-mix(in srgb, var(--text) 10%, transparent);
  color: var(--muted);
  font-size: 11.5px;
  font-weight: 800;
}
/* At the free cap, so the next tap's refusal is not a surprise. */
.tl-filter-count.is-full {
  background: color-mix(in srgb, var(--accent-2) 18%, transparent);
  color: var(--accent-2);
}

.tl-filter-hint {
  margin: 0 0 var(--tl-s3);
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--muted);
}

.tl-filter-input {
  width: 100%;
  box-sizing: border-box;
  min-height: 46px;
  padding: 0 var(--tl-s3);
  border: 1px solid var(--tl-line-strong);
  border-radius: var(--tl-r-md);
  background: color-mix(in srgb, var(--text) 5%, transparent);
  color: var(--text);
  font: inherit;
  /* 16px so iOS does not zoom the panel when the field is focused. */
  font-size: 16px;
}
.tl-filter-input::placeholder { color: var(--muted); }
.tl-filter-input:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 1px; border-color: transparent; }
/* The two country lists tint their own field, so it is obvious which one the
   cursor is in once the heading has scrolled past. */
.tl-filter-only .tl-filter-input:focus-visible { outline-color: var(--green); }
.tl-filter-never .tl-filter-input:focus-visible { outline-color: var(--danger); }

/* Quick-picks: the common case, without typing. They disappear as they are
   used, so the row is always "what you could still add". */
.tl-filter-quick {
  display: flex;
  flex-wrap: wrap;
  gap: var(--tl-s2);
  margin-top: var(--tl-s3);
}
.tl-quick-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  min-height: 34px;
  padding: 0 var(--tl-s3);
  border: 1px dashed var(--tl-line-strong);
  border-radius: var(--tl-r-pill);
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
}
.tl-quick-chip:hover { border-style: solid; color: var(--text); background: color-mix(in srgb, var(--text) 8%, transparent); }
.tl-filter-only .tl-quick-chip:hover { border-color: var(--green); }

.tl-filter-chips { margin-top: var(--tl-s3); }
.tl-filter-chips:empty { margin-top: 0; }

.interest-input-wrap { display: flex; gap: var(--tl-s2); }
.tl-filter-add {
  flex: 0 0 auto;
  min-height: 46px;
  padding: 0 var(--tl-s4);
  border: 1px solid var(--tl-line-strong);
  border-radius: var(--tl-r-md);
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
}
.tl-filter-add:hover { background: color-mix(in srgb, var(--text) 8%, transparent); }

.filters-actions { display: flex; gap: var(--tl-s2); }
.tl-filter-btn {
  flex: 1 1 0;
  min-height: var(--tl-tap);
  border: 1px solid transparent;
  border-radius: var(--tl-r-md);
  font: inherit;
  font-size: 15px;
  font-weight: 700;
  cursor: pointer;
}
.tl-filter-btn-ghost { border-color: var(--tl-line-strong); background: transparent; color: var(--muted); }
.tl-filter-btn-ghost:hover { background: color-mix(in srgb, var(--text) 8%, transparent); color: var(--text); }
.tl-filter-btn-primary { flex: 2 1 0; background: var(--accent); color: #fff; }
.tl-filter-btn-primary:hover { filter: brightness(1.08); }

/* An empty count is no count, not a blank pill. */
.tl-filter-count:empty { display: none; }

/* ===========================================================================
 * 8. SETTINGS, AS A SCREEN
 * ---------------------------------------------------------------------------
 * Five groups of unrelated things used to be one 380px column you scrolled,
 * stacked on top of the page you were trying to use and gone the moment you
 * tapped outside it. Settings is not a quick action; it is somewhere you go.
 *
 * Desktop: a column of sections beside the one you are reading.
 * Phone:   the column IS the screen, and picking a section opens it with a
 *          back arrow - the pattern every phone settings app uses, because
 *          five screens of one thing beats one screen of five.
 * ======================================================================== */

.tl-set-page { width: 100%; }
.tl-set-page.hidden { display: none; }

.tl-set-page-head {
  display: flex;
  align-items: center;
  gap: var(--tl-s3);
  margin-bottom: var(--tl-s5);
}
.tl-set-back {
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  border: 1px solid var(--tl-line);
  border-radius: var(--tl-r-md);
  background: transparent;
  color: var(--text);
  cursor: pointer;
}
.tl-set-back:hover { background: color-mix(in srgb, var(--text) 8%, transparent); }
.tl-set-back svg { width: 20px; height: 20px; }
.tl-set-page-head h2 {
  margin: 0;
  font-size: 24px;
  font-weight: 800;
  letter-spacing: -0.5px;
}

.tl-set-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--tl-s5);
  align-items: start;
}

/* --- The section list ---------------------------------------------------- */
.tl-set-nav {
  display: flex;
  flex-direction: column;
  gap: var(--tl-s1);
}
.tl-set-nav-item {
  display: flex;
  align-items: center;
  gap: var(--tl-s3);
  width: 100%;
  min-height: var(--tl-tap);
  padding: 0 var(--tl-s3);
  border: 0;
  border-radius: var(--tl-r-md);
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 15px;
  font-weight: 600;
  text-align: start;
  cursor: pointer;
}
.tl-set-nav-item:hover { background: color-mix(in srgb, var(--text) 7%, transparent); }
.tl-set-nav-item.selected { background: color-mix(in srgb, var(--accent) 18%, transparent); }
.tl-set-nav-icon {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: var(--tl-r-sm);
  background: color-mix(in srgb, var(--text) 8%, transparent);
  color: var(--muted);
}
.tl-set-nav-item.selected .tl-set-nav-icon {
  background: color-mix(in srgb, var(--accent) 26%, transparent);
  color: var(--text);
}
.tl-set-nav-icon svg { width: 18px; height: 18px; }
.tl-set-nav-label { flex: 1 1 auto; min-width: 0; }
.tl-set-nav-go {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  color: var(--muted);
}
.tl-set-nav-go svg { width: 18px; height: 18px; }

/* --- The pane ------------------------------------------------------------ */
.tl-set-pane.hidden { display: none; }
/* The old panel drew its own heading above every group; on a page the group
   is already named by the section you picked to get here. */
.tl-set-pane > section > .tl-set-legend { display: none; }
.tl-set-content {
  min-width: 0;
  padding: var(--tl-s5);
  border: 1px solid var(--tl-line);
  border-radius: var(--tl-r-lg);
  background: var(--tl-card);
}

@media (min-width: 860px) {
  .tl-set-layout { grid-template-columns: 240px minmax(0, 1fr); }
  .tl-set-nav {
    position: sticky;
    top: calc(64px + var(--tl-s4));
  }
  /* On a desktop both columns are on screen at once, so the chevron on each
     row would be promising a navigation that does not happen. */
  .tl-set-nav-go { display: none; }
}

/* --- Phone: two screens, not two columns. -------------------------------- */
@media (max-width: 859px) {
  .tl-set-page-head h2 { font-size: 21px; }
  .tl-set-content {
    padding: var(--tl-s4);
    border: 0;
    border-radius: 0;
    background: none;
  }
  /* Before a section is picked, the list is the whole screen. */
  .tl-set-page:not(.is-drilled) .tl-set-content { display: none; }
  .tl-set-page.is-drilled .tl-set-nav { display: none; }
  .tl-set-nav-item { min-height: 56px; }
}

/* The stage centres its children for the call screen; a settings page is
   read, not watched, so everything in it starts at the left edge. */
.tl-set-page,
.tl-set-page .tl-set-control,
.tl-set-page .tl-set-row { text-align: start; }

/* On a page there is room for label and control on one line, so the stacked
   layout the 380px panel needed only applies where it still has to. */
@media (min-width: 720px) {
  .tl-set-page .tl-set-control-stack {
    display: flex;
    align-items: center;
    gap: var(--tl-s4);
  }
  .tl-set-page .tl-set-control-stack > .tl-set-label {
    flex: 0 0 168px;
    margin-bottom: 0;
  }
  .tl-set-page .tl-set-control-stack > .tl-set-field,
  .tl-set-page .tl-set-control-stack > .lang-wrap,
  .tl-set-page .tl-set-control-stack > .tl-seg { flex: 1 1 auto; min-width: 0; }
  /* The segmented groups would stretch each option to fill a 700px row. */
  .tl-set-page .tl-set-control-stack > .tl-seg { justify-content: flex-start; }
}

/* A group inside a pane is already inside a card, so it does not need a
   second border around it - just a rule between its rows. */
.tl-set-page .tl-set-group {
  border: 0;
  border-radius: 0;
  background: none;
}
.tl-set-page .tl-set-group + .tl-set-group { margin-top: var(--tl-s5); }
.tl-set-page .tl-set-foot {
  display: flex;
  flex-wrap: wrap;
  gap: var(--tl-s2);
  margin-top: var(--tl-s5);
  padding-top: var(--tl-s4);
  border-top: 1px solid var(--tl-line);
}

/* ===========================================================================
 * 9. THE CALL SCREEN
 * ---------------------------------------------------------------------------
 * It used to be a stack of everything the app could do, on the one screen
 * where the point is to be talking to someone: orb, status, sub-status,
 * ticker, reassurance, partner card, reaction bar, four identical buttons
 * where hanging up looked exactly like muting, an auto-call checkbox, a music
 * button and an ad.
 *
 * Now it is three things in the order you need them - who you are talking to,
 * what you can do, and how to leave - on one centred column with air around
 * it. Mute and Add friend stay out in the open because they are wanted
 * mid-sentence; everything else is behind More. End Call is the only red pill
 * on the screen and it is nowhere near the other two.
 * ======================================================================== */

.tl-call {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: min(420px, 100%);
  margin: 0 auto;
  padding: var(--tl-s5) var(--tl-s4) var(--tl-s6);
  box-sizing: border-box;
  border: 1px solid var(--tl-line);
  border-radius: 28px;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--accent) 10%, var(--bg-2)) 0%,
    var(--bg-2) 46%);
  text-align: center;
}


/* The connection dot and the timer sit in the column with the status lines,
   centred, because that is what they are about. They used to be pinned to a
   corner in a header row of their own - a whole row of the shortest screen in
   the app, spent putting the word for what is happening as far from the
   sentence about it as the card allowed. */
.tl-call .connection-indicator {
  margin: var(--tl-s2) auto 0;
  justify-content: center;
}

/* --- Who ----------------------------------------------------------------- */
.tl-call .orb-wrap { margin-bottom: var(--tl-s4); }
.tl-call .status-text {
  margin: 0;
  font-size: 20px;
  font-weight: 800;
  letter-spacing: -0.3px;
}
.tl-call .sub-text {
  margin: var(--tl-s1) 0 0;
  font-size: 13.5px;
  color: var(--muted);
}

/* --- The voice ----------------------------------------------------------- */
.tl-call-wave {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 3px;
  width: 100%;
  height: 54px;
  margin: var(--tl-s4) 0 0;
}
.tl-call-wave.hidden { display: none; }
.tl-call-wave-bar {
  flex: 1 1 0;
  max-width: 5px;
  height: 8%;
  min-height: 3px;
  border-radius: 999px;
  background: linear-gradient(180deg, var(--accent-2), var(--accent));
  /* Short enough to track a voice, long enough not to strobe. */
  transition: height 90ms linear;
}
@media (prefers-reduced-motion: reduce) {
  .tl-call-wave-bar { transition: none; }
}

/* --- What you can do -----------------------------------------------------
 * Four circles, one row, one size, each with its word underneath. They are
 * the four things you do to a call, and none of them wins by enough to be
 * drawn bigger than the others: what tells them apart is colour, not scale.
 * The caption takes its button's colour, so whichever one is doing something
 * right now says so twice - red for Hang up and for a live mute, green once
 * Add friend has landed, grey for everything at rest.
 * ------------------------------------------------------------------------- */
.tl-call-main {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  align-items: start;
  justify-items: center;
  gap: var(--tl-s2);
  width: 100%;
  /* Clear of the waveform above: nothing should be one thumb-width from
     Hang up. */
  margin-top: var(--tl-s5);
}
.tl-call-main .call-action-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
}
.tl-call-main .action-label,
.tl-call-main .call-main-label {
  font-size: 12px;
  font-weight: 700;
  color: var(--muted);
  text-align: center;
  line-height: 1.2;
}

/* The caption follows the button. Red under Hang up, because the word and the
   circle saying the same thing is what stops a thumb landing on it by
   accident; green under Call, which is the only invitation on the screen. */
.tl-call-main .call-main-label.is-call { color: var(--green); }
.tl-call-main .call-main-label.is-hangup,
.tl-call-main .call-main-label.is-confirm { color: var(--danger); }
.tl-call-main .call-main-label.is-loading { color: var(--muted); }
.tl-call-main .action-btn.muted + .action-label { color: var(--danger); }
.tl-call-main .action-btn.added + .action-label { color: var(--green); }

/* One size for all three, set here so it beats style.css's own .action-btn
   and .stage.call-live .action-btn rules (3 classes, so ours need .stage
   too). Round, because round is what every phone has used for answer and
   hang up for twenty years. */
.stage .tl-call-main .action-btn,
.stage.call-live .tl-call-main .action-btn,
.stage .tl-call-main .call-main-btn,
.stage.call-live .tl-call-main .call-main-btn {
  width: 66px;
  height: 66px;
  border-radius: 50%;
  margin: 0;
}
.tl-call-main .call-main-icon { width: 27px; height: 27px; }
.tl-call-main .simple-icon-lg { width: 25px; height: 25px; }

/* Report is red before it is pressed, not after. It is the only control here
   whose whole job is to be recognised in a hurry. */
.tl-call-main .action-btn-danger { color: var(--danger); }
.tl-call-main .action-btn-danger svg { fill: currentColor; }
.tl-call-main .action-btn-danger:hover:not(:disabled) {
  background: color-mix(in srgb, var(--danger) 20%, transparent);
}

/* Dimmed rather than hidden while there is nobody on the other end: the row
   keeps its shape, so the buttons do not jump under your thumb the moment a
   call connects. */
.tl-call-main .action-btn:disabled { opacity: 0.4; cursor: default; }

.tl-call-main .action-btn.muted {
  background: color-mix(in srgb, var(--danger) 22%, transparent);
  border-color: var(--danger);
}
.tl-call-main .action-btn.muted svg { fill: var(--danger); }
/* The slash was drawn at a fixed 40px for a 72px button; the buttons shrink
   with the viewport now, so it does too. */
.tl-call-main .mute-slash { height: 58%; box-shadow: none; }
.tl-call-main .action-btn.added {
  background: color-mix(in srgb, var(--green) 22%, transparent);
  border-color: var(--green);
}
.tl-call-main .action-btn.added svg { fill: var(--green); }

/* --- Under the row: the auto switch, then the quiet buttons --------------- */
.tl-call-extras {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--tl-s3);
  width: min(420px, 100%);
  margin: var(--tl-s4) auto 0;
}

/* Auto-connect is a setting that stays on, not an action you take, so it is
   a switch and not a fourth circle - and a switch shows its state without
   being pressed, which is the whole reason it left the drawer. */
.tl-auto-switch {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  /* Small, and on one line: it is a setting, not one of the three actions,
     and anything taller starts competing with them for the eye. */
  padding: 6px 8px 6px 10px;
  border: 1px solid var(--tl-line);
  border-radius: var(--tl-r-md);
  background: color-mix(in srgb, var(--text) 5%, transparent);
  cursor: pointer;
  transition: border-color 0.15s ease, background-color 0.15s ease;
  -webkit-tap-highlight-color: transparent;
}
.tl-auto-switch:hover { border-color: var(--tl-line-strong); }
.tl-auto-switch-icon {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--text) 8%, transparent);
  color: var(--muted);
  transition: background-color 0.15s ease, color 0.15s ease;
}
.tl-auto-switch-icon svg { width: 14px; height: 14px; }
.tl-auto-switch-text {
  min-width: 0;
  flex: 0 1 auto;
}
.tl-auto-switch-text strong { font-size: 13px; font-weight: 700; color: var(--text); }
/* The explanation is what a tooltip is for. On one line it would double the
   height of the smallest control on the screen. */
.tl-auto-switch-text small { display: none; }

/* The input itself stays in the accessibility tree and keeps the keyboard
   working - it is just drawn as the track beside it. */
.tl-auto-switch input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}
.tl-switch {
  position: relative;
  flex: 0 0 auto;
  width: 38px;
  height: 22px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--text) 16%, transparent);
  transition: background-color 0.18s ease;
}
.tl-switch-knob {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
  transition: transform 0.18s ease;
}
.tl-auto-switch input:checked ~ .tl-switch { background: var(--green); }
.tl-auto-switch input:checked ~ .tl-switch .tl-switch-knob { transform: translateX(16px); }
.tl-auto-switch:has(input:checked) {
  border-color: color-mix(in srgb, var(--green) 55%, transparent);
  background: color-mix(in srgb, var(--green) 10%, transparent);
}
.tl-auto-switch:has(input:checked) .tl-auto-switch-icon {
  background: color-mix(in srgb, var(--green) 22%, transparent);
  color: var(--green);
}
.tl-auto-switch input:focus-visible ~ .tl-switch {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
  .tl-switch, .tl-switch-knob { transition: none; }
}

/* Your spirit animal, pickable mid-wait. It is a quiet button rather than a
   fifth circle: it changes how you look, not what the call does. */
.tl-call-quiet {
  display: flex;
  justify-content: center;
  gap: var(--tl-s2);
}
.tl-call-quiet-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 8px 14px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--muted);
  border: 1px solid var(--tl-line);
  border-radius: 999px;
  background: transparent;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}
.tl-call-quiet-btn svg { width: 15px; height: 15px; flex: 0 0 auto; }
.tl-call-quiet-btn:hover:not(:disabled) {
  color: var(--text);
  border-color: var(--tl-line-strong);
  background: color-mix(in srgb, var(--text) 6%, transparent);
}
.tl-call-quiet-btn:disabled { opacity: 0.4; cursor: default; }
.tl-call-quiet-btn.is-open {
  color: var(--text);
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 16%, transparent);
}

/* --- More ---------------------------------------------------------------- */
.tl-call-sheet {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--tl-s3);
  width: 100%;
  margin-top: var(--tl-s4);
  padding: var(--tl-s4);
  box-sizing: border-box;
  border: 1px solid var(--tl-line);
  border-radius: var(--tl-r-lg);
  background: color-mix(in srgb, var(--text) 5%, transparent);
}
.tl-call-sheet.hidden { display: none; }
.tl-call-sheet .call-action-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}
.tl-call-sheet .action-btn { width: 52px; height: 52px; border-radius: 50%; }
.tl-call-sheet .action-label { font-size: 12px; color: var(--muted); }
.tl-call-sheet .auto-call-row { margin: 0; font-size: 13px; }

/* Reactions belong to the live call, so they sit with it rather than in the
   sheet - but they are a strip, not a stack. */
.tl-call-reactions { margin-top: var(--tl-s4); }

@media (max-width: 480px) {
  .tl-call {
    border: 0;
    border-radius: 0;
    background: none;
    padding-inline: var(--tl-s2);
  }
  .tl-call-main { gap: var(--tl-s2); }
}


/* ===========================================================================
 * 10. "COMING SOON" CARDS
 * ---------------------------------------------------------------------------
 * Premium and the coin shop are announced, not on sale. A card that sells
 * something nobody can buy is a card that lies, so these say what is coming,
 * list what it will do, and carry one button that works today: a reminder for
 * when it does.
 * ======================================================================== */

.tl-soon-list {
  margin: var(--tl-s3) 0 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.tl-soon-list li {
  position: relative;
  padding-inline-start: 22px;
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--muted);
}
.tl-soon-list li::before {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  top: 4px;
  width: 13px;
  height: 8px;
  border-inline-start: 2px solid var(--green);
  border-bottom: 2px solid var(--green);
  transform: rotate(-45deg);
}

.tl-notify-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--tl-s2);
  width: 100%;
  min-height: 44px;
  padding: 0 var(--tl-s4);
  border: 0;
  border-radius: var(--tl-r-md);
  background: var(--accent);
  color: #fff;
  font: inherit;
  font-size: 13.5px;
  font-weight: 700;
  cursor: pointer;
}
.tl-notify-btn svg { width: 16px; height: 16px; }
.tl-notify-btn:hover { filter: brightness(1.08); }
.tl-notify-btn.is-notified {
  background: color-mix(in srgb, var(--green) 20%, transparent);
  color: var(--green);
  cursor: default;
  filter: none;
}

.tl-soon-cta { margin-top: var(--tl-s4); }
.tl-soon-foot {
  margin: var(--tl-s2) 0 0;
  font-size: 12px;
  text-align: center;
  color: var(--muted);
}

.tl-nav-promo-cta { margin-top: var(--tl-s3); }
.tl-nav-promo-link {
  display: block;
  margin-top: var(--tl-s2);
  font-size: 12px;
  font-weight: 600;
  text-align: center;
  color: var(--accent-2);
  text-decoration: none;
}
.tl-nav-promo-link:hover { text-decoration: underline; }

/* ===========================================================================
 * 11. THE PHONE DRAWER
 * ---------------------------------------------------------------------------
 * Everything the bottom bar has no room for: the destination that is not
 * built yet, Settings, Help, Safety, and the theme picker - the one
 * preference people change often enough that a trip into a settings screen
 * for it is a trip too many.
 *
 * Phone only, both ways round: the drawer and its "More" tab do not exist on
 * a desktop, where the rail already shows all of this, and the rail's own
 * Settings row does not exist on a phone, where the drawer holds it.
 * ======================================================================== */

.tl-nav-item-phone { display: none; }

@media (max-width: 859px) {
  .tl-nav-item-phone { display: flex; }
  .tl-nav-item-desk { display: none; }
}

.tl-drawer-overlay {
  position: fixed;
  inset: 0;
  z-index: 70;
  background: rgba(5, 7, 15, 0.6);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
}
.tl-drawer-overlay.hidden { display: none; }

.tl-drawer {
  position: fixed;
  z-index: 71;
  inset-block: 0;
  inset-inline-start: 0;
  width: min(300px, 86vw);
  display: flex;
  flex-direction: column;
  padding: var(--tl-s4);
  padding-top: calc(var(--tl-s4) + env(safe-area-inset-top, 0px));
  padding-bottom: calc(var(--tl-s4) + env(safe-area-inset-bottom, 0px));
  box-sizing: border-box;
  background: var(--bg-2);
  border-inline-end: 1px solid var(--tl-line);
  box-shadow: var(--tl-shadow);
  overflow-y: auto;
  overscroll-behavior: contain;
  animation: tl-drawer-in 180ms ease-out both;
}
.tl-drawer.hidden { display: none; }
@keyframes tl-drawer-in {
  from { transform: translateX(-100%); }
  to { transform: none; }
}
[dir="rtl"] .tl-drawer { animation-name: tl-drawer-in-rtl; }
@keyframes tl-drawer-in-rtl {
  from { transform: translateX(100%); }
  to { transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .tl-drawer { animation: none; }
}
/* On a desktop the rail already shows everything in here. */
@media (min-width: 860px) {
  .tl-drawer,
  .tl-drawer-overlay { display: none !important; }
}

.tl-drawer-head {
  display: flex;
  align-items: center;
  gap: var(--tl-s2);
  margin-bottom: var(--tl-s4);
}
.tl-drawer-brand {
  display: flex;
  align-items: center;
  gap: var(--tl-s2);
  font-size: 17px;
  font-weight: 800;
  letter-spacing: -0.3px;
}
.tl-drawer-head .tl-panel-close { margin-inline-start: auto; }

.tl-drawer-items {
  display: flex;
  flex-direction: column;
  gap: var(--tl-s1);
}
.tl-drawer-item {
  display: flex;
  align-items: center;
  gap: var(--tl-s3);
  width: 100%;
  min-height: var(--tl-tap);
  padding: 0 var(--tl-s3);
  border: 0;
  border-radius: var(--tl-r-md);
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 15px;
  font-weight: 600;
  text-align: start;
  text-decoration: none;
  cursor: pointer;
}
.tl-drawer-item:hover { background: color-mix(in srgb, var(--text) 8%, transparent); }
.tl-drawer-icon {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  color: var(--muted);
}
.tl-drawer-icon svg { width: 21px; height: 21px; }
.tl-drawer-item .tl-nav-soon { margin-inline-start: auto; display: inline-block; }

.tl-drawer-theme {
  margin-top: auto;
  padding-top: var(--tl-s4);
  border-top: 1px solid var(--tl-line);
}
.tl-drawer-theme-label {
  margin: 0 0 var(--tl-s2);
  font-size: 11.5px;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
.tl-drawer-themes {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--tl-s2);
}
.tl-theme-chip {
  display: flex;
  align-items: center;
  gap: 7px;
  min-height: 42px;
  padding: 0 var(--tl-s3);
  border: 1px solid var(--tl-line-strong);
  border-radius: var(--tl-r-md);
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
}
.tl-theme-chip.selected {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--text);
}
.tl-theme-dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 1px solid var(--tl-line-strong);
}
.theme-dot-ocean { background: #00b4d8; }
.theme-dot-sunset { background: #ff6b6b; }

/* ===========================================================================
 * 12. THE CALL ROW - call on the left, one ad beside it on the right
 * ---------------------------------------------------------------------------
 * The rectangle used to sit under the call card, which on a 1000px-tall
 * window put it below the fold. An ad nobody scrolls to is an ad nobody is
 * paid for - and the space beside the call card was empty anyway.
 *
 * Two equal columns from 980px up, stacked below it, because a phone only
 * ever has one column to give.
 * ======================================================================== */

.tl-call-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--tl-s4);
  width: 100%;
  align-items: stretch;
}

.tl-call-ad {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--tl-s2);
  min-width: 0;
  margin: 0;
}

@media (min-width: 980px) {
  .tl-call-row {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--tl-s5);
    max-width: 980px;
    margin-inline: auto;
  }
  /* The ad card is as tall as its creative and no taller. Matching the call
     card's height sounded tidy and produced a 500px panel with a 250px
     banner pinned to the top of it and a labelled void underneath - which
     reads as a thing that failed to load, not as a layout. It sits at the
     top of its column, level with the call card's own top edge. */
  .tl-call-row { align-items: start; }
  .tl-call-row .tl-call { width: 100%; margin: 0; }
  .tl-call-row .tl-call-ad {
    box-sizing: border-box;
    border: 1px solid var(--tl-line);
    border-radius: 28px;
    padding: var(--tl-s4);
  }
}

/* One column, one ad. The 300x250 card belongs to the two-column layout; the
   phone gets the strip in section 14 instead. Each is hidden at the other's
   widths, and a display:none slot never enters the viewport, so ads.js never
   loads it and it spends no slot budget. */
@media (max-width: 979px) {
  .tl-call-ad-wide { display: none; }
}
@media (min-width: 980px) {
  .tl-call-ad-phone { display: none; }
}

/* ===========================================================================
 * 13. THE SEARCH ANCHOR vs THE TAB BAR
 * ---------------------------------------------------------------------------
 * The adhesive 320x50 shown while the voice matcher is searching is
 * `position:fixed; bottom:0; z-index:45`. The phone's tab bar is
 * `position:fixed; bottom:0; z-index:56`. So on every phone since the tab bar
 * shipped, that ad has been rendering underneath it: requested, served,
 * counted as an impression, and impossible for anyone to see.
 *
 * That is worse than no ad. It earns nothing, and an impression nobody could
 * have seen is exactly what viewability audits and invalid-traffic checks
 * look for. It sits above the bar now, which is where an adhesive belongs
 * anyway - between the content and the navigation, not under both.
 * ======================================================================== */

@media (max-width: 859px) {
  .tl-search-anchor {
    bottom: calc(var(--tl-navbar) + env(safe-area-inset-bottom, 0px));
    /* Its own safe-area padding is now the tab bar's job, not its own. */
    padding-bottom: 0;
    min-height: 50px;
    border-radius: 12px;
  }
}

/* One landing ad, sized for the screen it is on. ads.js picks a unit from the
   viewport, so a "leaderboard" on a phone became a 320x50 - the lowest-CPM
   shape in the inventory, sitting in the best slot on the site. The phone
   gets a 300x250 instead, which is the shape that fits and the shape that
   pays. A display:none slot never enters the viewport, so ads.js never loads
   the one that is not in use and it spends no slot budget. */
.ad-card-phone { display: none; }
@media (max-width: 859px) {
  .ad-card-desk { display: none; }
  .ad-card-phone { display: block; }
}

/* ===========================================================================
 * 15. NOTHING DIMMED THAT COULD BE ABSENT
 * ---------------------------------------------------------------------------
 * Add friend, Mute and Report all need a partner. The Call button sits in
 * the same row, so the row itself can never go away - but the two circles
 * beside it and the Report button have nothing to do yet, and saying so
 * quietly is better than three controls that look live and are not.
 * ======================================================================== */

.call-panel:not(.is-connected) .tl-call-main .action-btn:disabled,
.call-panel:not(.is-connected) .tl-call-quiet-btn:disabled {
  border-color: var(--tl-line);
  background: color-mix(in srgb, var(--text) 4%, transparent);
}
/* Their labels go with them: three words at full contrast under three dead
   buttons is the row reading as live when it is not. */
.call-panel:not(.is-connected) .tl-call-main .action-btn:disabled + .action-label {
  opacity: 0.5;
}

/* ===========================================================================
 * 16. THE SPIRIT ANIMALS ON THE CALL SCREEN
 * ---------------------------------------------------------------------------
 * Denser than the landing picker, and inside the More sheet: you are waiting,
 * so the whole set is on show and there is no "More" tile to get past.
 * ======================================================================== */

.tl-call-animals { width: 100%; }
.tl-call-animals-label {
  margin: 0 0 var(--tl-s2);
  font-size: 12.5px;
  font-weight: 700;
  text-align: center;
  color: var(--muted);
}
.tl-call-animal-grid {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 6px;
  max-height: 188px;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.tl-call-animal-grid .animal-option { padding: 6px 2px 4px; border-radius: 12px; }
.tl-call-animal-grid .tl-animal-icon { width: 30px; height: 30px; }
.tl-call-animal-grid .animal-option-name { font-size: 9.5px; }
@media (min-width: 480px) {
  .tl-call-animal-grid { grid-template-columns: repeat(7, minmax(0, 1fr)); }
}

/* ===========================================================================
 * 15. WHAT THE COLOURS PROMISE
 * ---------------------------------------------------------------------------
 * Every control on the call screen was one of two colours: green for the big
 * one, grey for everything else. Grey says "inactive", so a row of grey said
 * "nothing here works" - including the buttons that do.
 *
 * The rule, and it is the same one phones have taught everybody for years:
 *   green   start, go, connect
 *   red     stop, end, this is off
 *   grey    nothing is happening here
 * ======================================================================== */

/* Muted is a thing being OFF, which is red everywhere else on a phone. Grey
   with a small slash through it was the only cue, and it read as "disabled"
   rather than as "they cannot hear you". */
.tl-call-main .action-btn.muted {
  background: color-mix(in srgb, var(--danger) 26%, transparent);
  color: var(--danger);
}
.tl-call-main .action-btn.muted svg { fill: var(--danger); }
/* The slash was drawn at a fixed 40px for a 72px button; the buttons shrink
   with the viewport now, so it does too. */
.tl-call-main .mute-slash { height: 58%; box-shadow: none; }

/* Add friend, once sent, is a thing that succeeded - green, not grey. */
.tl-call-main .action-btn.added {
  background: color-mix(in srgb, var(--green) 24%, transparent);
  color: var(--green);
  opacity: 1;
}
.tl-call-main .action-btn.added svg { fill: var(--green); }

/* The two landing cards already carry the product's two promises in colour -
   green for voice, accent for text. The labels under them are what tells
   somebody which is which before they read a word, so they keep full
   contrast rather than fading into the card. */
.tl-action-body small { opacity: 0.92; }

/* ===========================================================================
 * 14. THE PHONE CALL SCREEN - one screen, and the ad's own place in it
 * ---------------------------------------------------------------------------
 * On a phone the live call did not fit. The column - back button and
 * connection row, orb, status, waveform, partner, reactions, Mute/Add
 * friend/More, then Next/Call/Auto - was sized by three hard-coded viewport
 * heights (110px orb over 740, 92px under it, 72px under 620). Every real
 * phone in portrait lands in the middle band, where the stack came to roughly
 * 620px inside a viewport that, once the browser bar and the tab bar have
 * taken their share, is about 560px tall. The main row - Next, the call
 * button, Auto, the three controls the whole product is - was cut in half by
 * the tab bar, and the only way to hang up was to scroll first.
 *
 * So the sizes are keyed to the viewport itself rather than to three guesses
 * about it: each one is a clamp between the smallest it can be and the size it
 * has today, tracking dvh in between. Nothing jumps at a breakpoint, every
 * phone gets the largest call screen that fits it, and the controls stay put
 * when the browser bar collapses because dvh is the small viewport.
 *
 * The room that frees up is the ad's. It is one strip, with its height
 * reserved before anything loads, directly below the controls and separated
 * from them by more space than sits between any two controls - close enough to
 * be seen without scrolling, far enough that a thumb aiming for the call
 * button cannot land on a creative. (The 300x250 that used to stack here on a
 * phone is desktop-only now: 250px of rectangle, more than a third of the
 * viewport, under a screen that already overflowed.)
 * ======================================================================== */

@media (max-width: 979px) {
  /* The stage is already exactly the viewport minus the header and the tab bar
     - .app is 100dvh with the bar's height as padding, and .stage is the flex
     child that grows. It does not need a second min-height of its own, and the
     one it had (100dvh - 90px) was taller than the space it sits in. */
  .stage.call-live {
    min-height: 0;
    justify-content: flex-start;
    padding: var(--tl-s2) var(--tl-s3) 0;
  }

  /* The first screen, measured rather than hoped for.
   *
   * The call column and the ad strip share exactly the height between the
   * header and the tab bar, so the strip lands ON the first screen instead of
   * a few pixels under it - which is the difference between an ad everybody
   * sees and an ad only the people who scroll mid-call ever see. The chrome
   * estimate is deliberately generous (the header measures 52-57px): being a
   * few pixels short leaves a sliver of space above the tab bar, being a few
   * pixels long would put the strip under it.
   */
  .stage.call-live .tl-call-row {
    display: flex;
    flex-direction: column;
    /* One column, so the row's column gap would only push the strip down. The
       call column and the strip bring their own space. */
    gap: 0;
    min-height: calc(100dvh - 60px - var(--tl-callbar) - var(--tl-s3)
      - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px));
  }

  /* The free space goes above and below the call, which centres it in
     whatever is left once the strip has its height - so the strip sits on the
     bottom edge of the first screen and the call sits in the middle of the
     rest. Nothing is pinned or floating: both scroll away with the page. */
  .stage.call-live .tl-call {
    flex: 0 1 auto;
    margin-block: auto;
    padding: clamp(var(--tl-s1), 1dvh, var(--tl-s2)) var(--tl-s2) 0;
  }

  /* The back button and the connection row. Full-size taps, minimal margin. */
  .stage.call-live .tl-call-top { margin-bottom: clamp(var(--tl-s1), 1.2dvh, var(--tl-s3)); }
  .stage.call-live .tl-call-min { width: 40px; height: 40px; }
  .stage.call-live .connection-indicator { margin-bottom: 0; font-size: 11.5px; }

  /* The orb. The ring box is the orb plus room to pulse, so the two scale
     together; the timer inside it is the one number on the screen you read at
     a glance, so it gives up the least. */
  .stage.call-live .orb-wrap {
    width: clamp(58px, 11dvh, 124px);
    height: clamp(58px, 11dvh, 124px);
    margin-bottom: clamp(var(--tl-s1), 1.2dvh, var(--tl-s3));
  }
  .stage.call-live .orb {
    width: clamp(50px, 8.6dvh, 98px);
    height: clamp(50px, 8.6dvh, 98px);
  }
  .stage.call-live .orb-core {
    width: clamp(34px, 5.8dvh, 66px);
    height: clamp(34px, 5.8dvh, 66px);
  }
  .stage.call-live .orb-timer { font-size: clamp(15px, 2.4dvh, 22px); }

  .stage.call-live .tl-call .status-text {
    margin-bottom: 0;
    font-size: clamp(15px, 2.5dvh, 20px);
  }
  .stage.call-live .tl-call .sub-text { font-size: 12.5px; }

  /* The waveform is the proof the call is alive, so it stays on every phone -
     it just gets shorter rather than taking a fixed 54px everywhere. */
  .stage.call-live .tl-call-wave {
    height: clamp(18px, 3.6dvh, 48px);
    margin-top: clamp(var(--tl-s1), 1.2dvh, var(--tl-s3));
  }

  /* Who you are talking to is a name and a flag. It was taking 61px for two
     short lines, most of it the padding and margins of children that are
     empty on almost every call. */
  .stage.call-live .partner-card {
    margin: clamp(var(--tl-s1), 1.2dvh, var(--tl-s3)) 0 0;
    padding: 0;
  }
  .stage.call-live .partner-name { font-size: 15px; line-height: 1.25; }
  .stage.call-live .partner-interests:empty,
  .stage.call-live .partner-animal:empty { display: none; }

  .stage.call-live .tl-call-reactions {
    margin: clamp(var(--tl-s1), 1.2dvh, var(--tl-s3)) 0 0;
    gap: var(--tl-s2);
  }
  .stage.call-live .reaction-btn {
    width: clamp(38px, 5.2dvh, 44px);
    height: clamp(38px, 5.2dvh, 44px);
  }
  .stage.call-live .reaction-btn svg { width: 20px; height: 20px; }

  /* Four of them across a phone. One size for all four, never below 44px:
     these are taps, and the row you must never hit by accident. */
  .stage.call-live .tl-call-main {
    gap: var(--tl-s1);
    margin-top: clamp(var(--tl-s1), 1.4dvh, var(--tl-s5));
  }
  .stage .tl-call-main .action-btn,
  .stage.call-live .tl-call-main .action-btn,
  .stage .tl-call-main .call-main-btn,
  .stage.call-live .tl-call-main .call-main-btn {
    width: clamp(48px, 7.4dvh, 66px);
    height: clamp(48px, 7.4dvh, 66px);
  }
  /* "Add friend" is the long one. It gets to wrap rather than force the
     four columns apart. */
  .stage.call-live .tl-call-main .action-label,
  .stage.call-live .tl-call-main .call-main-label { font-size: 11px; }
  .stage.call-live .tl-call-main .call-main-icon,
  .stage.call-live .tl-call-main .simple-icon-lg {
    width: clamp(20px, 3.3dvh, 25px);
    height: clamp(20px, 3.3dvh, 25px);
  }

  /* On a phone the switch and the two quiet buttons share one line. Stacked
     they cost about 45px, and 45px here is 45px of the ad below falling
     behind the tab bar - which is the whole screen's tightest trade. */
  .stage.call-live .tl-call-extras {
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--tl-s2);
    margin-top: clamp(var(--tl-s1), 1.4dvh, var(--tl-s4));
  }
  .stage.call-live .tl-auto-switch {
    padding: 5px 7px 5px 9px;
    gap: 7px;
  }
  .stage.call-live .tl-auto-switch-text strong { font-size: 12.5px; }
  .stage.call-live .tl-call-quiet { width: auto; }
  .stage.call-live .tl-call-quiet-btn {
    flex: 0 0 auto;
    padding: 6px 10px;
    font-size: 12px;
  }

  /* More opens below the fold on a phone, so app.js scrolls it into view; this
     is how far it has to clear the tab bar to be fully visible when it does. */
  .stage.call-live .tl-call-sheet {
    scroll-margin-bottom: calc(var(--tl-navbar) + var(--tl-s3) + env(safe-area-inset-bottom, 0px));
  }

  /* --- The ad's place --------------------------------------------------- */
  .tl-call-ad-phone {
    /* More space above it than between any two controls, so the strip reads as
       a different thing from the screen it sits under rather than as one more
       row of it. */
    margin-top: clamp(var(--tl-s2), 2.4dvh, var(--tl-s5));
    margin-bottom: 0;
    margin-inline: auto;
    padding: 4px 6px 5px;
    border-radius: var(--tl-r-lg);
  }

  /* The box only exists once there is an ad in it.
     style.css keeps every .ad-card frameless until ads.js sets data-ad-filled,
     because a labelled empty panel announcing an advertisement that was never
     served is worse than no panel - and with a blocker or a filtering resolver
     that is what this slot would be. Same rule here: until then this is
     reserved space and nothing else. Only colour changes, never size, so the
     box appearing cannot move the call controls above it. */
  .tl-call-ad-phone[data-ad-filled] {
    /* Built like the call card above it rather than like a bare creative
       dropped on the page: the same hairline, the same corner, the same
       accent-tinted top edge, and a shadow so it reads as one object sitting
       on the screen instead of a hole cut in it. */
    border: 1px solid var(--tl-line);
    background:
      linear-gradient(180deg,
        color-mix(in srgb, var(--accent) 9%, var(--bg-2)) 0%,
        var(--bg-2) 70%);
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.26);
  }
  /* The creative sits on its own plate, centred, so a 320x50 in a wider card
     looks placed rather than stranded. */
  .tl-call-ad-phone [data-ad] {
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    border-radius: var(--tl-r-md);
  }
  /* The label keeps the site's size: a smaller disclosure to buy two pixels is
     not a trade worth making. */
  .tl-call-ad-phone .ad-card-label { margin-bottom: 2px; line-height: 1.2; }

  /* The music button and the conversation guide are below the fold during a
     call anyway; keeping them tight means the ad is the last thing on the
     first screen rather than the first thing on the second. */
  .stage.call-live .call-music-row { margin-top: var(--tl-s3); }
}

/* Three ways of saying the same thing, on the screen with the least room to
   say it. While a call is live the corner reads CONNECTED, the orb is counting
   and the partner's name is on screen, so on a short phone the status line
   stands down and gives its height to the strip below. It is back the moment
   the state is anything else - searching, connecting, reconnecting - because
   then it is the only thing saying it, and back on a tall phone at any time,
   where the room was never in question. */
@media (max-width: 979px) and (max-height: 800px) {
  .stage.call-live.call-connected .tl-call .status-text { display: none; }
}

/* A phone never gets a creative wider than a 320x50, so the card is sized to
   one and centred. It stays wide enough that ads.js, which picks the size from
   the slot's measured width, still measures well under its 484px threshold and
   asks for exactly that. */
@media (max-width: 600px) {
  .tl-call-ad-phone { width: min(100%, 372px); }
}

/* Short phones and landscape: something has to give, and it is never a control
   and never the ad's space - both of those are the point of the screen. The
   waveform goes instead. It is the nicest thing here and the only one that is
   purely decorative: the orb, the timer and the CONNECTED dot all say the same
   call is alive. */
@media (max-width: 979px) and (max-height: 659px) {
  .stage.call-live .tl-call-wave { display: none; }
  .stage.call-live .tl-call-reactions { margin-top: var(--tl-s1); }
}

/* ===========================================================================
 * 17. THE SHORTEST PHONES
 * ---------------------------------------------------------------------------
 * Section 14 sizes this screen to the viewport, but on a 640px-tall phone
 * even its minimums come to more than the room between the header and the
 * tab bar, and the main row - Next, the call button, Auto - ends up under
 * the bar again. Being unable to hang up without scrolling first is the one
 * failure this screen cannot have.
 *
 * So the last thing on it that is decoration rather than information stands
 * down: the rotating fun fact. It exists so that waiting does not feel like
 * dead air, and on a screen this short the waiting is the smaller problem.
 * The status line, the orb and the three controls all stay.
 * ======================================================================== */

@media (max-width: 979px) and (max-height: 680px) {
  .tl-call .search-ticker { display: none; }
  /* The strip keeps a clear separation from the controls so a thumb aiming
     for Hang Up cannot land on a creative - but on a screen this short that
     separation is the difference between an ad being seen and an ad being
     served to the underside of the tab bar. It is trimmed to the smallest
     gap that still reads as a break, not removed. */
  .tl-call-ad-phone { margin-top: var(--tl-s2); }
  /* The card's own bottom padding is the other half of that gap. The main
     row has its own breathing room built in, so this is padding around
     padding. */
  .stage .tl-call,
  .stage.call-live .tl-call { padding-bottom: var(--tl-s1); }
}

/* ===========================================================================
 * 18. THE CALL BUTTON, SLIMMED
 * ---------------------------------------------------------------------------
 * It read as a fat blob rather than a button. Three things were doing it,
 * and all three came from artwork drawn for a 140px orb on a landing page:
 *
 *   1. A 60px glow halo. The button's actual edge was the faintest part of
 *      it, so the eye read the whole soft cloud as the shape - which is what
 *      "fatty" is. A control needs a crisp edge; only the halo was ever
 *      saying "big", and the size already says that.
 *   2. A radial gradient lit from 35%/30%. That is how you shade a sphere,
 *      and a shaded sphere is a ball, not a button. Flat fills read as
 *      pressable; balls read as decoration.
 *   3. A 28%-white inset highlight across the top, which is the gloss that
 *      made the ball look inflated.
 *
 * It was also 92px against 60px neighbours - a 1.53x jump, which is what
 * made it read as swollen rather than important. It is now exactly the size
 * of the other two: colour carries the difference, so the size does not have
 * to. Sizing lives in section 9 with the rest of the row; what is left here
 * is only how the button is painted.
 * ======================================================================== */

/* Flat fills, and a shadow that sits under the button instead of around it -
   depth, not a halo. The three layers still cross-fade between modes exactly
   as they did; they just stop being spheres. */
.tl-call-main .call-main-btn::before,
.tl-call-main .call-main-btn::after,
.tl-call-main .call-main-btn .call-main-confirm-layer {
  box-shadow: none;
}
.tl-call-main .call-main-btn::before { background: var(--green); }
.tl-call-main .call-main-btn::after { background: var(--danger); }
.tl-call-main .call-main-btn .call-main-confirm-layer { background: #e08700; }

/* One soft drop shadow, tinted to whichever mode is showing, so the button
   sits on the screen rather than glowing through it. */
.tl-call-main .call-main-btn {
  box-shadow: 0 6px 18px color-mix(in srgb, var(--green) 38%, transparent);
  transition: box-shadow 0.35s ease, transform 0.12s ease;
}
.tl-call-main .call-main-btn.is-hangup {
  box-shadow: 0 6px 18px color-mix(in srgb, var(--danger) 40%, transparent);
}
.tl-call-main .call-main-btn.is-confirm {
  box-shadow: 0 6px 18px rgba(224, 135, 0, 0.4);
}
.tl-call-main .call-main-btn.is-loading {
  box-shadow: 0 6px 18px color-mix(in srgb, var(--green) 22%, transparent);
}
.tl-call-main .call-main-btn:active { transform: scale(0.96); }

/* The orb on the waiting screen breathes; a button someone is about to press
   should hold still. */
.tl-call-main .call-main-btn { animation: none; }


/* ===========================================================================
 * 19. BIG SCREENS
 * ---------------------------------------------------------------------------
 * The whole app was drawn at one size and then centred, so on a 1080p or
 * larger display it was a small, slightly cramped page marooned in the
 * middle of a lot of empty desktop. Two separate problems, and they need
 * different answers:
 *
 *   The furniture - nav and rail - should be at the edges. That is fixed
 *   above by letting the frame span the window.
 *
 *   The content should NOT simply stretch with it. A headline 1700px wide is
 *   unreadable, and a row of spirit animals spread across a whole monitor is
 *   worse than one you can take in. So the middle column caps its content at
 *   a comfortable measure and centres it in whatever room it has.
 *
 * Then everything scales: past 1600px the columns, the gutters and the type
 * all step up, because 15px body copy on a 27" screen is what "small" means.
 * ======================================================================== */

@media (min-width: 1180px) {
  /* The reading column. Wide enough for the hero and the picker to sit
     comfortably side by side with the rails, capped before the line length
     stops being readable, and centred in the space that is left. */
  .tl-frame > .stage {
    max-width: 1080px;
    margin-inline: auto;
    width: 100%;
  }
  .setup-panel { max-width: 100%; }
}

@media (min-width: 1600px) {
  .tl-frame {
    grid-template-columns: 268px minmax(0, 1fr) 340px;
    gap: var(--tl-s6);
    padding: var(--tl-s6) var(--tl-s6) 0;
  }
  .tl-frame.is-call { grid-template-columns: 268px minmax(0, 1fr); }

  /* Type and controls step up together - scaling one without the other is
     how a page ends up with big headings over small everything. */
  .tl-nav-item { font-size: 15.5px; min-height: 52px; }
  .tl-nav-icon { width: 24px; height: 24px; }
  .tl-nav-icon svg { width: 23px; height: 23px; }

  .setup-panel .setup-title { font-size: clamp(2.6rem, 2.9vw, 3.4rem); }
  .setup-panel .tl-hero-sub { font-size: 17px; max-width: 58ch; }

  .tl-action { min-height: 98px; padding: var(--tl-s5); }
  .tl-action-icon { width: 60px; height: 60px; }
  .tl-action-icon svg { width: 30px; height: 30px; }
  .tl-action-body strong { font-size: 20px; }
  .tl-action-body small { font-size: 14px; }

  .animal-picker-title { font-size: 17px; }
  .animal-picker-hint { font-size: 13.5px; }

  .tl-rail-card { padding: var(--tl-s5); }
  .tl-rail-head h2 { font-size: 15px; }
  .tl-rail-row-text strong { font-size: 14.5px; }
  .tl-rail-row-text small { font-size: 12.5px; }
  .tl-rail-row-icon { width: 38px; height: 38px; }
}

/* Past this the columns stop growing and only the middle does, so a very
   wide monitor gets more breathing room rather than a wider sidebar. */
@media (min-width: 2000px) {
  .tl-frame { padding-inline: clamp(var(--tl-s6), 4vw, 96px); }
  .tl-frame > .stage { max-width: 1180px; }
}

/* The ad and house cards inside the reading column centre themselves with
   `margin: auto` and cap at a width chosen for a 560px panel. Next to a
   left-aligned hero that reads as a stray box rather than part of the page,
   so on a desktop they line up with everything else and the creative centres
   inside the card instead of the card centring in the column. */
@media (min-width: 860px) {
  .tl-frame > .stage .ad-card {
    width: 100%;
    max-width: none;
    margin-inline: 0;
  }
  .tl-frame > .stage .ad-card [data-ad] {
    display: flex;
    align-items: center;
    justify-content: center;
  }
}

/* Everything below the call - the conversation guide, the notes, the music
   row - keeps the same measure and the same left edge as the call row above
   it. Without this the row is a centred 980px pair and the guide beneath it
   is full-column, so the two start at different places and the screen reads
   as two layouts stacked rather than one. */
@media (min-width: 980px) {
  .call-panel > *:not(.tl-call-row) {
    max-width: 980px;
    margin-inline: auto;
  }
}

/* ===========================================================================
 * 20. THE IN-CALL CHAT, AS A SHEET
 * ---------------------------------------------------------------------------
 * On a phone the chat during a call was a full-height drawer sliding in from
 * the right at 92vw. That is a page swap: the call disappears, and the thing
 * you were doing - talking to someone - stops being on screen at all. It
 * also slides sideways, which is the gesture language of navigation, so it
 * reads as "you have gone somewhere else".
 *
 * A chat over a live call is a box, not a destination. It comes up from the
 * bottom, stops well short of the top so the call is still visible above it,
 * and has the rounded top edge and the grab handle that say "this is a sheet
 * over the thing you were on, and it goes back down" - and it does go back
 * down, by dragging the handle, because that is the one gesture nobody has
 * to be taught.
 *
 * The call behind it is blurred rather than merely dimmed. Blur is what says
 * "still there, not in focus"; a flat dark wash says "gone", and the whole
 * point of a sheet is that the call has not gone anywhere.
 *
 * Desktop keeps the docked window: there it sits beside the call rather than
 * on top of it, which is the whole reason it works there.
 *
 * 767px, not 859: that is where style.css switches the panel to the docked
 * desktop window and hides the overlay, and a sheet with no backdrop was the
 * result of the two disagreeing.
 * ======================================================================== */

/* The grab handle exists only while the panel is a sheet; on desktop the
   panel is a docked window and there is nothing to drag. */
.side-panel.chat-side-panel .tl-sheet-grip { display: none; }

@media (max-width: 767px) {
  .side-panel.chat-side-panel .tl-sheet-grip { display: block; }

  /* The backdrop. Blurred, so the call reads as paused behind the box rather
     than closed. */
  #chatOverlay:not(.hidden) {
    background: rgba(8, 10, 20, 0.34);
    backdrop-filter: blur(9px) saturate(115%);
    -webkit-backdrop-filter: blur(9px) saturate(115%);
  }

  .side-panel.chat-side-panel {
    /* Anchored to the bottom edge, full width, rounded at the top only. */
    top: auto;
    bottom: 0;
    left: 0;
    right: 0;
    width: 100%;
    max-width: none;
    /* Shorter than it was: the top of the call - who you are talking to and
       for how long - has to stay on screen, or the sheet is just the old
       page swap with a rounded corner. */
    height: min(62dvh, 540px);
    padding: 0;
    gap: 0;
    border: 0;
    border-top: 1px solid var(--tl-line-strong);
    border-radius: var(--tl-r-lg) var(--tl-r-lg) 0 0;
    /* Up, not across: sideways is how this app moves between screens, and
       this is not a different screen. */
    transform: translateY(100%);
    /* Decelerating, so it arrives rather than stops - the same curve the
       message bubbles already use. */
    transition: transform 280ms cubic-bezier(0.2, 0.8, 0.2, 1);
    box-shadow: 0 -12px 40px rgba(0, 0, 0, 0.42);
    overflow: hidden;
    touch-action: pan-y;
  }
  .side-panel.chat-side-panel.open { transform: translateY(0); }

  /* While a finger is on it the sheet follows that finger exactly, so the
     transition has to be out of the way; it comes back for the snap. */
  .side-panel.chat-side-panel.is-dragging { transition: none; }

  /* The handle. It is what makes a sheet look draggable and, more usefully,
     what makes it obvious the call is still behind it. It is also the grab
     area, so it is a real element rather than a ::before - a pseudo-element
     cannot be dragged. */
  .side-panel.chat-side-panel .tl-sheet-grip {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    /* Tall enough to catch a thumb aimed at a 4px bar. */
    height: 26px;
    z-index: 3;
    cursor: grab;
  }
  .side-panel.chat-side-panel .tl-sheet-grip::before {
    content: "";
    position: absolute;
    top: 8px;
    left: 50%;
    width: 38px;
    height: 4px;
    transform: translateX(-50%);
    border-radius: 999px;
    background: color-mix(in srgb, var(--text) 26%, transparent);
  }
  .side-panel.chat-side-panel .side-panel-header {
    padding: var(--tl-s5) var(--tl-s4) var(--tl-s3);
  }
  .side-panel.chat-side-panel .chat-side-body {
    flex: 1;
    min-height: 0;
    padding-inline: var(--tl-s4);
    padding-bottom: calc(var(--tl-s3) + env(safe-area-inset-bottom, 0px));
  }
}

/* RTL slides sideways by default; a sheet has no side to come from, so the
   direction rules have nothing to flip here. */
[dir="rtl"] .side-panel.chat-side-panel { transform: translateY(100%); }
[dir="rtl"] .side-panel.chat-side-panel.open { transform: translateY(0); }
@media (min-width: 860px) {
  [dir="rtl"] .side-panel.chat-side-panel { transform: translateX(-100%); }
  [dir="rtl"] .side-panel.chat-side-panel.open { transform: translateX(0); }
}

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

/* The presence dot on the Settings profile row is absolutely positioned, and
   its container had no position of its own - so it anchored to the page
   instead of the avatar and rendered as a lone green dot pinned to the right
   edge of the screen, level with nothing. */
.tl-set-row-icon { position: relative; }
#settingsProfileAvatar { overflow: visible; }

/* Which phone call-screen ad runs, by how much height the screen has. The
   rectangle needs roughly 270px under the controls; below that the strip
   keeps the call controls clear of the tab bar, which always wins. */
.tl-call-ad-tall { display: none; }
@media (max-width: 979px) and (min-height: 760px) {
  .tl-call-ad-tall {
    /* Column, not row: `display: flex` alone laid the "Sponsored" label and
       the creative out side by side and shoved the rectangle off the right
       edge of the screen. */
    display: flex;
    flex-direction: column;
    align-items: center;
  }
  .tl-call-ad-short { display: none; }
  /* The rectangle is 250px against about 210px of room, so every pixel of
     the gap above it is a pixel of creative that falls behind the tab bar.
     It keeps enough of a break to stay clearly separate from the controls -
     a thumb going for Hang Up must never be able to land on an ad - and
     gives up the rest. */
  .tl-call-ad-phone { margin-top: var(--tl-s2); }
}

/* ===========================================================================
 * 21. THE CALL SCREEN'S EMPTY SPACE
 * ---------------------------------------------------------------------------
 * Three gaps, each individually reasonable, together about 136px on a 820px
 * phone - which is the difference between the ad below being 57% visible and
 * all of it being on screen.
 *
 *   The orb's box was 150px around a much smaller ball, so most of the space
 *   between the header and the status line was an empty ring.
 *
 *   The ticker's bottom margin (18px) and the control row's top margin (32px)
 *   stacked into a 50px hole between the icebreaker and the buttons.
 *
 *   The card's bottom padding (32px) and the ad's own top margin (24px)
 *   stacked into another 56px before the word SPONSORED.
 *
 * Nothing here shrinks a control or a line of text. It is all air between
 * them, and the ad is a better use of it. The separation above the ad is kept
 * - a thumb going for Hang Up must never land on a creative - it is just no
 * longer paid for twice.
 *
 * The selectors carry .stage.call-live because style.css sizes the orb with
 * it, and a phone size has to out-specify that rather than hope to out-order
 * it - the same trap that swallowed this block once already in a merge.
 * ======================================================================== */

@media (max-width: 979px) {
  .stage .tl-call,
  .stage.call-live .tl-call {
    padding-top: var(--tl-s2);
    padding-bottom: var(--tl-s3);
  }

  .stage .tl-call .orb-wrap,
  .stage.call-live .tl-call .orb-wrap {
    width: clamp(84px, 13dvh, 108px);
    height: clamp(84px, 13dvh, 108px);
    margin-bottom: var(--tl-s1);
  }
  .stage .tl-call .orb,
  .stage.call-live .tl-call .orb {
    width: clamp(72px, 11dvh, 94px);
    height: clamp(72px, 11dvh, 94px);
  }
  .stage .tl-call .orb-core,
  .stage.call-live .tl-call .orb-core {
    width: clamp(48px, 7.4dvh, 64px);
    height: clamp(48px, 7.4dvh, 64px);
  }

  .stage .tl-call .search-ticker,
  .stage.call-live .tl-call .search-ticker {
    margin-top: var(--tl-s2);
    margin-bottom: 0;
  }
  .stage .tl-call .connection-indicator,
  .stage.call-live .tl-call .connection-indicator { margin-top: var(--tl-s1); }

  .stage .tl-call-main,
  .stage.call-live .tl-call-main { margin-top: var(--tl-s4); }

  /* One gap above the ad, not the card's padding plus the ad's margin. It is
     wider than the space between any two controls, because a thumb heading
     for Hang Up must never be able to land on a creative - but it is paid
     for once rather than twice. */
  .tl-call-ad-phone { margin-top: var(--tl-s4); }
}
