/*
  Talkbox — one stylesheet.

  Named pieces: docs/DESIGN.md Kit.
  Pictures: brand/screens.html #kit-buttons #kit-fields #kit-page.
  A new piece goes in that Kit table and in a KIT section here.
  SCREEN sections only place those pieces. Never a second stylesheet.

  KIT (named pieces)
  1  Tokens
  2  Reset and app host
  3  Type
  4  Scroll
  5  Page — Header, Content, Footer
  6  Identity — Wordmark, Flag, Portrait, Edit
  7  Button — Primary, Gray, Red, Quiet, Ghost, Soft
  8  Field, Input, File drop, Checkbox
  9  Dropdown — Language, Region, Level, Voice, App language
  10 Marquee
  11 Layout — Pair, Stack, Kind
  12 Door
  13 Partner
  14 Goals
  15 Game
  16 Talk
  17 Pop and Tip          (later in the file, after Settings, for cascade)
  18 Icons
  19 Motion

  SCREEN (recipes that use the Kit)
  Home
  Create
  Settings
  Steps, Mic blocked, Ended
  Review
  Voice portal
  Rooms — Code, Seats, Lobby, Board, Monitor
*/

@import url("https://fonts.googleapis.com/css2?family=Montserrat:wght@500;600;700;800&display=swap");

@import url("../brand/logo/logo.css");


/* ==========================================================================
   KIT — Tokens
   ========================================================================== */

:root {
  /* Brand — Kahoot play purple. One hex per paint. */
  --tb-booth: #46178f;
  --tb-booth-deep: #2d0b5a;
  --tb-booth-soft: #7c4dcc;
  --tb-green: #26890c;
  --tb-ritual: #ffa602;
  --tb-blue: #1368ce;
  --tb-red: #e21b3c;
  --tb-red-deep: #9b1229;
  --tb-on-booth: #ffffff;
  --tb-ink: #222222;
  --tb-ritual-ink: #8a5100;
  --tb-ritual-deep: #c47a00;
  /* Quiet is opaque (white mixed into booth). Never give Lucide an
     alpha color: crossing strokes would show through. Hover of quiet
     text and icons is white. */
  --tb-quiet: color-mix(in srgb, var(--tb-on-booth) 60%, var(--tb-booth));
  --tb-quiet-hover: var(--tb-on-booth);
  --tb-dim: var(--tb-quiet);

  /* One hex, many names. Never assign the hex twice. */
  --tb-live: var(--tb-green);
  --tb-ok: var(--tb-green);
  --tb-yellow: var(--tb-ritual);
  --tb-care: var(--tb-blue);
  --tb-warn: var(--tb-red);
  --tb-purple: var(--tb-booth);
  --tb-purple-deep: var(--tb-booth-deep);
  --tb-purple-soft: var(--tb-booth-soft);
  --tb-on-purple: var(--tb-on-booth);
  --tb-surface: var(--tb-on-booth);

  /* Fills on purple. Alpha is for wash, never for icons. */
  --tb-wash: rgba(255, 255, 255, 0.12);
  --tb-wash-hover: rgba(255, 255, 255, 0.18);
  --tb-wash-on: rgba(255, 255, 255, 0.32);
  --tb-hair: rgba(255, 255, 255, 0.22);

  --tb-card-line: var(--tb-wash-hover);
  --tb-card-line-bright: var(--tb-hair);

  /* Type — one family, Montserrat. Six roles in .tb-type-*.
     A component composes a role. It does not invent a px size. */
  --tb-font: "Montserrat", system-ui, sans-serif;
  --tb-font-display: var(--tb-font);
  --tb-weight-medium: 500;
  --tb-weight-semibold: 600;
  --tb-weight-bold: 700;
  --tb-weight-black: 800;

  /* Label, Hint, Body, Loud, Title, Code.
     Title at 40 is the Sign in door wordmark, not a seventh role.
     Board Code is giant. Lobby's code stays --tb-btn-h beside Open board. */
  --tb-text-label: 12px;
  --tb-text-sm: 14px;
  --tb-text: 16px;
  --tb-text-lg: 20px;
  --tb-text-xl: 28px;
  --tb-text-2xl: 40px;
  --tb-text-code: 72px;
  --tb-text-lobby-code: clamp(4rem, 9vw, 6rem);

  --tb-track-tighter: -0.03em;
  --tb-track-tight: -0.02em;
  --tb-track-snug: -0.01em;
  --tb-track: 0;
  --tb-track-soft: 0.05em;
  --tb-track-wide: 0.06em;
  --tb-track-label: 0.08em;
  --tb-track-code: 0.12em;

  /* Radius — soft Kahoot-like; join controls use control radius */
  --tb-radius-sm: 4px;
  --tb-radius: 8px;
  --tb-radius-lg: 16px;
  --tb-radius-xl: 24px;
  --tb-radius-pill: 999px;
  --tb-control-radius: 12px;

  /* Jackbox-informed join column + tactile CTA */
  --tb-column: 420px;
  --tb-column-min: 320px;
  /* Teacher desk column. Home, Create, and Account share it so the
     inset matches. Lobby is its own 640. */
  --tb-home: 56rem;
  --tb-home-wash: var(--tb-wash);
  --tb-home-wash-hover: var(--tb-wash-hover);
  --tb-home-quiet: var(--tb-quiet);
  --tb-btn-h: 48px;
  /* The conversation partner's photo. One square on every profile:
     Practice, Waiting, Lobby, Monitor, Review, Create, Home. Not
     sized from the text beside it. Not a postage stamp. Not a hero.
     Tap on Practice still opens the larger popup view. */
  --tb-who-portrait: var(--tb-btn-h);
  /* Home rows (lesson). Hover wash lives on the row box, not in the
     gap. Space between rows is --tb-home-unit-gap. List window shows
     three full units. */
  --tb-home-unit-pad: var(--tb-space-3);
  --tb-home-unit-gap: var(--tb-label-gap);
  --tb-home-unit: calc(var(--tb-who-portrait) + (var(--tb-home-unit-pad) * 2));
  --tb-home-list-show: 3;
  --tb-home-list-max: calc(
    (var(--tb-home-list-show) * var(--tb-home-unit)) +
    ((var(--tb-home-list-show) - 1) * var(--tb-home-unit-gap))
  );
  /* Raised brick face. Face + --tb-brick-lip = --tb-btn-h so a yellow
     Join matches a wash field. Landing, Sign in, and Name use this same
     height. Do not invent a taller door control. Do not shrink type
     to match. */
  /* Lucide. Default in .tb-btn; a component may pick sm. Never a text glyph. */
  --tb-icon: 20px;
  --tb-icon-sm: 16px;
  /* Create lesson name. Empty field keeps Untitled lesson's width. */
  --tb-edit-min: 8em;
  /* Additional instructions. One control taller than a default
     growing textarea (12em is four --tb-btn-h). Scene notes run longer. */
  --tb-notes-max: calc(var(--tb-btn-h) * 5);
  /* Pop fade. See-through so the current screen stays visible.
     Never a solid plate. */
  --tb-pop-veil: color-mix(in srgb, var(--tb-booth-deep) 42%, transparent);
  /* Create Language nested dialects. Same booth-deep family as the
     open menu: base is --tb-booth-deep, the expanded parent row is a
     wash step, this well is one more step lighter. Never --tb-booth
     (that matches the page and the menu bottom merges). */
  --tb-lang-well: color-mix(in srgb, var(--tb-on-booth) 22%, var(--tb-booth-deep));
  /* Practice talk bar: Hold to talk and type compose share one height so
     swapping modes does not jump the bottom of the screen. */
  --tb-talk-bar-h: 56px;
  /* Tail hang. 10px outside the bubble, 2px stem overlaps the fill.
     Partner and teacher hang left. Student hangs right. The talk
     column reserves this on both sides so leftover air to each tail
     matches. */
  --tb-bubble-tail: 10px;
  --tb-bubble-tail-stem: 2px;
  /* Practice is a phone call: Goals, then the circle and name as one
     unit, centered in the leftover between Goals and talk, then a talk
     band (--tb-talk-min to --tb-talk-max) above Hold to talk. The
     circle scales down so talk keeps its floor. Section-gap under the
     person, then an alpha fade at the top of talk, not a brand color.
     Talk has no rail. Create and Home keep the smaller rounded-square
     portrait. */
  /* Chat circle. Slightly under 4 bricks so the call face is not
     the whole leftover band. Talk still shrinks toward who-call-min. */
  --tb-who-call: calc(var(--tb-btn-h) * 3.75);
  /* Chat: talk keeps this floor. The call circle shrinks toward
     --tb-who-call-min so the band is never a sliver. */
  --tb-who-call-min: calc(var(--tb-btn-h) * 2);
  /* Chat band. Lower than leftover-fill so the call circle can sit in
     the air between Goals and the top of talk. Seven bricks max: about
     five recent bubbles, then scroll. Five bricks min: about three
     bubbles, then the circle scales down. */
  --tb-talk-min: calc(var(--tb-btn-h) * 5);
  --tb-talk-max: calc(var(--tb-btn-h) * 7);
  /* Board QR. 320px square. Quiet zone is in the SVG. */
  --tb-board-qr: 320px;
  /* Class Review goal letters. */
  --tb-key-a: var(--tb-yellow);
  --tb-key-b: var(--tb-blue);
  --tb-key-c: var(--tb-green);
  --tb-key-d: var(--tb-red);
  --tb-key-e: var(--tb-booth-soft);
  --tb-key-miss: var(--tb-quiet);
  /* Lobby column. */
  --tb-lobby: 640px;
  --tb-look-fade: calc(var(--tb-section-gap) * 2);
  --tb-look-fade-mask: linear-gradient(to bottom, rgb(0 0 0 / 0) 0, rgb(0 0 0) var(--tb-look-fade));
  /* Quiet product scrollbar. Hint-color thumb, no track, no
     Windows stepper. --tb-rail is the spec overlay, not this.
     A hair wider than space-1 so the rail is easier to see.
     Inset keeps the thumb inside rounded menus: with arrows gone the
     thumb was running past the top and bottom of the panel.
     Page scroll thumbs sit in the right --tb-page-pad, not an extra
     end gutter that made left and right unequal. Talk has no rail. */
  --tb-scroll: 6px;
  --tb-scroll-inset: var(--tb-control-radius);
  --tb-scroll-gutter: calc(var(--tb-scroll) + var(--tb-space-1));
  --tb-rail: 12px;
  --tb-scroll-thumb: var(--tb-quiet);
  --tb-topbar-h: 48px;
  /* 3D brick lip. Box-shadow does not take layout, so chrome that
     contains a brick must reserve this in the content flow. Never
     fake it by making topbar/dock padding-top != padding-bottom. */
  --tb-brick-lip: 5px;
  --tb-shadow-brick: 0 var(--tb-brick-lip) 0 0 var(--tb-booth-deep);
  --tb-shadow-brick-press: 0 2px 0 0 var(--tb-booth-deep);
  --tb-shadow-brick-ritual: 0 var(--tb-brick-lip) 0 0 var(--tb-ritual-deep);
  --tb-shadow-brick-ritual-press: 0 2px 0 0 var(--tb-ritual-deep);
  --tb-shadow-brick-red: 0 var(--tb-brick-lip) 0 0 var(--tb-red-deep);
  --tb-shadow-brick-red-press: 0 2px 0 0 var(--tb-red-deep);
  /* Gray brick. Same grammar as ritual: mix the system's light with its
     dark. Not a white plate, not the disabled fade. Lip is a deeper mix.
     Face stays light so Settings sits with yellow, not a muddy mid-gray. */
  --tb-gray: color-mix(in srgb, var(--tb-on-booth) 84%, var(--tb-ink));
  --tb-gray-deep: color-mix(in srgb, var(--tb-on-booth) 54%, var(--tb-ink));
  --tb-shadow-brick-gray: 0 var(--tb-brick-lip) 0 0 var(--tb-gray-deep);
  --tb-shadow-brick-gray-press: 0 2px 0 0 var(--tb-gray-deep);
  /* One disabled grammar for every tb-btn: keep live colors, drop the
     brick, fade. Never Kahoot grey. Never a white plate. */
  --tb-btn-disabled-opacity: 0.42;

  /* Space (8px grid) */
  --tb-space-1: 4px;
  --tb-space-2: 8px;
  --tb-space-3: 12px;
  --tb-space-4: 16px;
  /* One page inset. Home set it; every product page uses it. Horizontal
     inset stays this value on both sides. Do not invent a second page
     padding system. Do not add a scroll gutter only on the end: that
     made left and right look unequal. Chrome-pad follows this. */
  --tb-page-pad: var(--tb-space-4);
  /* Even top AND bottom on the topbar. Match the page inset so Talkbox is
     not glued to the phone edge. Do not shrink this to hug the dock.
     Do not park a section-gap under the topbar: that extra gap is what
     read as fat bottom pad on the header. */
  --tb-chrome-pad: var(--tb-page-pad);
  /* Dock hugs its content. Even top and bottom, tighter than the topbar.
     Never split padding-top from padding-bottom. Never alias this to
     chrome-pad: shrinking the header's top inset was not asked. */
  --tb-dock-pad: var(--tb-space-2);
  /* Space between labeled body blocks. Bigger than the 12px inside a group. Not the air under
     Talkbox or above the dock. */
  --tb-section-gap: var(--tb-space-4);
  /* Label sits with its control. Closer than the gap between fields,
     not glued to the input. */
  --tb-label-gap: var(--tb-space-2);
  /* Tight. Stacked plates in an open menu. Smaller than the label gap.
     Hover and selected stay apart without 8px of empty purple. */
  --tb-tight-gap: var(--tb-space-1);

  /* Motion. One duration for page animation: Kind slide, jump-to-latest,
     bubble tap tilt, empty Speaking partner example swipe.
     Do not invent a second speed. Loops: partner typing dots (the line
     is still loading) and the empty partner example cycle. */
  --tb-move: 180ms;
  --tb-move-ease: ease-out;

  /* Hold to talk visualizer. The mic analyser writes --tb-ptt-level (0–1)
     and per-bar --h on .tb-ptt__viz. Speech punches. Quiet sits still
     at a short floor. ~19 bars make a wider viz in the same brick. */
  --tb-ptt-level: 0;
  --tb-ptt-viz-h: 28px;
  --tb-ptt-bar-w: 5px;
  --tb-ptt-bar-gap: 4px;

  /* Call profile visualizer. Lines start at the portrait's diameter
     and grow out. Pad is room for a full punch. The ring is masked
     to that circle so nothing sits on or behind the photo. */
  --tb-who-viz-min: 8px;
  --tb-who-viz-max: 22px;
  --tb-who-viz-pad: var(--tb-who-viz-max);
  --tb-who-viz-tick-w: 3px;
}


/* ==========================================================================
   KIT — Reset and app host
   ========================================================================== */

/* Reset for Talkbox surfaces */
.tb-root {
  box-sizing: border-box;
  font-family: var(--tb-font);
  color: var(--tb-ink);
  -webkit-font-smoothing: antialiased;
}

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

/* Live product: html is the one viewport box (svh = toolbars visible).
   Children are 100% of that, never a second 100svh that clips Hold to
   talk. min-height: 0 on the page let iOS collapse the product to
   nothing and paint the gallery dark canvas. Do not add more pad;
   chrome-pad is already the only air under the status bar. */
html.tb-app,
html.tb-app body {
  height: 100svh;
  max-height: 100svh;
  min-height: 100svh;
  overflow: hidden;
  background: var(--tb-booth);
  color-scheme: light;
}


/* ==========================================================================
   KIT — Type
   ========================================================================== */
/* Type roles. Six only: Label, Hint, Body, Loud, Title, Code.
   Semantic aliases and live components compose these. A later rule
   may set color, wrap, or layout. It does not invent a size.
   Leftover aliases stay grouped so old class names do not explode. */
.tb-type-label,.tb-label,.tb-ai__k {
  font-family: var(--tb-font);
  font-size: var(--tb-text-label);
  font-weight: var(--tb-weight-black);
  letter-spacing: var(--tb-track-label);
  text-transform: uppercase;
  line-height: 1;
}

.tb-type-hint,.tb-sub-on,.tb-note,.tb-practice__who-blurb,.tb-practice__who-level,.tb-voice-pick__tags,.tb-lang-pick__sub,.tb-cefr-pick__name,.tb-voice-pick .tb-lang-pick__btn .tb-voice-pick__gender,.tb-goals__add,.tb-who__new,.tb-voice__redo,.tb-msg__who,.tb-home__region-name,.tb-home__import-help,.tb-lobby__time,.tb-lobby__count,.tb-board-count,.tb-landing__code,.tb-voice-note,.tb-topbar .tb-btn--ghost {
  font-family: var(--tb-font);
  font-size: var(--tb-text-sm);
  font-weight: var(--tb-weight-medium);
  letter-spacing: var(--tb-track);
}

.tb-type-body {
  font-family: var(--tb-font);
  font-size: var(--tb-text);
  font-weight: var(--tb-weight-medium);
  letter-spacing: var(--tb-track);
}

.tb-type-loud,
.tb-board-url {
  margin: 0;
  font-family: var(--tb-font);
  font-size: var(--tb-text-lg);
  font-weight: var(--tb-weight-black);
  letter-spacing: var(--tb-track-tight);
  line-height: 1;
  color: var(--tb-on-booth);
}

.tb-type-title,.tb-screen-title,.tb-lockup,.tb-logo,.tb-edit {
  font-family: var(--tb-font);
  font-size: var(--tb-text-xl);
  font-weight: var(--tb-weight-black);
  letter-spacing: var(--tb-track-tight);
}

.tb-type-code,
.tb-code {
  font-family: var(--tb-font);
  font-size: var(--tb-text-code);
  font-weight: var(--tb-weight-black);
  letter-spacing: var(--tb-track-code);
  line-height: 1;
}

.tb-sub-on,.tb-note {
  margin: 0;
  color: var(--tb-quiet);
}

.tb-ai__month .tb-sub-on {
  margin: 0;
}

.tb-tip--word .tb-sub-on {
  line-height: 1.2;
}

.tb-ended .tb-screen-title,
.tb-ended .tb-sub-on {
  margin: 0;
  line-height: 1;
}

.tb-monitor-confirm .tb-sub-on {
  margin: 0;
}

.tb-voice-start .tb-sub-on { text-wrap: balance; }

.tb-voice__top .tb-sub-on {
  margin: 0;
}

/* Hug glyphs so the section gap is the air you see, not leftover
   line-box padding above/below the words. */
.tb-hold .tb-screen-title,
.tb-hold .tb-sub-on {
  margin: 0;
  line-height: 1;
}

.tb-restart .tb-screen-title,
.tb-restart .tb-sub-on {
  margin: 0;
  line-height: 1;
}

.tb-restart .tb-sub-on {
  text-wrap: balance;
}


/* ==========================================================================
   KIT — Scroll
   ========================================================================== */

/* Quiet product scrollbar. Hint color (`--tb-quiet`), a hair wider
   than space-1. Transparent track. No Windows stepper arrows. Not the spec rail.
   Chromium: only ::-webkit-scrollbar. scrollbar-width / scrollbar-color
   there paint the native Windows bar (arrows at both ends) and ignore
   the button hide. Firefox does not paint ::-webkit-scrollbar, so it
   gets the standard properties in the moz block.
   Track margin is the old arrow slot: the thumb stops inside the
   rounded panel instead of running past the top and bottom. */
*::-webkit-scrollbar {
  width: var(--tb-scroll);
  height: var(--tb-scroll);
}

*::-webkit-scrollbar-track {
  background: transparent;
  margin-block: var(--tb-scroll-inset);
  margin-inline: var(--tb-scroll-inset);
}

*::-webkit-scrollbar-thumb {
  background: var(--tb-scroll-thumb);
  border-radius: var(--tb-radius-pill);
}

*::-webkit-scrollbar-button,
*::-webkit-scrollbar-button:start:decrement,
*::-webkit-scrollbar-button:end:increment,
*::-webkit-scrollbar-button:vertical:start:increment,
*::-webkit-scrollbar-button:vertical:end:decrement,
*::-webkit-scrollbar-button:horizontal:start:increment,
*::-webkit-scrollbar-button:horizontal:end:decrement {
  display: none;
  width: 0;
  height: 0;
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  border: 0;
}

*::-webkit-scrollbar-corner {
  background: transparent;
}

/* Hint thumb in a reserved gutter, not on the rows. Spec `tb-rail` is
   a 12px docs column. Product must not copy that width. Classic Windows
   ::-webkit-scrollbar occupies layout; a wrapped pane hides that bar
   and this overlay sits in --tb-scroll-gutter (page scrollers) or on
   dropdown option plates (Search stays even; do not gutter those).
   Talk has no rail. Do not set position:relative on
   `.tb-drop > .tb-rail-host`: that host is already the absolute panel. */
.tb-page .tb-rail,
.tb-landing .tb-rail,
.tb-drop .tb-rail,
.tb-lang-pick .tb-rail {
  position: absolute;
  top: var(--tb-scroll-inset);
  bottom: var(--tb-scroll-inset);
  right: 0;
  width: var(--tb-scroll);
  z-index: 2;
  cursor: pointer;
  pointer-events: auto;
}

/* Page columns: thumb lives in the page's right inset (--tb-page-pad),
   a little air in from the phone edge, never on goal X / text. Content
   keeps equal page-pad on left and right. Menus keep right: 0 so the
   thumb stays on the option plates. */
.tb-page--fill > .tb-rail-host > .tb-rail,
.tb-page > .tb-create-col ~ .tb-rail,
.tb-page > .tb-home ~ .tb-rail,
.tb-page > .tb-settings ~ .tb-rail,
.tb-page > .tb-ai ~ .tb-rail,
.tb-page > .tb-body ~ .tb-rail {
  right: calc(-1 * var(--tb-page-pad) + var(--tb-space-1));
}

.tb-page .tb-rail[hidden],
.tb-landing .tb-rail[hidden],
.tb-drop .tb-rail[hidden],
.tb-lang-pick .tb-rail[hidden] {
  display: none;
}

.tb-page .tb-rail__thumb,
.tb-landing .tb-rail__thumb,
.tb-drop .tb-rail__thumb,
.tb-lang-pick .tb-rail__thumb {
  position: absolute;
  left: 0;
  width: 100%;
  background: var(--tb-scroll-thumb);
  border-radius: var(--tb-radius-pill);
}

.tb-rail-pane::-webkit-scrollbar {
  display: none;
  width: 0;
  height: 0;
}

/* Page scroller + overlay host. JS may wrap the column; keep the fill
   flex so Create / Home still eat the leftover above the dock. */
.tb-page--fill > .tb-rail-host {
  position: relative;
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.tb-page--fill > .tb-rail-host > .tb-rail-pane {
  flex: 1;
  min-height: 0;
}

.tb-create-col::-webkit-scrollbar,
.tb-home::-webkit-scrollbar,
.tb-settings::-webkit-scrollbar,
.tb-ai::-webkit-scrollbar,
.tb-review-page::-webkit-scrollbar,
.tb-notice::-webkit-scrollbar,
.tb-lobby__scroll::-webkit-scrollbar,
.tb-monitor-scroll::-webkit-scrollbar {
  display: none;
  width: 0;
  height: 0;
}

@supports (-moz-appearance: none) {
  * {
    scrollbar-width: thin;
    scrollbar-color: var(--tb-scroll-thumb) transparent;
  }
  .tb-practice .tb-talk,
  .tb-rail-pane {
    scrollbar-width: none;
  }

}

.tb-field > .tb-rail-host {
  width: 100%;
}

.tb-rail-host:has(> .tb-input) > .tb-rail,
.tb-rail-host:has(> .tb-textarea) > .tb-rail,
.tb-rail-host:has(> .tb-character__bio) > .tb-rail {
  display: none;
}

/* Overlay so Create / Lobby keep their column height. The rail-host is
   this same panel once the menu is attached. The pane inside scrolls;
   the Hint thumb sits on that pane, not in a reserved column. */
.tb-drop__menu,
.tb-lang-pick__menu,
.tb-drop > .tb-rail-host,
.tb-lang-pick > .tb-rail-host {
  display: none;
  position: absolute;
  left: 0;
  right: 0;
  top: calc(100% + var(--tb-space-2));
  z-index: 50;
  list-style: none;
  margin: 0;
  padding: var(--tb-tight-gap);
  max-height: 240px;
  overflow-x: hidden;
  overflow-y: auto;
  /* Overlay the Hint rail. `stable` reserved a fat UA thin-bar
     column (wider than --tb-scroll) so Search and option plates sat
     tight on the left and open on the right. */
  scrollbar-gutter: auto;
  border-radius: var(--tb-control-radius);
  background: var(--tb-booth-deep);
  box-shadow: none;
}

.tb-voice-pick .tb-lang-pick__menu,
.tb-voice-pick > .tb-rail-host {
  left: auto;
  right: 0;
  width: max-content;
  min-width: 7.5rem;
  max-width: calc(100cqw - var(--tb-page-pad) * 2);
  max-height: 240px;
  justify-items: start;
}

/* Level has six rows. Let them fit so this menu does not scroll. */
.tb-cefr-pick .tb-lang-pick__menu,
.tb-cefr-pick > .tb-rail-host {
  max-height: none;
}

.tb-drop.is-open > .tb-rail-host,
.tb-lang-pick.is-open > .tb-rail-host {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  /* Even panel pad. A 0 on the right shoved option plates toward
     the left and left a fat empty column for the rail. */
  padding: var(--tb-tight-gap);
}

/* Open Language is sized once from language names AND dialect names
   (collapsed wells still measure). It must not widen when a nested
   well opens. min-width 100% keeps the closed control's half. On a
   phone it may grow right past that half so Southern France fits.
   Cap to the product page (`tb-screen`), never the gallery `100vw`. */
.tb-lang-pick:not(.tb-voice-pick):not(.tb-cefr-pick):not(.tb-dialect-pick):not(.tb-app-lang):not(.tb-class-lang):not(.tb-voice-lang-pick) > .tb-lang-pick__menu,
.tb-lang-pick:not(.tb-voice-pick):not(.tb-cefr-pick):not(.tb-dialect-pick):not(.tb-app-lang):not(.tb-class-lang):not(.tb-voice-lang-pick) > .tb-rail-host {
  right: auto;
  width: max-content;
  min-width: 100%;
  max-width: calc(100cqw - var(--tb-page-pad) * 2);
}

.tb-dialect-pick > .tb-lang-pick__menu,
.tb-dialect-pick > .tb-rail-host {
  right: auto;
  width: max-content;
  min-width: 100%;
  max-width: calc(100cqw - var(--tb-page-pad) * 2);
}

.tb-practice .tb-talk::-webkit-scrollbar {
  width: 0;
  height: 0;
  display: none;
}

/* If the quiet rail wraps the menu, keep the same pad and grid.
   Shared drop rail-host rules use 4px and flex, which cramped
   the heading and made Search's air below look like a second gap. */
.tb-app-lang > .tb-rail-host,
.tb-app-lang.is-open > .tb-rail-host {
  max-height: 24rem;
  padding: var(--tb-drop-pad) var(--tb-tight-gap);
  overflow: hidden;
}

.tb-landing .tb-app-lang > .tb-rail-host {
  max-height: min(24rem, calc(100cqi - var(--tb-chrome-pad) - (var(--tb-space-4) * 4)));
}

/* Overlay host for the native-language list only. Heading and Search
   stay out of the rail. The thumb sits on the option plates. */
.tb-app-lang .tb-drop__menu > .tb-rail-host {
  min-height: 0;
  position: relative;
  display: block;
  overflow: hidden;
  padding: 0;
  max-height: none;
}

.tb-body::-webkit-scrollbar {
  display: none;
  width: 0;
  height: 0;
}

.tb-topbar__end .tb-class-lang > .tb-lang-pick__menu,
.tb-topbar__end .tb-class-lang > .tb-rail-host {
  left: auto;
  right: 0;
  width: max-content;
  min-width: 12rem;
  max-width: calc(100cqw - var(--tb-page-pad) * 2);
  /* Shared lang-pick cap is 240px. Home Language rows are two lines
     (name + people). Use the same 24rem cap as App language so more of
     the list shows; keepInHost still keeps it in the phone. The list
     is the scrollport (Hint rail). */
  max-height: 24rem;
}

.tb-character .tb-voice-pick--fact .tb-lang-pick__menu,
.tb-character .tb-voice-pick--fact > .tb-rail-host {
  left: auto;
  right: 0;
  top: calc(100% + var(--tb-space-2));
  bottom: auto;
  width: max-content;
  min-width: 7.5rem;
  max-width: calc(100cqw - var(--tb-page-pad) * 2);
  z-index: 80;
}

.tb-voice-lang-pick > .tb-lang-pick__menu,
.tb-voice-lang-pick > .tb-rail-host {
  right: auto;
  width: max-content;
  min-width: 100%;
  max-width: calc(100cqw - var(--tb-page-pad) * 2);
}


/* ==========================================================================
   KIT — Page (Header, Content, Footer)
   ========================================================================== */

/* Pages */
.tb-page {
  min-height: 100%;
  padding: var(--tb-page-pad);
  font-family: var(--tb-font);
  /* Overlay host. Menus, tips, and pops pin to this page (the phone),
     not the gallery window. container-name is for cqw caps. */
  position: relative;
  container-type: inline-size;
  container-name: tb-screen;
}

.tb-page--play {
  background: var(--tb-purple);
  color: var(--tb-on-purple);
}

.tb-center {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--tb-section-gap);
  min-height: 100%;
}

/* Phone-column constrain (Jackbox join pattern) */
.tb-constrain {
  width: 100%;
  max-width: var(--tb-column);
  min-width: min(100%, var(--tb-column-min));
  margin: 0 auto;
  padding: 0;
}

/* Product screens (moved from brand/screens.html) */
.tb-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--tb-space-3);
  min-height: 0;
  /* padding-block keeps top and bottom equal. Do not split.
     No margin under the bar: same-color section-gap here looked like
     extra bottom pad on the header. */
  padding-block: var(--tb-chrome-pad);
  margin-bottom: 0;
  flex-shrink: 0;
}

/* On single-column screens the lockup shares the form's column so the page
   reads as one block of content, not a logo adrift from a centred form. */
.tb-topbar--column {
  max-width: var(--tb-home);
  margin-left: auto;
  margin-right: auto;
  width: 100%;
}

.tb-page:has(.tb-home) .tb-topbar,
.tb-page:has(.tb-create) .tb-topbar,
.tb-page:has(.tb-class-lang) .tb-topbar {
  position: relative;
  z-index: 20;
  overflow: visible;
}

.tb-topbar__end {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--tb-space-3);
  flex-shrink: 0;
}

.tb-page--fill {
  /* Hug the parent frame. A 560px floor left slack on a phone whose
     parent height failed to resolve, and padded the visual viewport
     with leftover purple. Gallery chrome sizes the phone frame.
     Live app (html.tb-app) hugs svh below. */
  min-height: 0;
  height: 100%;
  max-height: 100%;
  display: flex;
  flex-direction: column;
}

.tb-page--fill > [data-home] {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.tb-page--fill > [data-practice],
.tb-page--fill > .tb-practice {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.tb-page--fill > .tb-center {
  flex: 1;
  min-height: 0;
}

.tb-page--fill > .tb-body {
  flex: 1;
  min-height: 0;
}

html.tb-app .tb-page--fill {
  min-height: 100%;
  height: 100%;
  max-height: 100%;
}

/* Mobile thumb zone. Sticky to the bottom of the phone frame. Create,
   Review Done, Settings Back. Practice already uses the talk bar. */
/* A page that carries a topbar hands its top inset
   to that chrome, so chrome-pad is the only air above Talkbox. Do not stack
   page-pad on top of it. */
.tb-page:has(.tb-topbar) {
  padding-top: 0;
}

/* A page that carries a bottom pane hands its bottom inset to the pane, so
   the pane lands on the bottom edge of the phone instead of floating a page
   pad above it. Sticky cannot escape its containing block, so a negative
   bottom margin here does nothing: the padding has to go. */
.tb-page:has(.tb-dock),
.tb-page:has(.tb-talk-bar) {
  padding-bottom: 0;
}

.tb-dock {
  position: sticky;
  bottom: 0;
  z-index: 5;
  flex-shrink: 0;
  margin-top: auto;
  margin-left: calc(-1 * var(--tb-page-pad));
  margin-right: calc(-1 * var(--tb-page-pad));
  margin-bottom: 0;
  width: auto;
  max-width: none;
  display: flex;
  flex-direction: column;
  gap: var(--tb-space-2);
  /* Equal top and bottom. Brick lips live on the button (margin below
     the face), not extra dock pad. Do not paint a line on the tab. */
  padding-block: var(--tb-dock-pad);
  padding-inline: var(--tb-page-pad);
  isolation: isolate;
  background: var(--tb-booth);
  border: none;
  outline: none;
  /* Booth cover at the sticky/clip edge. Not a yellow line. Create
     lesson's ritual lip can composite as a 1px fringe through overflow. */
  box-shadow: 0 -1px 0 var(--tb-booth);
  overflow: visible;
}

.tb-dock > [data-times]:empty {
  display: none;
}

/* Equal paths: both SSO buttons are the same quiet brick as Open board.
   Mark and word sit as one centered group. Quiet has no lip. Height is
   --tb-btn-h, same as Join on Landing. */
/* Body. The scrolling column between header and footer.
   Live Home / Create / Settings / AI / Review / Notice names stay as
   aliases. New screens use this class. */
.tb-body {
  max-width: var(--tb-home);
  margin: 0 auto;
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: var(--tb-section-gap);
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: none;
}

#create .tb-dock {
  max-width: none;
}

#home .tb-dock,
[data-home] ~ .tb-dock,
.tb-pop-host:has(.tb-home) > .tb-dock {
  max-width: none;
  margin-inline: calc(-1 * var(--tb-page-pad));
}

/* Kill the ritual hairline on Home's dock. Opaque booth. */
#home .tb-dock,
.tb-pop-host .tb-dock,
.tb-page--fill .tb-dock {
  isolation: isolate;
  overflow: visible;
  background: var(--tb-booth);
  border: none;
  border-top: none;
  outline: none;
  box-shadow: 0 -1px 0 var(--tb-booth);
}

#home .tb-dock::before,
.tb-pop-host:has(.tb-home) > .tb-dock::before,
.tb-page--fill:has(.tb-home) .tb-dock::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 2px;
  background: var(--tb-booth);
  pointer-events: none;
  z-index: 2;
}

/* Home and Create Language: flag + Italian as white text. No wash field,
   no chevron. Click still opens the menu. */
.tb-topbar__end:has(.tb-class-lang) {
  gap: var(--tb-space-2);
}

.tb-pop__card > .tb-screen-title {
  margin-bottom: var(--tb-space-4);
}

[data-pop="import"] .tb-pop__card > .tb-screen-title {
  margin-bottom: 0;
}

.tb-screen-title {
  margin: 0;
  color: var(--tb-on-booth);
}

.tb-review-head .tb-screen-title {
  line-height: 1.02;
}

/* Home must not clip Create lesson into a 1px ritual hairline at the
   sticky dock. The pop still covers the page; overflow hidden returns
   while a pop is open. */
.tb-page:has(.tb-home).tb-pop-host {
  overflow: visible;
}

.tb-page:has(.tb-home).tb-pop-host:has(.tb-pop:not([hidden])) {
  overflow: hidden;
}

.tb-pop-host .tb-create-col,
.tb-pop-host .tb-home,
.tb-pop-host .tb-settings,
.tb-pop-host .tb-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}

.tb-blocked .tb-screen-title {
  margin: 0;
  line-height: 1;
}

/* Isolated voice portal. Not classroom chrome. Open is Welcome.
   Room and Read are a line plus Hold to talk. Redo is Hint.
   Do not reuse .tb-voice: that class is the Create Hear chip. */
.tb-landing .tb-screen-title {
  margin: 0;
}

/* Stack helpers */
.tb-topbar--lobby {
  max-width: 640px;
}

.tb-page--board-fill {
  min-height: 320px;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  gap: var(--tb-section-gap);
  text-align: center;
  padding: var(--tb-page-pad);
  overflow: hidden;
}

.tb-notice > .tb-screen-title {
  margin: 0;
}

.tb-page:has(.tb-monitor-head) {
  padding-top: 0;
}


/* ==========================================================================
   KIT — Identity (Wordmark, Flag, Portrait, Edit)
   ========================================================================== */

/* Brand wordmark. Title role. Color is booth on light identity pages. */
.tb-logo {
  margin: 0;
  color: var(--tb-booth);
}

/* HTML lockup — Title 28. Product chrome is the Talkbox wordmark only (no PNG mark).
   No Beta tag on the word. */
.tb-lockup {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0;
  margin: 0;
  line-height: 1;
}

.tb-lockup__name {
  font-size: 1em;
  font-weight: inherit;
  letter-spacing: inherit;
  line-height: 1;
  /* Trim the unused descender well so chrome-pad above the caps
     matches chrome-pad below the baseline. Talkbox has no descenders. */
  text-box: trim-both cap alphabetic;
  text-box-trim: trim-both;
  text-box-edge: cap alphabetic;
  color: var(--tb-booth);
}

.tb-lockup--on-play .tb-lockup__name {
  color: var(--tb-on-booth);
}

/* Title at 40 on the Sign in door. Not a seventh role. */
.tb-lockup--lg {
  font-size: var(--tb-text-2xl);
}

/* Teacher chrome: the wordmark is the way back to Home. Same type, no underline. */
a.tb-lockup {
  text-decoration: none;
  color: inherit;
  cursor: pointer;
}

a.tb-lockup:focus-visible,
a.tb-lockup__name:focus-visible {
  outline: 2px solid var(--tb-on-booth);
  outline-offset: 4px;
}

a.tb-lockup__name {
  text-decoration: none;
  color: inherit;
  cursor: pointer;
}

/* One dropdown: `.tb-drop`. Language, Level, and Voice all use it.
   `.tb-lang-pick` is the same control (JS still looks for that class).
   Never a native select. Flag images, not emoji. Menu is a control list
   on play purple, not a card. Overflow uses the Talkbox rail. Never a
   Windows native bar (those arrows are the classic stepper).
   Closed Language is a normal wash form control (--tb-wash), same as
   the other tb-lang-pick__btn faces. Hover and open: --tb-wash-hover.
   Never selected yellow, never a white plate. */
/* The flag is the picture. Never a frame. Never a white plate behind it.
   Gallery html uses color-scheme: dark; Chromium then paints a white
   canvas on SVG <img>. Banned. !important so a later rule cannot put a
   border, outline, radius, drop-shadow, or fill back on.
   Chromium also AA-rasterizes SVG <img> edges against white. Do not
   clip-path: inset (that AA-mixes flag color with purple into a light
   halo). Crop that fringe by painting the flag larger than the slot
   with a negative margin so layout stays 28x21 (or 18x14 / 20x15).
   1px was not enough: France still showed a white hairline. Crop 2px.
   Immediate parents overflow:hidden clip the extra. White in the flag
   artwork (Japan, France center stripe) stays. A plate around the
   picture is banned.
   Slot is 4:3 (28x21). Flag SVGs must be 4:3 art. A 19:10 US or 1:2 UK
   plus object-fit:cover crops the canton and turns stars into mush. */
img.tb-flag,
.tb-flag,
img[src*="/flags/"] {
  --tb-flag-w: 28px;
  --tb-flag-h: 21px;
  --tb-flag-crop: 2px;
  display: block;
  width: calc(var(--tb-flag-w) + 2 * var(--tb-flag-crop));
  height: calc(var(--tb-flag-h) + 2 * var(--tb-flag-crop));
  margin: calc(var(--tb-flag-crop) * -1);
  object-fit: cover;
  object-position: center;
  flex-shrink: 0;
  overflow: hidden;
  border: none !important;
  outline: none !important;
  box-shadow: none !important;
  border-radius: 0 !important;
  background: none !important;
  background-color: transparent !important;
  background-image: none !important;
  filter: none !important;
  color-scheme: only light;
  forced-color-adjust: none;
}

/* Clip the 2px overpaint on the flag itself so Language / Voice menu
   rows can overflow:visible (names wrap, dialect wells stay whole).
   Do not put overflow:hidden on the option. That clips wrapping names.
   The slot is a crop window, never a plate: no fill, border, radius,
   shadow, or padding. */
.tb-flag-slot {
  display: block;
  box-sizing: content-box;
  flex: 0 0 auto;
  width: var(--tb-flag-w, 28px);
  height: var(--tb-flag-h, 21px);
  min-width: var(--tb-flag-w, 28px);
  max-width: var(--tb-flag-w, 28px);
  min-height: var(--tb-flag-h, 21px);
  max-height: var(--tb-flag-h, 21px);
  margin: 0;
  padding: 0;
  overflow: hidden;
  border: none !important;
  outline: none !important;
  box-shadow: none !important;
  border-radius: 0 !important;
  background: none !important;
  background-color: transparent !important;
  background-image: none !important;
  filter: none !important;
  line-height: 0;
}

.tb-lang-pick__subs .tb-flag-slot {
  --tb-flag-w: 18px;
  --tb-flag-h: 14px;
}

.tb-drop__btn > span:not(.tb-flag-slot):not(.tb-ai-pick__mark),
.tb-lang-pick__btn > .tb-lang-pick__who,
.tb-lang-pick__btn > .tb-cefr-pick__who,
.tb-lang-pick__btn > .tb-voice-pick__who {
  flex: 1;
  min-width: 0;
}

.tb-lang-pick__subs .tb-flag {
  --tb-flag-w: 18px;
  --tb-flag-h: 14px;
}

.tb-dialect-pick .tb-lang-pick__option .tb-flag-slot,
.tb-dialect-pick .tb-lang-pick__option > .tb-flag {
  flex: 0 0 auto;
}

.tb-practice__who-lang .tb-flag,
.tb-practice__who-lang .tb-flag-slot {
  /* Compact flag on the Body name row. 18×14 sits with 16px caps;
     20×15 read as taller than Sofía. No frame. */
  --tb-flag-w: 18px;
  --tb-flag-h: 14px;
}

/* Profile flags are not wrapped in a slot. The global overpaint (w+2,
   margin -1) would pull the picture 1px left of Sofía and the bio.
   Layout is the exact flag. Source-crop the 1px SVG fringe so it is
   not a halo on purple. */
.tb-practice__who-lang img.tb-flag,
.tb-practice__who-lang > .tb-flag {
  flex: 0 0 auto;
  width: var(--tb-flag-w);
  height: var(--tb-flag-h);
  min-width: var(--tb-flag-w);
  max-width: var(--tb-flag-w);
  min-height: var(--tb-flag-h);
  max-height: var(--tb-flag-h);
  margin: 0;
  object-fit: cover;
  object-view-box: inset(2px);
}

.tb-practice__who-lang .tb-flag[hidden],
.tb-practice__who-dialect[hidden] {
  display: none;
}

.tb-landing .tb-lockup__name { color: var(--tb-on-booth); }

.tb-landing a.tb-lockup {
  text-decoration: none;
  color: inherit;
}

.tb-auth-mark {
  display: inline-flex;
  width: 20px;
  height: 20px;
  flex-shrink: 0;
}

.tb-auth-mark svg {
  width: 20px;
  height: 20px;
  display: block;
}

.tb-topbar__end .tb-class-lang,
.tb-topbar__end .tb-class-lang .tb-flag,
.tb-topbar__end .tb-class-lang .tb-flag-slot {
  --tb-flag-w: 18px;
  --tb-flag-h: 14px;
}

/* Compact Home flags: the global 2px overpaint (22×18 in an 18×14
   slot) clips Italy's red stripe. Source-inset the SVG fringe instead,
   same as the person flag. Slot stays a crop window, never a plate. */
.tb-topbar__end .tb-class-lang img.tb-flag,
.tb-topbar__end .tb-class-lang .tb-flag,
.tb-home__region img.tb-flag,
.tb-home__region .tb-flag {
  width: var(--tb-flag-w);
  height: var(--tb-flag-h);
  min-width: var(--tb-flag-w);
  max-width: var(--tb-flag-w);
  min-height: var(--tb-flag-h);
  max-height: var(--tb-flag-h);
  margin: 0;
  object-fit: cover;
  object-view-box: inset(2px);
}

.tb-topbar__end .tb-class-lang > .tb-lang-pick__btn .tb-flag-slot {
  width: var(--tb-flag-w);
  height: var(--tb-flag-h);
  min-width: var(--tb-flag-w);
  max-width: var(--tb-flag-w);
  min-height: var(--tb-flag-h);
  max-height: var(--tb-flag-h);
}

.tb-home__region,
.tb-home__region .tb-flag,
.tb-home__region .tb-flag-slot {
  --tb-flag-w: 18px;
  --tb-flag-h: 14px;
}

/* In-place text. Just the words. Hover shows a Lucide pencil to the
   right of the title, centered on that line. Not raised into the
   topbar. Not at the page edge. No underline. No wash. Rest is a
   click to edit: pointer, like a button. Text caret only once the
   field is active. */
.tb-edit {
  display: flex;
  flex-direction: row;
  align-items: center;
  align-self: start;
  width: fit-content;
  max-width: 100%;
  gap: var(--tb-space-2);
  line-height: 1.02;
  min-height: 1em;
  cursor: pointer;
}

.tb-edit__text {
  display: block;
  flex: 1 1 auto;
  outline: none;
  cursor: pointer;
  min-width: var(--tb-edit-min);
  max-width: 100%;
  overflow-wrap: break-word;
  font-size: inherit;
  font-weight: inherit;
  letter-spacing: inherit;
  line-height: inherit;
}

.tb-edit:focus-within,
.tb-edit:focus-within .tb-edit__text {
  cursor: text;
  caret-color: var(--tb-on-booth);
  -webkit-user-select: text;
  user-select: text;
}

.tb-edit__pen {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: var(--tb-icon);
  height: var(--tb-icon);
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--tb-quiet);
  font: inherit;
  font-size: inherit;
  line-height: 0;
  cursor: pointer;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity var(--tb-move) var(--tb-move-ease), color var(--tb-move) var(--tb-move-ease);
}

/* Pencil is a hover hint. Click Untitled to clear it and type. */
.tb-edit:hover:not(:focus-within) .tb-edit__pen {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}

.tb-edit__pen:hover,
.tb-edit__pen:focus-visible {
  color: var(--tb-quiet-hover);
}

.tb-edit__pen svg {
  display: block;
  width: 100%;
  height: 100%;
}

.tb-character .tb-voice-pick--fact .tb-flag,
.tb-character .tb-voice-pick--fact .tb-flag-slot,
.tb-character .tb-voice-pick--fact .tb-voice-pick__place {
  display: none;
}

.tb-character .tb-voice-pick--fact .tb-flag {
  --tb-flag-w: 18px;
  --tb-flag-h: 14px;
}

.tb-character .tb-voice-pick--fact .tb-flag-slot {
  --tb-flag-w: 18px;
  --tb-flag-h: 14px;
}

/* The photo is the control. No corner refresh button. Hover or tap
   the square, then confirm. Spinner stays in this square while FAL runs. */
.tb-portrait {
  appearance: none;
  -webkit-appearance: none;
  position: relative;
  display: block;
  width: var(--tb-who-square, calc(var(--tb-btn-h) * 2 + var(--tb-space-2)));
  height: var(--tb-who-square, calc(var(--tb-btn-h) * 2 + var(--tb-space-2)));
  margin: 0;
  padding: 0;
  flex-shrink: 0;
  border: none;
  border-radius: var(--tb-radius-lg);
  background: var(--tb-wash);
  box-shadow: inset 0 0 0 2px var(--tb-hair);
  cursor: pointer;
  overflow: hidden;
}

/* Create copies Practice's face: the picture only. No hairline plate
   on an empty square or on a generated photo. */
.tb-character .tb-portrait {
  --tb-who-square: var(--tb-who-portrait);
  width: var(--tb-who-square);
  height: var(--tb-who-square);
  background: transparent;
  box-shadow: none;
  overflow: hidden;
}

.tb-character .tb-portrait img {
  transform: scale(1.08);
}

.tb-character .tb-portrait:not(:has(img[src]:not([src=""]))) {
  background: var(--tb-wash);
}

.tb-portrait img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: var(--tb-radius-lg);
  pointer-events: none;
  transition: opacity var(--tb-move) var(--tb-move-ease);
}

.tb-portrait:hover img,
.tb-portrait:focus-visible img {
  opacity: 0.82;
}

.tb-portrait:focus-visible {
  outline: 2px solid var(--tb-on-booth);
  outline-offset: 3px;
}

.tb-portrait:disabled,
.tb-portrait.is-busy {
  cursor: default;
}

.tb-portrait img:not([src]),
.tb-portrait.is-busy img:not([src]) {
  visibility: hidden;
}

/* Loader sits in the portrait square, centered where the photo will land. */
.tb-portrait.is-busy::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 1.5em;
  height: 1.5em;
  margin: -0.75em;
  border: 2px solid var(--tb-on-booth);
  border-right-color: transparent;
  border-radius: 50%;
  animation: tb-spin 0.7s linear infinite;
  z-index: 1;
}

@media (prefers-reduced-motion: reduce) {
  .tb-portrait.is-busy::after {
    animation: none;
    border-right-color: var(--tb-on-booth);
  }

}

.tb-portrait:disabled img,
.tb-portrait.is-busy img {
  opacity: 1;
}

.tb-page--board-fill .tb-lockup {
  flex: 0 0 auto;
}


/* ==========================================================================
   KIT — Button (Primary, Gray, Red, Quiet, Ghost, Soft)
   ========================================================================== */

/* Buttons — the kit. One disabled grammar.
   Hairline:  .tb-btn--quiet  (SSO, Open board)
   Raised:    .tb-btn--primary (Practice, Create, Join, Start, Hold to talk, Back)
   Gray brick: .tb-btn--gray (Home Settings)
   Red brick: .tb-btn--red (End all, Stop)
   Ghost / soft: Teacher mode, Dismiss / Keep this image
   Disabled: keep the live colors, keep the brick, fade. Never grey. Never white. */
.tb-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--tb-space-2);
  min-height: var(--tb-btn-h);
  padding: 0 var(--tb-space-4);
  border: none;
  border-radius: var(--tb-control-radius);
  font-family: var(--tb-font);
  font-size: var(--tb-text);
  font-weight: var(--tb-weight-bold);
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
}

/* HTML hidden loses to display:flex/inline-flex on .tb-btn. Keep the
   attribute for park-and-return doors (Microsoft Sign in). */
.tb-btn[hidden] {
  display: none !important;
}

/* Raised bricks: the yellow/red/purple face shrinks by the lip so the
   whole brick (face + shadow) is --tb-btn-h, same as a wash field.
   Type stays on .tb-btn. Quiet, ghost, and flat yellow have no lip.
   Talk-bar Send uses the same lip as Hold to talk (--tb-talk-bar-h). */
.tb-btn--primary,
.tb-btn--red,
.tb-btn--gray {
  min-height: calc(var(--tb-btn-h) - var(--tb-brick-lip));
  margin-bottom: var(--tb-brick-lip);
}

.tb-btn svg {
  width: var(--tb-icon);
  height: var(--tb-icon);
  flex-shrink: 0;
}

.tb-btn:disabled {
  transform: none;
  cursor: not-allowed;
  opacity: var(--tb-btn-disabled-opacity);
}

.tb-btn--block {
  width: 100%;
}

/* Primary is the yellow brick. On a purple ground a purple brick disappears,
   so the one thing you press on each screen is the one thing that is yellow. */
.tb-btn--primary {
  background: var(--tb-ritual);
  color: var(--tb-ink);
  font-weight: var(--tb-weight-black);
  box-shadow: var(--tb-shadow-brick-ritual);
}

.tb-btn--primary:active:not(:disabled) {
  box-shadow: var(--tb-shadow-brick-ritual-press);
  transform: translateY(3px);
}

.tb-btn--primary:disabled {
  background: var(--tb-yellow);
  color: var(--tb-ink);
}

.tb-btn--red {
  background: var(--tb-red);
  color: var(--tb-surface);
  font-weight: var(--tb-weight-black);
  box-shadow: var(--tb-shadow-brick-red);
}

.tb-btn--red:active:not(:disabled) {
  box-shadow: var(--tb-shadow-brick-red-press);
  transform: translateY(3px);
}

/* Gray brick. Same face-plus-lip as Create. Ink icon, like Create type. */
.tb-btn--gray {
  background: var(--tb-gray);
  color: var(--tb-ink);
  box-shadow: var(--tb-shadow-brick-gray);
}

.tb-btn--gray:active:not(:disabled) {
  box-shadow: var(--tb-shadow-brick-gray-press);
  transform: translateY(3px);
}

.tb-btn--ghost {
  background: transparent;
  color: var(--tb-dim);
}

.tb-btn--ghost:hover {
  color: var(--tb-quiet-hover);
}

.tb-btn--ghost:has(.lucide-x) {
  color: var(--tb-quiet);
  opacity: 1;
}

.tb-btn--soft {
  background: var(--tb-wash-hover);
  color: var(--tb-on-purple);
}

/* Quiet — a real control that does not raise its voice. Used for per-seat
   End so the red weight stays on the one End all the teacher rarely presses. */
.tb-btn--quiet {
  background: transparent;
  color: var(--tb-on-booth);
  box-shadow: inset 0 0 0 1.5px var(--tb-hair);
}

.tb-btn--quiet:hover {
  color: var(--tb-on-purple);
  box-shadow: inset 0 0 0 1.5px var(--tb-quiet);
}

/* Topbar right is the quiet meta zone: what the lesson is and the one edit
   that changes it. Nothing here competes with the lockup. */
.tb-topbar .tb-btn--ghost {
  min-height: 0;
  height: auto;
  padding: 0;
  line-height: 1;
  text-box: trim-both cap alphabetic;
  text-box-trim: trim-both;
  text-box-edge: cap alphabetic;
  color: var(--tb-quiet);
}

.tb-topbar .tb-btn--ghost:hover { color: var(--tb-quiet-hover); }

/* Ghost dock actions are text, not 48px controls. Keep dock-pad even
   around the words instead of an empty button face. Import is nested
   in .tb-home__import, so it needs the same cut. */
.tb-dock > .tb-btn--ghost,
.tb-home__import > .tb-btn--ghost {
  min-height: 0;
  height: auto;
  padding: 0;
  align-self: flex-start;
  gap: var(--tb-space-1);
  font-size: var(--tb-text-sm);
  font-weight: var(--tb-weight-medium);
  line-height: 1;
}

/* Expand chevron: Lucide chevron-right. Collapsed points right, beside
   the words. Open rotates 90deg to point down. */
.tb-chevron {
  display: block;
  width: var(--tb-icon-sm);
  height: var(--tb-icon-sm);
  flex-shrink: 0;
  color: inherit;
  pointer-events: none;
  transform-origin: 50% 50%;
  transition: transform var(--tb-move) var(--tb-move-ease);
}

.tb-home__import > .tb-btn--ghost > .tb-chevron,
.tb-home__import > .tb-btn--ghost > svg.tb-chevron {
  width: var(--tb-icon-sm);
  height: var(--tb-icon-sm);
}

.tb-home__import > .tb-btn--ghost[aria-expanded="true"] > .tb-chevron,
.tb-home__import > .tb-btn--ghost[aria-expanded="true"] > svg.tb-chevron {
  transform: rotate(90deg);
}

.tb-ai__details > summary > .tb-chevron,
.tb-ai__details > summary > svg.tb-chevron {
  width: var(--tb-icon-sm);
  height: var(--tb-icon-sm);
  color: inherit;
}

.tb-ai__details[open] > summary > .tb-chevron,
.tb-ai__details[open] > summary > svg.tb-chevron {
  transform: rotate(90deg);
}

.tb-home__go > .tb-btn--primary {
  flex: 1 1 auto;
  width: auto;
  min-width: 0;
}

.tb-home__go > .tb-btn--gray {
  flex: 0 0 auto;
  width: var(--tb-btn-h);
  min-width: var(--tb-btn-h);
  padding: 0;
}

/* Subpage leave. Same brick as Create, gray like Settings. */
.tb-dock > .tb-btn--gray.tb-btn--block {
  width: 100%;
  font-weight: var(--tb-weight-black);
}

.tb-who__row > .tb-btn--primary {
  appearance: none;
  -webkit-appearance: none;
  width: var(--tb-btn-h);
  min-width: var(--tb-btn-h);
  max-width: none;
  padding-top: 0;
  padding-bottom: 0;
  padding-inline: 0;
  line-height: 1;
  flex-shrink: 0;
  align-self: end;
  justify-self: start;
  justify-content: center;
  position: relative;
  overflow: visible;
  white-space: nowrap;
}

.tb-who__row > .tb-btn--primary[aria-busy="true"],
.tb-who__row > .tb-btn--primary[aria-busy="true"]:disabled {
  pointer-events: none;
  background: var(--tb-ritual);
  color: transparent;
  opacity: 1;
  box-shadow: var(--tb-shadow-brick-ritual);
}

.tb-who__row > .tb-btn--primary[aria-busy="true"] svg {
  visibility: hidden;
}

.tb-who__row > .tb-btn--primary[aria-busy="true"]::after {
  content: "";
  position: absolute;
  inset: 50%;
  width: 1.25em;
  height: 1.25em;
  margin: -0.625em;
  border: 2px solid var(--tb-ink);
  border-right-color: transparent;
  border-radius: 50%;
  animation: tb-spin 0.7s linear infinite;
}

@media (prefers-reduced-motion: reduce) {
  .tb-who__row > .tb-btn--primary[aria-busy="true"]::after {
    animation: none;
    border-right-color: var(--tb-ink);
  }

}

.tb-character .tb-practice__who-top .tb-voice-pick--fact .tb-lang-pick__btn > button.tb-speak.tb-btn {
  margin: 0;
  width: 1.25em;
  min-width: 0;
  min-height: 0;
  height: 1.25em;
  padding: 0;
  color: inherit;
}

/* Done is the brick that leaves, the same grammar as Review's Done, full
   width of the shared phone column. */
.tb-ended .tb-btn {
  width: 100%;
}

/* The answers are the same width in the same column, so End, Restart,
   Stop, and Keep going stay one clear stack instead of mixed sizes. */
.tb-monitor-confirm .tb-btn { width: 100%; }

.tb-voice-start .tb-btn--primary {
  width: 100%;
}

.tb-blocked .tb-btn--primary { width: 100%; }

/* Real checkboxes for voice consent and class lesson pick. */
.tb-check {
  appearance: none;
  -webkit-appearance: none;
  display: flex;
  align-items: flex-start;
  gap: var(--tb-space-3);
  width: 100%;
  margin: 0;
  padding: var(--tb-space-3);
  border: none;
  border-radius: var(--tb-control-radius);
  background: var(--tb-wash);
  color: var(--tb-on-booth);
  font-family: var(--tb-font);
  font-size: var(--tb-text);
  font-weight: var(--tb-weight-medium);
  letter-spacing: var(--tb-track);
  line-height: 1.25;
  text-align: left;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.tb-check:hover,
.tb-check:focus-visible {
  background: var(--tb-wash-hover);
}

.tb-check__box {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: calc(var(--tb-space-4) + var(--tb-space-2));
  height: calc(var(--tb-space-4) + var(--tb-space-2));
  margin-top: 1px;
  border: 2px solid var(--tb-quiet);
  border-radius: var(--tb-radius-sm);
  background: transparent;
  color: transparent;
}

.tb-check__box svg {
  display: block;
  width: var(--tb-icon-sm);
  height: var(--tb-icon-sm);
}

.tb-check.is-on .tb-check__box,
.tb-check[aria-checked="true"] .tb-check__box {
  border-color: var(--tb-yellow);
  background: var(--tb-yellow);
  color: var(--tb-ink);
}

.tb-check__text {
  flex: 1 1 auto;
  min-width: 0;
}

.tb-monitor-foot--stack .tb-btn--ghost {
  align-self: flex-start;
  width: auto;
  min-height: 0;
  height: auto;
  padding: 0;
  font-size: var(--tb-text-sm);
  font-weight: var(--tb-weight-medium);
  line-height: 1;
}

.tb-monitor-foot--stack .tb-btn--red {
  width: 100%;
}

/* When the lesson put you in a mode you did not pick, the way out is a real
   soft control, the same family as the Mic blocked switch. A text whisper
   here would undercut the claim that the two modes are equal. */
.tb-hold .tb-btn--soft {
  margin-top: var(--tb-space-2);
}

.tb-lobby__code-row .tb-btn {
  flex: 0 0 auto;
  width: auto;
}

.tb-monitor-foot .tb-btn--ghost { padding-left: 0; }

.tb-create__teacher .tb-check__box {
  width: var(--tb-icon-sm);
  height: var(--tb-icon-sm);
  margin-top: 0;
}

.tb-create__teacher .tb-check__box svg {
  width: 12px;
  height: 12px;
}

.tb-create__teacher.is-on .tb-check__box,
.tb-create__teacher[aria-checked="true"] .tb-check__box {
  border-color: var(--tb-yellow);
  background: var(--tb-yellow);
  color: var(--tb-ink);
}

.tb-dock > .tb-pair .tb-btn {
  width: 100%;
}


/* ==========================================================================
   KIT — Field, Input, File drop, Checkbox
   ========================================================================== */

/* Field labels sit on purple as quiet signage. Chrome icons (X, pencil,
   Language chevron) use this same quiet color at rest. Add also
   uses --tb-quiet, but it is Hint type, not Label. Hover of quiet text
   and icons is white. Margin is zero: .tb-field gap tucks the label to
   its control. Line-height 1 so that 8px is real air, not a tall line box. */
.tb-label {
  display: block;
  margin: 0;
  color: var(--tb-quiet);
}

/* Fields. Gap inside is smaller than the gap between fields, so a
   label never floats between two controls. Inputs are wash fill, no
   hairline. */
.tb-field {
  display: flex;
  flex-direction: column;
  gap: var(--tb-label-gap);
  width: 100%;
}

/* If a rail host ever wraps a rounded input/textarea, do not paint a square
   plate behind it. Prefer not wrapping those controls at all (tb-rail.js). */
.tb-rail-host:has(> .tb-input),
.tb-rail-host:has(> .tb-textarea),
.tb-rail-host:has(> .tb-character__bio) {
  background: transparent !important;
  border-radius: var(--tb-control-radius);
  grid-template-columns: minmax(0, 1fr);
}

/* Inputs are controls on the play ground, not white panels. Same wash as
   Home tiles: fill only, no hairline. appearance:none is required: native
   UA chrome paints a square plate behind border-radius and shows as dark
   corners on play purple. */
.tb-input,
.tb-textarea {
  appearance: none;
  -webkit-appearance: none;
  width: 100%;
  border: none;
  border-radius: var(--tb-control-radius);
  box-shadow: none;
  padding: var(--tb-space-3) var(--tb-space-4);
  font-family: var(--tb-font);
  font-size: var(--tb-text);
  font-weight: var(--tb-weight-bold);
  color: var(--tb-on-purple);
  background: var(--tb-wash);
  outline: none;
  min-height: var(--tb-btn-h);
}

.tb-input:focus,
.tb-textarea:focus {
  background: var(--tb-wash-hover);
  box-shadow: none;
}

.tb-textarea {
  min-height: var(--tb-btn-h);
  max-height: 12em;
  resize: none;
  overflow-x: hidden;
  overflow-y: hidden;
  font-weight: var(--tb-weight-semibold);
  line-height: 1.45;
  /* Height is set by sizeArea on input. field-sizing alone does not shrink
     reliably when the teacher deletes a blank line. */
  field-sizing: fixed;
}

.tb-input::placeholder,
.tb-textarea::placeholder {
  color: var(--tb-quiet);
  font-weight: var(--tb-weight-medium);
}

.tb-pair .tb-field,
.tb-pair .tb-drop,
.tb-pair .tb-lang-pick {
  align-self: start;
}

/* Closed Region: one line, --tb-btn-h, no chevron, marquee if the name
   overflows. Open menu is flag + name, then Hint people count under
   the name like Home Language. Closed stays flag + Standard only. */
.tb-field[hidden] {
  display: none !important;
}

.tb-input--code {
  text-align: center;
  padding-block: 0;
  font-size: var(--tb-text-xl);
  letter-spacing: var(--tb-track-code);
  font-weight: var(--tb-weight-black);
}

/* Typed PIN stays Title 28. Empty, Enter code is the same Hint
   placeholder as every other input (Body 16, medium, quiet), not Join
   black and not code tracking. ::placeholder font-size alone is ignored
   in WebKit, so the empty field itself is 16. */
.tb-input--code:placeholder-shown {
  font-size: var(--tb-text);
  font-weight: var(--tb-weight-medium);
  letter-spacing: var(--tb-track);
}

.tb-input--code::placeholder {
  font-size: var(--tb-text);
  font-weight: var(--tb-weight-medium);
  letter-spacing: var(--tb-track);
  color: var(--tb-quiet);
}

/* Name door. No Your name label. Placeholder and typed name stay Body,
   centered, same as Enter code's alignment (not its Title size). */
.tb-input--name {
  text-align: center;
}

.tb-goals__top .tb-label {
  margin: 0;
}

.tb-app-lang > .tb-drop__menu > .tb-label {
  margin: 0 var(--tb-drop-row-pad);
  text-align: start;
}

.tb-settings__section > .tb-label,
.tb-ai__section > .tb-label {
  margin: 0;
  width: 100%;
  flex-shrink: 0;
  color: var(--tb-quiet);
}

.tb-home__head .tb-label {
  margin: 0;
}

.tb-home__block .tb-label {
  margin: 0;
  color: var(--tb-home-quiet);
}

.tb-file-drop[hidden],
[data-import-url][hidden] {
  display: none;
}

/* File import: a drop box, not a hint plus a Choose a file brick. */
.tb-file-drop {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--tb-space-3);
  width: 100%;
  min-height: 8.5rem;
  padding: var(--tb-space-4);
  border: 2px dashed var(--tb-card-line);
  border-radius: var(--tb-control-radius);
  background: transparent;
  color: var(--tb-on-booth);
  text-align: center;
}

.tb-file-drop.is-over {
  border-color: var(--tb-card-line-bright);
  background: var(--tb-wash);
}

.tb-file-drop.has-file {
  border-style: solid;
  background: var(--tb-wash);
}

.tb-file-drop__input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  opacity: 0;
  cursor: pointer;
}

.tb-file-drop svg {
  width: 28px;
  height: 28px;
  flex-shrink: 0;
  pointer-events: none;
}

.tb-file-drop [data-import-file-name] {
  font-size: var(--tb-text-sm);
  font-weight: var(--tb-weight-semibold);
  line-height: 1.35;
  color: var(--tb-home-quiet);
  pointer-events: none;
}

.tb-file-drop.has-file [data-import-file-name] {
  font-weight: var(--tb-weight-bold);
  color: var(--tb-on-booth);
}

.tb-pair .tb-field {
  margin: 0;
  min-width: 0;
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  gap: var(--tb-label-gap);
}

.tb-create__compact .tb-field { margin: 0; }

.tb-create__compact [data-times] > .tb-field {
  margin: 0;
}

.tb-character__notes .tb-label {
  margin: 0;
}

.tb-character__notes .tb-textarea {
  max-height: var(--tb-notes-max);
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* Empty notes stay one control. A long e.g. placeholder must not wrap. */
.tb-character__notes .tb-textarea:placeholder-shown {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.tb-tip .tb-label {
  margin: 0;
}

.tb-voice-start .tb-voice-lang .tb-label {
  display: block;
  text-align: left;
}

.tb-notice section .tb-label {
  margin: 0;
}


/* ==========================================================================
   KIT — Dropdown (Language, Region, Level, Voice, App language)
   ========================================================================== */

.tb-drop,
.tb-lang-pick {
  position: relative;
  width: 100%;
  /* One inline padding for the closed control. The menu panel already
     carries tight-gap, so option rows subtract it and land on the same 16px edge. */
  --tb-drop-pad: var(--tb-space-4);
  --tb-drop-row-pad: calc(var(--tb-drop-pad) - var(--tb-tight-gap));
  /* Air between open-menu rows so hover and selected plates
     do not sit flush. Same on Language, Level, and Voice.
     Tight, not the 8px label gap. */
  --tb-drop-row-gap: var(--tb-tight-gap);
}

.tb-drop.is-open,
.tb-lang-pick.is-open {
  z-index: 40;
}

.tb-drop__btn,
.tb-lang-pick__btn {
  appearance: none;
  -webkit-appearance: none;
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--tb-space-3);
  width: 100%;
  min-height: var(--tb-btn-h);
  padding: 0 var(--tb-drop-pad);
  border: none;
  border-radius: var(--tb-control-radius);
  box-shadow: none;
  outline: none;
  background: var(--tb-wash);
  color: var(--tb-on-booth);
  font-family: var(--tb-font);
  font-size: var(--tb-text);
  font-weight: var(--tb-weight-black);
  text-align: left;
  cursor: pointer;
  overflow: hidden;
}

.tb-drop__btn:hover,
.tb-lang-pick__btn:hover,
.tb-drop.is-open .tb-drop__btn,
.tb-lang-pick.is-open .tb-lang-pick__btn {
  background: var(--tb-wash-hover);
}

/* Closed Language and Region: 8px between flag and name.
   Generic drops keep space-3. Never let the flag slot take the leftover. */
.tb-lang-pick__btn {
  gap: var(--tb-space-2);
}

/* Closed Language: dialect flag, Spanish (label), Mexico as the quiet sub.
   Column stack inside --tb-btn-h. Open Language is the nested dialect well. */
.tb-lang-pick__who {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 0;
  min-width: 0;
  flex: 1;
  overflow: hidden;
  line-height: 1.1;
}

.tb-lang-pick__label {
  min-width: 0;
  width: 100%;
}

.tb-lang-pick__sub,
.tb-cefr-pick__name {
  color: var(--tb-quiet);
  opacity: 1;
}

/* Closed Female 1 is quiet subtext like Mexico. Open voice rows are
   dropdown options, same type as Language / Level names. */
.tb-voice-pick .tb-lang-pick__btn .tb-voice-pick__gender {
  color: var(--tb-quiet);
  opacity: 1;
}

/* Closed Language label, dialect sub, and Level band: each line marquees
   on overflow. JS drives motion (WAAPI): 5s dwell at the start, scroll,
   3s dwell at the overflow end, scroll back. Never wrap. Never ellipsis.
   Never grow the control. Open menus still wrap in full. */
.tb-lang-pick__btn .tb-lang-pick__label,
.tb-lang-pick__btn .tb-lang-pick__sub,
.tb-lang-pick__btn .tb-cefr-pick__name {
  display: block;
  width: 100%;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
}

/* Closed Language keeps Mexico as the quiet sub. Height lock below
   holds --tb-btn-h. Each line marquees. Do not hide the sub. */
.tb-voice-pick .tb-lang-pick__btn {
  min-height: var(--tb-btn-h);
}

/* Closed Voice may clip; menu option rows must show full names. */
.tb-voice-pick .tb-lang-pick__btn > .tb-voice-pick__who {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tb-voice-pick__who {
  display: inline-flex;
  align-items: baseline;
  gap: var(--tb-tight-gap);
  min-width: 0;
}

.tb-voice-pick__row .tb-voice-pick__gender {
  font-size: inherit;
  font-weight: inherit;
  color: inherit;
}

.tb-voice-pick .tb-lang-pick__option > .tb-voice-pick__who {
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  flex-direction: column;
  align-items: flex-start;
  flex-wrap: nowrap;
  white-space: normal;
  overflow: visible;
  text-overflow: unset;
}

/* The chevron is in the layout, not absolute, so text can never slide under it.
   Expand chevrons are Lucide chevron-right. Collapsed points right. Open
   rotates 90deg to point down over --tb-move. */
.tb-drop__btn > svg,
.tb-lang-pick__btn > svg {
  flex: 0 0 auto;
  display: block;
  width: 20px;
  height: 20px;
  color: var(--tb-quiet);
  pointer-events: none;
  transform-origin: 50% 50%;
  transition: transform var(--tb-move) var(--tb-move-ease), color var(--tb-move) var(--tb-move-ease);
}

.tb-drop__btn:hover > svg,
.tb-lang-pick__btn:hover > svg,
.tb-drop.is-open .tb-drop__btn > svg,
.tb-lang-pick.is-open .tb-lang-pick__btn > svg {
  color: var(--tb-quiet-hover);
}

.tb-drop__btn:hover .tb-lang-pick__sub,
.tb-lang-pick__btn:hover .tb-lang-pick__sub,
.tb-lang-pick__btn:hover .tb-cefr-pick__name,
.tb-lang-pick__btn:hover .tb-voice-pick__gender,
.tb-drop.is-open .tb-lang-pick__sub {
  color: var(--tb-quiet-hover);
}

/* Voice already pushes with .tb-speak's auto margin. Everywhere else the
   chevron takes the slack so it lands on the right padding edge. */
.tb-drop__btn:not(:has(.tb-speak)) > svg,
.tb-lang-pick__btn:not(:has(.tb-speak)) > svg {
  margin-left: auto;
}

/* Closed Language, Region, and Level: no chevron.
   The control itself is the affordance. Voice keeps Hear. */
.tb-lang-pick:not(.tb-voice-pick) .tb-lang-pick__btn > svg {
  display: none;
}

/* Closed Language (Spanish + Mexico) stays --tb-btn-h. Overflow
   marquees. Closed Level is the same stack: B2 over the band. */
.tb-lang-pick:not(.tb-voice-pick) .tb-lang-pick__btn {
  padding-top: 0;
  padding-bottom: 0;
  height: var(--tb-btn-h);
  min-height: var(--tb-btn-h);
  overflow: hidden;
}

.tb-drop.is-open .tb-drop__btn > svg,
.tb-lang-pick.is-open .tb-lang-pick__btn > svg {
  transform: rotate(90deg);
}

/* Open Level: code in its own column, band wraps beside it. Closed
   Level stacks like Language: B2 on top (label), Upper Intermediate
   under it (quiet sub, marquees). Do not bleach the closed band white
   when the menu is open. */
.tb-cefr-pick__who {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  column-gap: var(--tb-space-2);
  min-width: 0;
  flex: 1;
}

.tb-cefr-pick .tb-lang-pick__btn .tb-cefr-pick__who {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 0;
  overflow: hidden;
  line-height: 1.1;
}

.tb-cefr-pick__code {
  white-space: nowrap;
}

.tb-cefr-pick .tb-lang-pick__btn .tb-cefr-pick__code {
  min-width: 0;
  width: 100%;
}

.tb-cefr-pick__name {
  min-width: 0;
  line-height: 1.2;
}

.tb-cefr-pick .tb-lang-pick__btn .tb-cefr-pick__name {
  color: var(--tb-quiet);
}

/* Band names stay quiet in the list. The selected row is a white plate,
   so inherit that row's ink (same as A1 / B1) or they vanish. */
.tb-cefr-pick .tb-lang-pick__option .tb-cefr-pick__name {
  color: var(--tb-quiet);
  white-space: normal;
  overflow: visible;
}

.tb-cefr-pick .tb-lang-pick__option.is-active .tb-cefr-pick__name {
  color: inherit;
}

/* Open Create voice: parent rows Male, Female. Click a gender
   to open ages (Adult, Older adult, Young, Teen). Click an age
   to open that well, same accordion as Language. Expand the
   matching gender and age when the menu opens. Voices in the
   age well stay Body options with Hear, not compact dialect type. */
.tb-voice-pick__group,
.tb-voice-pick__band {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  list-style: none;
}

.tb-voice-pick__ages,
.tb-voice-pick__voices {
  display: flex;
  flex-direction: column;
  gap: var(--tb-drop-row-gap);
  list-style: none;
}

.tb-voice-pick__row {
  display: flex;
  align-items: center;
  width: 100%;
  max-width: 100%;
  border-radius: 8px;
  padding-right: var(--tb-drop-row-pad);
}

.tb-voice-pick__row .tb-lang-pick__option {
  flex: 1 1 auto;
  min-width: 0;
  width: auto;
  padding-right: var(--tb-space-2);
}

.tb-voice-pick .tb-lang-pick__option > .tb-voice-pick__who {
  gap: 0;
}

.tb-voice-pick__tags {
  display: block;
  max-width: 100%;
  color: var(--tb-quiet);
  line-height: 1.2;
  white-space: normal;
  overflow-wrap: anywhere;
  pointer-events: none;
}

.tb-voice-pick .tb-voice-pick__voices .tb-voice-pick__row:has(.tb-lang-pick__option.is-active) .tb-voice-pick__tags {
  color: inherit;
}

.tb-drop.is-open > .tb-drop__menu,
.tb-lang-pick.is-open > .tb-lang-pick__menu {
  display: flex;
  flex-direction: column;
  gap: var(--tb-drop-row-gap);
}

.tb-drop > .tb-rail-host .tb-drop__menu,
.tb-lang-pick > .tb-rail-host .tb-lang-pick__menu {
  display: flex;
  flex-direction: column;
  gap: var(--tb-drop-row-gap);
  position: static;
  max-height: none;
  /* Host is max-height + overflow hidden. height 100% here resolved
     as auto (parent height is content-sized), so the pane grew to
     every row, the host clipped it, and it could not scroll. Flex
     the pane to the host and let this be the scrollport. */
  height: auto;
  min-height: 0;
  flex: 1 1 auto;
  padding: 0;
  overflow-x: hidden;
  overflow-y: auto;
  background: transparent;
  box-shadow: none;
  border-radius: 0;
}

.tb-drop__option,
.tb-lang-pick__option {
  display: flex;
  align-items: center;
  gap: var(--tb-space-3);
  width: 100%;
  min-height: 44px;
  padding: 0 var(--tb-drop-row-pad);
  border: none;
  border-radius: 8px;
  background: transparent;
  color: var(--tb-on-booth);
  font-family: var(--tb-font);
  font-size: var(--tb-text);
  font-weight: var(--tb-weight-bold);
  text-align: left;
  cursor: pointer;
  overflow: visible;
}

.tb-lang-pick__option:hover {
  background: var(--tb-wash);
}

.tb-lang-pick__option.is-active {
  background: var(--tb-surface);
  color: var(--tb-booth);
}

/* Leftover nested Language (no product screen uses it). Home
   Language, Voice Start Language, Create Region, Level, and Voice
   use the white selected plate. */
.tb-lang-pick:not(.tb-voice-pick):not(.tb-cefr-pick):not(.tb-dialect-pick):not(.tb-app-lang):not(.tb-class-lang):not(.tb-voice-lang-pick) .tb-lang-pick__option.is-active:not(.tb-lang-pick__dialect) {
  background: transparent;
  color: var(--tb-on-booth);
}

.tb-lang-pick__item {
  display: block;
  overflow: visible;
  border-radius: 8px;
}

.tb-lang-pick__item[hidden] {
  display: none;
}

.tb-lang-pick__item.is-expanded {
  background: var(--tb-wash);
  padding-bottom: var(--tb-space-1);
}

.tb-lang-pick__row {
  display: flex;
  flex-direction: row;
  align-items: center;
  width: 100%;
  min-width: 0;
  border-radius: 8px;
}

.tb-lang-pick__row:hover {
  background: var(--tb-wash);
}

.tb-lang-pick__item.is-expanded > .tb-lang-pick__row,
.tb-lang-pick__item.is-expanded > .tb-lang-pick__row:hover {
  background: transparent;
}

.tb-lang-pick__row > .tb-lang-pick__option {
  flex: 1 1 auto;
  min-width: 0;
  font-weight: var(--tb-weight-black);
}

.tb-lang-pick__row > .tb-lang-pick__option:hover,
.tb-lang-pick__row > .tb-lang-pick__option.is-active {
  background: transparent;
  color: inherit;
}

.tb-lang-pick__row > .tb-lang-pick__option .tb-lang-pick__name {
  flex: 1 1 auto;
  min-width: 0;
}

/* Menu names wrap in full. No ellipsis on Language / Level / Voice menus. */
.tb-lang-pick__name {
  flex: 1;
  min-width: 0;
  white-space: normal;
  overflow: visible;
  text-overflow: unset;
}

@media (prefers-reduced-motion: reduce) {
  .tb-drop__btn > svg,
  .tb-lang-pick__btn > svg {
    transition: none;
  }

}

/* Dialects live inside the open language: a nested well, compact.
   Same left pad as language rows. The well margin is the nest.
   Fill is --tb-lang-well, one step lighter than the expanded row.
   Never --tb-booth. */
.tb-lang-pick__subs {
  display: flex;
  flex-direction: column;
  gap: var(--tb-drop-row-gap);
  list-style: none;
  margin: 0 var(--tb-tight-gap) var(--tb-space-2);
  padding: var(--tb-tight-gap);
  overflow: visible;
  border-radius: var(--tb-radius-sm);
  background: var(--tb-lang-well);
}

/* Collapsed wells still count for the open menu's max-content width
   so expanding French does not jump the panel. Height stays 0.
   Horizontal pad stays so dialect names like Southern France measure. */
.tb-lang-pick__subs[hidden] {
  display: block;
  visibility: hidden;
  overflow: hidden;
  height: 0;
  max-height: 0;
  margin-block: 0;
  padding-block: 0;
  width: max-content;
  pointer-events: none;
}

.tb-lang-pick__subs[hidden] .tb-lang-pick__name {
  white-space: nowrap;
}

.tb-lang-pick__subs .tb-lang-pick__option {
  min-height: 0;
  padding-top: var(--tb-tight-gap);
  padding-bottom: var(--tb-tight-gap);
  padding-left: var(--tb-drop-row-pad);
  padding-right: var(--tb-drop-row-pad);
  gap: var(--tb-space-2);
  font-size: var(--tb-text-sm);
  font-weight: var(--tb-weight-semibold);
  color: var(--tb-on-booth);
  opacity: 1;
}

.tb-lang-pick__subs .tb-lang-pick__option:hover {
  background: var(--tb-wash);
}

/* No second white plate. Chosen dialect stays heavier type. */
.tb-lang-pick__subs .tb-lang-pick__option.is-active {
  background: transparent;
  color: var(--tb-on-booth);
  font-weight: var(--tb-weight-black);
  opacity: 1;
}

/* Voice keepers in the age well stay Body + Hear, white selected plate.
   Age parents in the gender well stay accordion rows, not compact. */
.tb-voice-pick .tb-voice-pick__ages > .tb-voice-pick__band > .tb-lang-pick__row > .tb-voice-pick__parent {
  min-height: calc(var(--tb-btn-h) - var(--tb-brick-lip));
  padding-top: 0;
  padding-bottom: 0;
  font-size: var(--tb-text);
  font-weight: var(--tb-weight-black);
}

.tb-voice-pick .tb-voice-pick__voices .tb-lang-pick__option {
  min-height: calc(var(--tb-btn-h) - var(--tb-brick-lip));
  height: auto;
  padding-top: var(--tb-tight-gap);
  padding-bottom: var(--tb-tight-gap);
  font-size: var(--tb-text);
  font-weight: var(--tb-weight-bold);
}

.tb-voice-pick .tb-voice-pick__voices .tb-voice-pick__row:hover {
  background: var(--tb-wash);
}

.tb-voice-pick .tb-voice-pick__voices .tb-voice-pick__row:has(.tb-lang-pick__option.is-active) {
  background: var(--tb-surface);
  color: var(--tb-booth);
}

.tb-voice-pick .tb-voice-pick__voices .tb-voice-pick__row:has(.tb-lang-pick__option.is-active):hover {
  background: var(--tb-surface);
}

.tb-voice-pick .tb-voice-pick__voices .tb-voice-pick__row .tb-lang-pick__option:hover,
.tb-voice-pick .tb-voice-pick__voices .tb-voice-pick__row .tb-lang-pick__option.is-active {
  background: transparent;
  color: inherit;
}

.tb-voice-pick .tb-voice-pick__parent {
  font-weight: var(--tb-weight-black);
}

.tb-dialect-pick .tb-lang-pick__btn {
  height: var(--tb-btn-h);
  min-height: var(--tb-btn-h);
  gap: var(--tb-space-2);
  overflow: hidden;
}

.tb-dialect-pick .tb-lang-pick__btn .tb-lang-pick__who,
.tb-dialect-pick .tb-lang-pick__btn .tb-lang-pick__label {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
}

.tb-dialect-pick .tb-lang-pick__option {
  align-items: center;
  gap: var(--tb-space-2);
  min-height: 0;
  padding-top: var(--tb-space-2);
  padding-bottom: var(--tb-space-2);
  overflow: visible;
  white-space: normal;
}

.tb-dialect-pick .tb-lang-pick__option > .tb-lang-pick__who {
  flex: 1;
  min-width: 0;
  overflow: visible;
}

.tb-dialect-pick .tb-lang-pick__name {
  flex: 1 1 auto;
  min-width: 0;
  white-space: normal;
  overflow: visible;
}

.tb-dialect-pick .tb-lang-pick__option .tb-lang-pick__sub {
  display: block;
  color: var(--tb-quiet);
  white-space: normal;
  overflow: visible;
}

.tb-dialect-pick .tb-lang-pick__option.is-active .tb-lang-pick__sub {
  color: inherit;
}

.tb-dialect-pick > .tb-lang-pick__btn .tb-lang-pick__sub {
  display: none !important;
}

/* App language. Not Create Language. Same tb-drop as Level: booth-deep
   menu, even tight-gap panel pad, option rows, white selected plate. Closed
   face is Lucide languages only, ghost, not a wash Language control. */
.tb-app-lang {
  position: relative;
  width: auto;
  flex-shrink: 0;
  z-index: 40;
}

.tb-landing > .tb-app-lang {
  position: absolute;
  top: var(--tb-chrome-pad);
  right: var(--tb-page-pad);
}

.tb-app-lang > .tb-drop__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0;
  width: auto;
  min-height: 0;
  height: auto;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  color: var(--tb-quiet);
  overflow: visible;
}

.tb-app-lang > .tb-drop__btn:hover,
.tb-app-lang.is-open > .tb-drop__btn {
  background: transparent;
  color: var(--tb-quiet-hover);
}

.tb-app-lang > .tb-drop__btn > svg,
.tb-app-lang.is-open > .tb-drop__btn > svg {
  margin-left: 0;
  width: var(--tb-icon);
  height: var(--tb-icon);
  color: inherit;
  transform: none;
}

.tb-app-lang__face,
.tb-app-lang__flag,
.tb-app-lang__job,
.tb-app-lang__pick {
  display: none;
}

/* Keep first-open flags decoded. Not display:none: that skips load.
   Experience sends Cache-Control: no-store, so a preload link cannot
   feed the list imgs. Blob URLs in this host can. */
.tb-app-lang__warm {
  position: absolute;
  left: 0;
  top: 0;
  width: var(--tb-flag-w, 28px);
  height: var(--tb-flag-h, 21px);
  overflow: hidden;
  opacity: 0;
  pointer-events: none;
}

.tb-app-lang > .tb-drop__menu {
  left: auto;
  right: 0;
  width: min(16rem, calc(100cqi - 2 * var(--tb-page-pad)));
  /* Never a percentage max-height here. A percentage resolves against the
     positioned ancestor, which is the picker itself (one icon tall), so the
     menu collapsed to a 22px sliver and only the Search field showed. */
  max-height: 24rem;
  overflow: hidden;
  /* This panel does not scroll. The list below does. Do not inherit a
     reserved gutter or Search sits tight left and a scrollbar-column right. */
  scrollbar-gutter: auto;
  grid-template-rows: auto auto minmax(0, 1fr);
  gap: var(--tb-label-gap);
  /* Heading + Search use drop-pad above the heading.
     Left/right stay tight-gap so flags still land on --tb-drop-pad.
     Gap heading to Search, and Search to the list, is the label gap.
     Do not pull the list up: a hover plate must not kiss Search. */
  padding: var(--tb-drop-pad) var(--tb-tight-gap);
  text-align: start;
}

.tb-app-lang.is-open > .tb-drop__menu {
  display: grid;
  /* Beat .tb-drop.is-open (row-gap 4px). Label to Search is 8px. */
  gap: var(--tb-label-gap);
}

.tb-landing .tb-app-lang > .tb-drop__menu {
  max-height: min(24rem, calc(100cqi - var(--tb-chrome-pad) - (var(--tb-space-4) * 4)));
}

.tb-app-lang > .tb-rail-host .tb-drop__menu,
.tb-app-lang.is-open > .tb-rail-host .tb-drop__menu {
  display: grid;
  grid-template-rows: auto auto minmax(0, 1fr);
  gap: var(--tb-label-gap);
  padding: 0;
  max-height: none;
}

.tb-app-lang__find {
  /* width auto so this margin does not overflow. Menu pad is drop-pad
     top/bottom and tight-gap left/right. Row-pad lands the wash on
     --tb-drop-pad, same as the label and the flags. Inner pad matches
     other wash fields. */
  width: auto;
  min-width: 0;
  margin: 0 var(--tb-drop-row-pad);
  min-height: 0;
  height: auto;
  padding: var(--tb-space-2) var(--tb-drop-pad);
  font-size: var(--tb-text-sm);
  font-weight: var(--tb-weight-medium);
}

.tb-app-lang__find::-webkit-search-decoration,
.tb-app-lang__find::-webkit-search-cancel-button,
.tb-app-lang__find::-webkit-search-results-button,
.tb-app-lang__find::-webkit-search-results-decoration {
  appearance: none;
  -webkit-appearance: none;
}

.tb-app-lang__list {
  list-style: none;
  min-height: 0;
  overflow-x: hidden;
  overflow-y: auto;
  scrollbar-gutter: auto;
  margin: 0;
  padding: 0;
}

.tb-app-lang .tb-drop__menu > .tb-rail-host > .tb-app-lang__list {
  height: 100%;
  max-height: none;
}

.tb-app-lang__empty {
  margin: 0;
  padding: var(--tb-label-gap) var(--tb-drop-row-pad);
  min-height: calc(var(--tb-flag-h, 21px) + 2 * var(--tb-label-gap));
  display: flex;
  align-items: center;
}

/* Settings wears the same picker as the Sign in door, but as a wash
   unit: Lucide languages, App language. No flag or name on the
   closed row. Click opens the list. Door keeps the ghost icon only. */
.tb-app-lang--field {
  width: 100%;
}

.tb-app-lang--field > .tb-drop__btn {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: var(--tb-space-2);
  width: 100%;
  min-width: 0;
  height: var(--tb-btn-h);
  min-height: var(--tb-btn-h);
  padding: 0 var(--tb-drop-pad);
  border-radius: var(--tb-control-radius);
  background: var(--tb-wash);
  color: var(--tb-on-booth);
  overflow: hidden;
  cursor: pointer;
}

.tb-app-lang--field > .tb-drop__btn:hover,
.tb-app-lang--field.is-open > .tb-drop__btn,
.tb-app-lang--field.tb-drop.is-open > .tb-drop__btn {
  background: var(--tb-wash-hover);
  color: var(--tb-on-booth);
}

.tb-app-lang--field > .tb-drop__btn > svg,
.tb-app-lang--field.is-open > .tb-drop__btn > svg {
  display: block;
  flex: none;
  margin-left: 0;
  width: var(--tb-icon);
  height: var(--tb-icon);
  color: var(--tb-quiet);
  transform: none;
}

.tb-app-lang--field > .tb-drop__btn:hover > svg,
.tb-app-lang--field.is-open > .tb-drop__btn > svg {
  color: var(--tb-quiet);
}

.tb-app-lang--field > .tb-drop__btn > .tb-app-lang__job {
  display: block;
  flex: 0 0 auto;
  color: inherit;
  font-size: var(--tb-text);
  font-weight: var(--tb-weight-black);
  letter-spacing: var(--tb-track);
  white-space: nowrap;
}

/* Menu spans the control, same as a Create dropdown. */
.tb-app-lang--field > .tb-drop__menu {
  left: 0;
  right: 0;
  width: auto;
}

.tb-ai-pick .tb-ai-pick__face {
  display: block;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tb-ai-pick > .tb-drop__btn {
  gap: var(--tb-space-2);
}

.tb-ai-pick__mark {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  background-color: currentColor;
  -webkit-mask: var(--tb-ai-mark) center / contain no-repeat;
  mask: var(--tb-ai-mark) center / contain no-repeat;
}

.tb-ai-pick__mark[data-mark="openai"] {
  --tb-ai-mark: url("../brand/providers/openai.svg");
}

.tb-ai-pick__mark[data-mark="anthropic"] {
  --tb-ai-mark: url("../brand/providers/anthropic.svg");
}

.tb-ai-pick__mark[data-mark="google"] {
  --tb-ai-mark: url("../brand/providers/google.svg");
}

.tb-ai-pick__mark[data-mark="googlegemini"] {
  --tb-ai-mark: url("../brand/providers/googlegemini.svg");
}

.tb-ai-pick__mark[data-mark="deepseek"] {
  --tb-ai-mark: url("../brand/providers/deepseek.svg");
}

.tb-ai-pick__mark[data-mark="qwen"] {
  --tb-ai-mark: url("../brand/providers/qwen.svg");
}

.tb-ai-pick__mark[data-mark="elevenlabs"] {
  --tb-ai-mark: url("../brand/providers/elevenlabs.svg");
}

.tb-ai-pick__mark[data-mark="microsoftazure"] {
  --tb-ai-mark: url("../brand/providers/microsoftazure.svg");
}

.tb-ai-pick__mark[data-mark="bytedance"] {
  --tb-ai-mark: url("../brand/providers/bytedance.svg");
}

.tb-ai-pick__name {
  flex: 1 1 auto;
  min-width: 0;
}

.tb-ai-pick__group {
  margin: var(--tb-space-2) var(--tb-drop-row-pad) var(--tb-space-1);
}

.tb-ai-pick__group:first-child {
  margin-top: 0;
}

.tb-ai-pick .tb-drop__option:hover {
  background: var(--tb-wash);
}

.tb-ai-pick .tb-drop__option.is-active {
  background: var(--tb-surface);
  color: var(--tb-booth);
}

.tb-ai__pick:has(.tb-ai__cost:not([hidden])) .tb-ai__select,
.tb-ai__pick:has(.tb-ai__cost:not([hidden])) .tb-ai-pick .tb-drop__btn {
  padding-right: calc(var(--tb-space-4) + 9ch);
}

.tb-topbar__end .tb-class-lang {
  position: relative;
  flex: 0 0 auto;
  width: auto;
  min-width: 0;
  z-index: 30;
}

.tb-topbar__end .tb-class-lang > .tb-lang-pick__btn {
  width: max-content;
  max-width: 9rem;
  height: auto;
  min-height: 0;
  padding: 0;
  border-radius: 0;
  background: transparent;
  gap: var(--tb-space-2);
  overflow: visible;
  font-size: var(--tb-text-sm);
  font-weight: var(--tb-weight-medium);
  line-height: 1;
  color: var(--tb-on-booth);
}

.tb-topbar__end .tb-class-lang > .tb-lang-pick__btn:hover,
.tb-topbar__end .tb-class-lang.is-open > .tb-lang-pick__btn {
  background: transparent;
  color: var(--tb-on-booth);
}

.tb-topbar__end .tb-class-lang > .tb-lang-pick__btn > .tb-lang-pick__who {
  flex: 0 1 auto;
  min-width: 0;
  flex-direction: row;
}

.tb-topbar__end .tb-class-lang > .tb-lang-pick__btn .tb-lang-pick__label {
  flex: 0 1 auto;
  min-width: 0;
  width: auto;
}

.tb-topbar__end .tb-class-lang > .tb-lang-pick__btn > svg {
  display: none;
}

.tb-class-lang > .tb-lang-pick__btn .tb-lang-pick__sub,
.tb-class-lang .tb-lang-pick__subs,
.tb-class-lang .tb-lang-pick__more,
.tb-voice-lang-pick > .tb-lang-pick__btn .tb-lang-pick__sub,
.tb-voice-lang-pick .tb-lang-pick__subs,
.tb-voice-lang-pick .tb-lang-pick__more {
  display: none !important;
}

/* Open Home Language and Voice Start Language: Hint people count
   under the name. Same quiet sub as Mexico / Elementary. Closed stays
   flag + Italian only. */
.tb-class-lang .tb-lang-pick__option,
.tb-voice-lang-pick .tb-lang-pick__option {
  align-items: center;
  min-height: 0;
  padding-top: var(--tb-space-2);
  padding-bottom: var(--tb-space-2);
}

.tb-class-lang .tb-lang-pick__option > .tb-lang-pick__who,
.tb-voice-lang-pick .tb-lang-pick__option > .tb-lang-pick__who {
  flex: 1;
  min-width: 0;
  overflow: visible;
}

.tb-class-lang .tb-lang-pick__option .tb-lang-pick__name,
.tb-voice-lang-pick .tb-lang-pick__option .tb-lang-pick__name {
  white-space: normal;
  overflow: visible;
}

.tb-class-lang .tb-lang-pick__option .tb-lang-pick__sub,
.tb-voice-lang-pick .tb-lang-pick__option .tb-lang-pick__sub {
  display: block;
  color: var(--tb-quiet);
  white-space: normal;
  overflow: visible;
}

.tb-class-lang .tb-lang-pick__option.is-active .tb-lang-pick__sub,
.tb-voice-lang-pick .tb-lang-pick__option.is-active .tb-lang-pick__sub {
  color: inherit;
}

.tb-pair .tb-drop,
.tb-pair .tb-lang-pick {
  min-width: 0;
  width: 100%;
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.tb-pair .tb-lang-pick__btn {
  flex: none;
  width: 100%;
  height: var(--tb-btn-h);
  min-width: 0;
  min-height: var(--tb-btn-h);
}

.tb-character:has(.tb-voice-pick.is-open) {
  z-index: 50;
}

/* Create is the Practice profile, editable. Same `tb-practice__who`
   type. Full bio, no more. Do not restyle the name or flag. */
.tb-character .tb-practice__who:has(.tb-voice-pick.is-open) {
  z-index: 6;
}

.tb-character .tb-practice__who-top .tb-voice-pick--fact {
  flex: 0 0 auto;
  margin-left: auto;
  width: fit-content;
  max-width: none;
  overflow: visible;
}

/* Closed Create: Male or Female left of Hear. Quiet subtext like
   Mexico, not Body. A 48px brick on this row is taller than Practice's
   name line, so a hole opens between the name and the bio. */
.tb-character .tb-practice__who-top .tb-voice-pick--fact .tb-lang-pick__btn > .tb-voice-pick__who {
  display: inline-flex;
  flex: 0 0 auto;
  min-width: 0;
  overflow: visible;
  text-overflow: unset;
}

.tb-character .tb-practice__who-top .tb-voice-pick--fact .tb-lang-pick__btn .tb-voice-pick__gender {
  white-space: nowrap;
}

/* Closed voice is Female 1 (or Male, Female 2) then Hear, at the end
   of the name row. Not a second flag line. The open menu lists every
   approved keeper for that dialect and stacks above Additional
   instructions. */
.tb-character .tb-practice__who-top .tb-voice-pick--fact .tb-lang-pick__btn {
  width: fit-content;
  max-width: 100%;
  min-height: 0;
  height: auto;
  padding: 0;
  gap: var(--tb-tight-gap);
  background: transparent;
  box-shadow: none;
  border-radius: 0;
  overflow: visible;
  font-size: inherit;
  font-weight: inherit;
  letter-spacing: inherit;
  line-height: inherit;
  color: var(--tb-on-booth);
  transition: color var(--tb-move) var(--tb-move-ease);
}

.tb-character .tb-voice-pick--fact .tb-lang-pick__btn:hover,
.tb-character .tb-voice-pick--fact.is-open .tb-lang-pick__btn,
.tb-character .tb-voice-pick--fact .tb-lang-pick__btn:focus-visible {
  background: transparent;
  color: var(--tb-on-purple);
  box-shadow: none;
  outline: none;
}

.tb-character .tb-voice-pick--fact .tb-lang-pick__btn > svg {
  display: none;
}

.tb-character .tb-voice-pick--fact .tb-voice-pick__row {
  width: 100%;
  max-width: 100%;
}

.tb-character .tb-voice-pick--fact .tb-voice-pick__row .tb-lang-pick__option {
  flex: 1 1 auto;
  min-width: 0;
}

.tb-character .tb-voice-pick--fact .tb-voice-pick__place {
  overflow-wrap: break-word;
}

.tb-voice-lang .tb-lang-pick,
.tb-voice-lang .tb-lang-pick__btn {
  width: 100%;
}

/* Voice Start Language: wash like Create Region. Closed is flag +
   Italian. Open is the same flat A–Z list as Home Language. */
.tb-voice-lang-pick .tb-lang-pick__btn {
  height: var(--tb-btn-h);
  min-height: var(--tb-btn-h);
  gap: var(--tb-space-2);
  overflow: hidden;
}

.tb-voice-lang-pick .tb-lang-pick__btn .tb-lang-pick__who,
.tb-voice-lang-pick .tb-lang-pick__btn .tb-lang-pick__label {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
}


/* ==========================================================================
   KIT — Marquee, Layout (Pair, Stack, Kind)
   ========================================================================== */

.tb-marquee {
  display: block;
  width: 100%;
  min-width: 0;
  overflow: hidden;
}

.tb-marquee__run {
  display: inline-block;
  max-width: none;
  white-space: nowrap;
}

/* JS owns the ping-pong via the Web Animations API. CSS keyframes cannot
   hold 5s / 3s when cycle length varies with overflow. Keep animation
   none so a leftover @keyframes cannot double-run with WAAPI. */
.tb-marquee.is-on > .tb-marquee__run {
  animation: none;
}

@media (prefers-reduced-motion: reduce) {
  .tb-marquee.is-on > .tb-marquee__run {
    animation: none;
  }

}

/* Stack helpers */
.tb-stack {
  display: flex;
  flex-direction: column;
  gap: var(--tb-space-4);
  width: 100%;
}

/* Two fields on one row: Language and Level on Create.
   minmax(0, 1fr) keeps a long CEFR name from blowing past the column.
   Both closed controls stay --tb-btn-h; overflow marquees. */
.tb-pair {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--tb-space-3);
  align-items: start;
}

.tb-voice-start .tb-voice-lang .tb-pair {
  width: 100%;
}

.tb-kind {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0;
  position: relative;
  width: 100%;
  height: var(--tb-btn-h);
  padding: var(--tb-tight-gap);
  background: var(--tb-wash);
  border-radius: var(--tb-control-radius);
  transition: background-color var(--tb-move) var(--tb-move-ease);
}

.tb-kind:hover {
  background: var(--tb-wash-hover);
}

.tb-kind__slide {
  position: absolute;
  z-index: 0;
  top: var(--tb-tight-gap);
  left: var(--tb-tight-gap);
  width: calc((100% - (var(--tb-tight-gap) * 2)) / 2);
  height: calc(100% - (var(--tb-tight-gap) * 2));
  border-radius: calc(var(--tb-control-radius) - var(--tb-tight-gap));
  background: var(--tb-wash-on);
  pointer-events: none;
  transform: translateX(0);
  transition: transform var(--tb-move) var(--tb-move-ease);
}

.tb-kind[data-on="game"] .tb-kind__slide {
  transform: translateX(100%);
}

.tb-kind__btn {
  appearance: none;
  -webkit-appearance: none;
  position: relative;
  z-index: 1;
  flex: none;
  min-width: 0;
  height: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--tb-on-booth);
  font-family: var(--tb-font);
  font-size: var(--tb-text);
  font-weight: var(--tb-weight-semibold);
  letter-spacing: var(--tb-track);
  cursor: pointer;
}

.tb-kind__btn:hover,
.tb-kind__btn:focus-visible,
.tb-kind__btn.is-on,
.tb-kind__btn.is-on:hover,
.tb-kind__btn.is-on:focus-visible {
  background: transparent;
  color: var(--tb-on-booth);
  font-weight: var(--tb-weight-semibold);
  outline: none;
}

@media (prefers-reduced-motion: reduce) {
  .tb-kind,
  .tb-kind__slide {
    transition: none;
  }
}

.tb-dock > .tb-pair {
  width: 100%;
  align-items: stretch;
}


/* ==========================================================================
   KIT — Door (Sign in, Join, Name)
   ========================================================================== */

/* Shared Body 600 (Type): people, goals, lesson names, lobby chips.
   Not a seventh role. Lives here because Sign in thesis uses it. */
.tb-practice__who-name,
.tb-character__name,
.tb-home__name,
.tb-goals__list > li,
.tb-landing__thesis,
.tb-lobby__names,
.tb-review-lesson,
.tb-settings__fact {
  font-family: var(--tb-font);
  font-size: var(--tb-text);
  font-weight: var(--tb-weight-semibold);
  letter-spacing: var(--tb-track);
}

.tb-landing {
  position: relative;
  min-height: 560px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--tb-section-gap);
  padding: var(--tb-page-pad);
  text-align: center;
  background: var(--tb-booth);
  color: var(--tb-on-booth);
  font-family: var(--tb-font);
  -webkit-font-smoothing: antialiased;
  /* Overlay host, same as .tb-page. The App menu pins here, not the gallery. */
  container-type: inline-size;
  container-name: tb-screen;
}

.tb-landing__thesis {
  margin: 0;
  max-width: 16em;
  line-height: 1.35;
}

/* Sign in reuses the door. Stack gap lives on .tb-stack, same as Landing. */
.tb-landing__auth {
  width: 100%;
}

.tb-landing__form {
  width: 100%;
  text-align: left;
}

/* Have a code? on Sign in. Join brick on the code page. Quiet Hint. */
.tb-landing__code {
  margin-top: calc(-1 * var(--tb-space-2));
  padding: 0;
  min-height: 0;
  height: auto;
  font-size: var(--tb-text-sm);
  font-weight: var(--tb-weight-medium);
  color: var(--tb-quiet);
}

.tb-landing__code:hover {
  color: var(--tb-quiet-hover);
}


/* ==========================================================================
   KIT — Partner
   ========================================================================== */

.tb-practice > .tb-practice__who {
  --tb-who-face: var(--tb-who-call);
  order: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  /* Take the leftover between Goals (or the topbar) and talk.
     Empty Goals still leaves this band, so the circle does not hug the top. */
  flex: 1 1 0;
  min-height: calc(var(--tb-who-call-min) + var(--tb-space-2) + 1.2em);
  width: 100%;
  margin-bottom: var(--tb-section-gap);
  text-align: center;
}

.tb-practice:has(> .tb-goals[hidden]) > .tb-practice__who {
  margin-bottom: var(--tb-section-gap);
}

.tb-practice .tb-practice__who-face {
  border-radius: 50%;
  flex: 1 1 0;
  width: auto;
  height: auto;
  aspect-ratio: 1;
  min-width: var(--tb-who-call-min);
  min-height: var(--tb-who-call-min);
  max-width: var(--tb-who-call);
  max-height: var(--tb-who-call);
  object-fit: cover;
}

/* Call viz: round-cap lines around the Practice circle while the
   partner line is playing. Same family as Hold to talk. The wrap is
   the portrait plus pad. The face is the call circle, centered, not
   the padded box. Inner end of each line is that circle. A hole mask
   the size of the face keeps the photo clear. Experience: family,
   ticks, wave. */
.tb-practice:has(.tb-who-viz),
.tb-practice > .tb-practice__who:has(.tb-who-viz) {
  overflow: visible;
}

.tb-practice > .tb-practice__who:has(.tb-who-viz) {
  min-height: calc(var(--tb-who-call-min) + var(--tb-who-viz-pad) * 2 + var(--tb-space-2) + 1.2em);
}

.tb-practice .tb-who-viz {
  --tb-who-viz-hole: calc(50% - var(--tb-who-viz-pad));
  position: relative;
  box-sizing: border-box;
  flex: 1 1 0;
  display: grid;
  place-items: center;
  overflow: visible;
  width: auto;
  height: auto;
  aspect-ratio: 1;
  min-width: calc(var(--tb-who-call-min) + var(--tb-who-viz-pad) * 2);
  min-height: calc(var(--tb-who-call-min) + var(--tb-who-viz-pad) * 2);
  max-width: calc(var(--tb-who-call) + var(--tb-who-viz-pad) * 2);
  max-height: calc(var(--tb-who-call) + var(--tb-who-viz-pad) * 2);
}

.tb-practice .tb-who-viz .tb-practice__who-face {
  position: relative;
  z-index: 1;
  display: block;
  width: calc(100% - var(--tb-who-viz-pad) * 2);
  height: calc(100% - var(--tb-who-viz-pad) * 2);
  max-width: var(--tb-who-call);
  max-height: var(--tb-who-call);
}

.tb-who-viz__ring,
.tb-who-viz__svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
  -webkit-mask-image: radial-gradient(
    circle at center,
    transparent var(--tb-who-viz-hole),
    #000 var(--tb-who-viz-hole)
  );
  mask-image: radial-gradient(
    circle at center,
    transparent var(--tb-who-viz-hole),
    #000 var(--tb-who-viz-hole)
  );
}

.tb-who-viz__bar {
  position: absolute;
  left: 50%;
  top: 50%;
  width: var(--tb-who-viz-bar-w, var(--tb-ptt-bar-w));
  height: var(--len, var(--tb-who-viz-min));
  margin-left: calc(var(--tb-who-viz-bar-w, var(--tb-ptt-bar-w)) / -2);
  margin-top: calc(var(--len, var(--tb-who-viz-min)) * -1);
  transform-origin: 50% 100%;
  transform: rotate(var(--a, 0deg)) translateY(calc(-1 * var(--r, 0px)));
  border-radius: 999px;
  background: var(--tb-on-booth);
}

.tb-who-viz--ticks .tb-who-viz__bar {
  --tb-who-viz-bar-w: var(--tb-who-viz-tick-w);
}

.tb-who-viz__svg {
  width: 100%;
  height: 100%;
}

.tb-who-viz__path {
  fill: none;
  stroke: var(--tb-on-booth);
  stroke-width: var(--tb-ptt-bar-w);
  stroke-linecap: round;
  stroke-linejoin: round;
}

.tb-practice .tb-practice__who-id {
  align-self: center;
  align-items: center;
  min-height: 0;
  margin-top: var(--tb-space-2);
}

.tb-practice .tb-practice__who-top {
  justify-content: center;
}

/* Chat: personality lives on the profile card, not under the circle. */
.tb-practice .tb-practice__who-bio {
  display: none;
}

/* Partner identity: a profile, not a chip. Rounded-square portrait
   (`--tb-who-portrait`), then one text column on a single left edge:
   the name (Body) with the dialect flag, remaining time at the end of
   that row when live, then the personality as the full Hint sentence
   (one sentence, wrapping if needed). The square is one size. Chat
   does not paint A2. Chat hides the personality under the circle; the
   profile card shows it. Lobby, Monitor, and Review keep Hint Level A2
   after the flag. Level is not under the bio. The stack is vertically
   centered with the square. Practice itself is the call circle
   (`--tb-who-call`), not this square. Create, Review, and (folded)
   Home keep the square. Create shows that same full bio, editable,
   no level on the person (Level is the dropdown). */
.tb-practice__who {
  --tb-who-face: var(--tb-who-portrait);
  display: grid;
  grid-template-columns: var(--tb-who-face) minmax(0, 1fr);
  column-gap: var(--tb-space-3);
  align-items: center;
  min-width: 0;
  flex-shrink: 0;
}

.tb-practice__who-face {
  display: block;
  flex-shrink: 0;
  align-self: center;
  width: var(--tb-who-face);
  height: var(--tb-who-face);
  min-width: var(--tb-who-face);
  max-width: var(--tb-who-face);
  min-height: var(--tb-who-face);
  max-height: var(--tb-who-face);
  object-fit: cover;
  border-radius: var(--tb-radius-lg);
  box-shadow: inset 0 0 0 2px var(--tb-hair);
  transition: opacity var(--tb-move) var(--tb-move-ease);
}

/* Chat: the face is a control. Click opens the profile card. Create
   still uses Photo. Home who is pointer-events none. */
.tb-practice .tb-practice__who-face {
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.tb-practice .tb-practice__who-face:hover,
.tb-practice .tb-practice__who-face:focus-visible {
  opacity: 0.82;
}

.tb-practice .tb-practice__who-face:focus-visible {
  outline: 2px solid var(--tb-on-booth);
  outline-offset: 3px;
}

.tb-practice__who-id {
  display: flex;
  flex-direction: column;
  justify-content: center;
  row-gap: var(--tb-space-1);
  min-height: var(--tb-who-face);
  min-width: 0;
  overflow: visible;
}

.tb-practice__who-top {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: var(--tb-space-2);
  min-width: 0;
  min-height: 0;
}

.tb-practice__who-name {
  margin: 0;
  display: block;
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  line-height: 1.25;
  color: var(--tb-on-booth);
}

/* Level A2 sat on the name row after the flag on Lobby and class Review.
   Hint. Not under the bio. Practice / Chat / personal Review hide it. */
.tb-practice__who-top > .tb-practice__who-level {
  margin: 0;
  flex: 0 0 auto;
  min-width: 0;
  font-style: normal;
  line-height: 1.2;
  color: var(--tb-quiet);
  white-space: nowrap;
}

.tb-practice__who-top > .tb-practice__who-level[hidden] {
  display: none;
}

/* Chat / Practice / personal Review: CEFR stays in the DOM for data. Do not paint A2. */
.tb-practice .tb-practice__who-top > .tb-practice__who-level,
#review .tb-review-page .tb-practice__who-top > .tb-practice__who-level {
  display: none;
}

/* Dialect flag sits after the name on that same row. Picture only: not
   a voice control. No language name. Class Review kept Level A2 after
   the flag. Chat and personal Review do not. Flag stays the picture: no
   border, no radius, no plate. */
.tb-practice__who-lang {
  margin: 0;
  padding: 0;
  display: flex;
  flex: 0 0 auto;
  flex-wrap: nowrap;
  align-items: center;
  column-gap: 0;
  min-width: 0;
  pointer-events: none;
  cursor: default;
}

/* Language name stays in the DOM for data. Hide it on the person.
   Hide the speak wrapper too, or an empty gap sits after the flag.
   Dialect name is already hidden. A2 in speak is data. Lobby paints
   Level A2 after the flag. Chat does not. */
.tb-practice__who .tb-practice__who-speak,
.tb-practice__who .tb-practice__who-label,
.tb-practice__who-speak .tb-practice__who-level {
  display: none;
}

/* Comma only if both language and dialect names show. Default hides the dialect name. */
.tb-practice__who-label:not([hidden]) + .tb-practice__who-dialect::before {
  content: ", ";
}

/* Personality is the full Hint sentence. One sentence, wrapping if it
   needs the next line. No clamp. No ...more. Create stays editable. */
.tb-practice__who-bio {
  min-width: 0;
  overflow: visible;
  text-decoration: none;
}

.tb-practice__who-blurb {
  margin: 0;
  line-height: 1.25;
  color: var(--tb-quiet);
  overflow-wrap: break-word;
  white-space: normal;
}

.tb-goals__top,
.tb-who__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--tb-space-2);
  width: 100%;
  height: var(--tb-icon);
}

/* Type labels Talk | Game. Hide the empty refresh row until Change shows. */
.tb-who__top:not(:has(.tb-who__new:not([hidden]))) {
  display: none;
}

.tb-goals__top .tb-goals__fact,
.tb-goals__top .tb-goals__fact-label,
.tb-goals__top .tb-goals__add,
.tb-who__top .tb-who__new {
  margin-left: auto;
  text-align: right;
}

.tb-goals__add,
.tb-who__new,
.tb-voice__redo {
  appearance: none;
  -webkit-appearance: none;
  width: auto;
  height: auto;
  min-height: 0;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  box-shadow: none;
  text-transform: none;
  line-height: 1;
  color: var(--tb-quiet);
  cursor: pointer;
}

/* Speaking partner redo. Lucide refresh-cw, Hint quiet, same
   row as Add. Not the word Change. Not a 48px brick. */
.tb-who__new {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tb-icon);
  min-width: var(--tb-icon);
  min-height: var(--tb-icon);
}

.tb-who__new i,
.tb-who__new svg {
  display: block;
  width: var(--tb-icon-sm);
  height: var(--tb-icon-sm);
}

.tb-goals__add:hover,
.tb-goals__add:focus-visible,
.tb-who__new:hover,
.tb-who__new:focus-visible,
.tb-voice__redo:hover,
.tb-voice__redo:focus-visible {
  color: var(--tb-quiet-hover);
}

.tb-home__activity .tb-practice__who {
  display: contents;
  pointer-events: none;
}

.tb-home__activity .tb-practice__who-face {
  grid-column: 1;
  grid-row: 1;
  align-self: center;
  justify-self: start;
}

/* Home fold: partner name, flag, and bio stay off the row. */
.tb-home__activity .tb-practice__who-id {
  display: none;
}

.tb-who__body {
  display: flex;
  flex-direction: column;
  gap: var(--tb-space-3);
}

.tb-who__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--tb-space-3);
  align-items: start;
}

.tb-who__row[hidden],
.tb-who__new[hidden] {
  display: none !important;
}

/* One grid cell so Create stays a square brick at the bottom while the
   prompt grows. Overlay sits on that same cell: empty examples swipe
   here. Not display:contents, or the cycle would steal the brick column. */
.tb-who__field {
  position: relative;
  min-width: 0;
  display: grid;
}

.tb-who__prompt {
  grid-area: 1 / 1;
  font-weight: var(--tb-weight-bold);
  min-height: var(--tb-btn-h);
  height: var(--tb-btn-h);
  min-width: 0;
}

/* Empty partner is one control. A long e.g. placeholder must not wrap
   the field taller than Language and Level. */
.tb-who__prompt:placeholder-shown {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Empty Speaking partner: four examples swipe up over --tb-move.
   Quiet Hint type, not typed Body. Pointer-events none so the field
   still takes the click. Hidden when they type. */
.tb-who__cycle {
  grid-area: 1 / 1;
  display: grid;
  align-items: start;
  pointer-events: none;
  overflow: hidden;
  min-height: var(--tb-btn-h);
  padding: var(--tb-space-3) var(--tb-space-4);
  color: var(--tb-quiet);
  font-family: var(--tb-font);
  font-size: var(--tb-text);
  font-weight: var(--tb-weight-medium);
  letter-spacing: var(--tb-track);
  line-height: 1.45;
}

.tb-who__cycle[hidden] {
  display: none !important;
}

.tb-who__cycle-line {
  grid-area: 1 / 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transform: translateY(0);
  opacity: 1;
  transition: transform var(--tb-move) var(--tb-move-ease),
    opacity var(--tb-move) var(--tb-move-ease);
}

.tb-who__cycle-line.is-next {
  transform: translateY(100%);
  opacity: 0;
  transition: none;
}

.tb-who__cycle-line.is-leave {
  transform: translateY(-100%);
  opacity: 0;
}

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

}

/* Character reads as a person's profile, not a third stack of glass fields:
   face, the name as Body, one quiet voice fact, then the bio as prose.
   Additional instructions sit under that person as a labeled wash field. */
.tb-character {
  display: flex;
  flex-direction: column;
  gap: var(--tb-space-4);
  overflow: visible;
  position: relative;
  flex-shrink: 0;
}

.tb-character[hidden] {
  display: none !important;
}

.tb-character .tb-practice__who-top {
  flex-wrap: nowrap;
}

.tb-character .tb-practice__who-lang {
  appearance: none;
  -webkit-appearance: none;
  font: inherit;
  font-family: inherit;
  font-size: inherit;
  font-weight: inherit;
  letter-spacing: inherit;
  line-height: inherit;
  color: inherit;
  margin: 0;
  padding: 0;
  border: none;
  background: none;
  box-shadow: none;
  text-align: left;
  cursor: default;
  pointer-events: none;
}

.tb-character .tb-practice__who-name {
  outline: none;
  cursor: text;
  box-shadow: inset 0 -2px 0 transparent;
  transition: box-shadow var(--tb-move) var(--tb-move-ease);
}

.tb-character .tb-practice__who-name:hover {
  box-shadow: inset 0 -2px 0 var(--tb-hair);
}

.tb-character .tb-practice__who-name:focus {
  box-shadow: inset 0 -2px 0 var(--tb-ritual);
}

.tb-character .tb-practice__who-bio {
  display: block;
  overflow: visible;
  cursor: text;
}

.tb-character .tb-practice__who-blurb {
  display: block;
  flex: none;
  width: 100%;
  white-space: normal;
  overflow: visible;
  outline: none;
  cursor: text;
}

.tb-character .tb-practice__who-top > .tb-practice__who-level {
  display: none;
}

/* Coaching for this person. Same wash field as Speaking partner.
   Grows with the text, then caps at five --tb-btn-h. Wheel and
   touch scroll. Quiet Hint-color rail. No chrome. */
.tb-character__notes {
  width: 100%;
  flex-shrink: 0;
}

.tb-who-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  row-gap: var(--tb-space-2);
  min-width: 0;
  text-align: center;
}

.tb-pop[data-pop="face"] .tb-practice__who-top {
  justify-content: center;
  max-width: 100%;
}

.tb-pop[data-pop="face"] .tb-practice__who-top[hidden] {
  display: none;
}

/* The name has room here, so it wraps instead of clipping to an ellipsis. */
.tb-pop[data-pop="face"] .tb-practice__who-name {
  overflow: visible;
  text-overflow: clip;
  white-space: normal;
}

.tb-pop[data-pop="face"] .tb-practice__who-blurb[hidden],
.tb-pop[data-pop="face"] .tb-practice__who-name:empty {
  display: none;
}

.tb-lobby__who .tb-practice__who {
  max-width: 100%;
}

.tb-monitor-scroll .tb-practice__who,
.tb-monitor-scroll .tb-goals,
.tb-monitor-scroll .tb-seats {
  flex-shrink: 0;
}

.tb-monitor-scroll .tb-practice__who {
  max-width: var(--tb-lobby);
}


/* ==========================================================================
   KIT — Goals
   ========================================================================== */

.tb-practice > .tb-goals {
  order: 1;
  flex: 0 0 auto;
  margin-bottom: var(--tb-section-gap);
}

/* Goals — one component, same idea as .tb-practice__who. One tree.
   Create is a wash field like Speaking partner. Live is mark +
   sentence. Class Review adds A B C and n/total. Not XP, not a bar. */
.tb-goals {
  --tb-goals-mark: 1.1em;
  --tb-goals-circle: 0.85em;
  --tb-goals-fact: minmax(4.5em, auto);
  --tb-goals-cols: var(--tb-goals-mark) minmax(0, 1fr);
  display: flex;
  flex-direction: column;
  gap: var(--tb-label-gap);
  flex-shrink: 0;
  width: 100%;
}

.tb-goals[hidden] {
  display: none !important;
}

.tb-goals--edit {
  --tb-goals-cols: minmax(0, 1fr) auto;
}

.tb-goals__fact-label {
  margin: 0;
}

.tb-goals__none {
  margin: 0;
}

.tb-goals__none[hidden] {
  display: none !important;
}

.tb-goals--empty .tb-goals__list {
  display: none;
}

.tb-goals__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--tb-space-1);
}

/* Catalog Practice: only the current I can…, plus ones already hit.
   Later goals stay off until this one is done. */
.tb-practice[data-reveal-goals] .tb-goals__list > li:not(.is-done):not(.is-now) {
  display: none;
}

.tb-goals--edit .tb-goals__list {
  gap: var(--tb-space-3);
}

.tb-goals__list > li {
  display: grid;
  grid-template-columns: var(--tb-goals-cols);
  column-gap: var(--tb-space-2);
  align-items: start;
  line-height: 1.25;
  color: var(--tb-on-booth);
}

.tb-goals--edit .tb-goals__list > li {
  /* X sits with the first line, not centered when the sentence wraps. */
  align-items: start;
}

.tb-goals__mark,
.tb-goals__line:not(textarea),
.tb-goals__fact {
  font: inherit;
  letter-spacing: inherit;
  line-height: inherit;
  color: inherit;
}

.tb-goals__mark {
  grid-column: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--tb-goals-mark);
  height: 1lh;
  min-height: 1lh;
  color: var(--tb-quiet);
}

.tb-goals--edit .tb-goals__mark {
  display: none;
}

.tb-goals__mark i,
.tb-goals__mark svg {
  display: block;
  width: 1em;
  height: 1em;
  margin: 0;
  /* Sit in the first line box (parent is 1lh, flex-centered).
     Do not translateY: a negative nudge parks the mark above the I can…. */
}

.tb-goals__list > li:not(.is-done) .tb-goals__mark:not([data-key]) i,
.tb-goals__list > li:not(.is-done) .tb-goals__mark:not([data-key]) svg {
  width: var(--tb-goals-circle);
  height: var(--tb-goals-circle);
}

.tb-goals__mark[data-key] {
  /* Class goal letters on Monitor. Live uses Lucide circle / check. */
  font-weight: var(--tb-weight-semibold);
}

.tb-goals--class .tb-goals__list > li.is-done .tb-goals__mark {
  color: inherit;
}

.tb-goals__mark[data-key="A"] { color: var(--tb-key-a); }

.tb-goals__mark[data-key="B"] { color: var(--tb-key-b); }

.tb-goals__mark[data-key="C"] { color: var(--tb-key-c); }

.tb-goals__mark[data-key="D"] { color: var(--tb-key-d); }

.tb-goals__mark[data-key="E"] { color: var(--tb-key-e); }

.tb-goals__list > li.is-done .tb-goals__mark:not([data-key]) {
  color: var(--tb-ritual);
}

.tb-goals__line {
  grid-column: 2;
  min-width: 0;
  margin: 0;
}

/* Create Goals is the same wash field as Speaking partner.
   Live sentences keep 1.25; the textarea must not inherit that or
   "I can…" sits high in the 48px well while Partner looks centered. */
.tb-goals--edit .tb-goals__line {
  grid-column: 1;
  min-height: var(--tb-btn-h);
  height: var(--tb-btn-h);
}

.tb-goals--edit .tb-goals__line:placeholder-shown {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.tb-goals__fact,
.tb-goals__fact-label {
  grid-column: 3;
  justify-self: end;
  margin: 0;
  font-variant-numeric: tabular-nums;
  font-style: normal;
  text-align: right;
  white-space: nowrap;
}

.tb-goals__list > li.is-done .tb-goals__line {
  color: var(--tb-quiet);
  font-weight: var(--tb-weight-medium);
}

.tb-goals__remove {
  grid-column: 2;
  justify-self: end;
  align-self: start;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  margin: var(--tb-space-3) 0 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--tb-quiet);
  cursor: pointer;
  appearance: none;
  -webkit-appearance: none;
}

.tb-goals__remove:hover,
.tb-goals__remove:focus-visible {
  color: var(--tb-quiet-hover);
}

.tb-goals__remove svg {
  display: block;
  width: 20px;
  height: 20px;
}

/* Goals plus: Lucide plus only, no Add word. Same --tb-icon as the
   X, pencil, and Speaking partner refresh. Right side of the row. */
.tb-goals__add {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  align-self: center;
  width: var(--tb-icon);
  min-width: var(--tb-icon);
  height: var(--tb-icon);
  min-height: var(--tb-icon);
  padding: 0;
  gap: 0;
  line-height: 0;
}

.tb-goals__add i,
.tb-goals__add svg {
  display: block;
  width: var(--tb-icon);
  height: var(--tb-icon);
  flex-shrink: 0;
}

/* Review flips Practice's emphasis. In Practice a done goal goes quiet to
   get out of the way. Here what they got is the point, so it stays bright
   and the open one stays calm. An open goal is not a failure. */
.tb-review-page .tb-goals__list > li.is-done .tb-goals__line {
  color: var(--tb-on-booth);
  font-weight: var(--tb-weight-semibold);
}

.tb-review-page .tb-goals__list > li:not(.is-done) .tb-goals__line {
  color: var(--tb-quiet);
  font-weight: var(--tb-weight-medium);
}

/* How many they got sits on the label row as Hint, right aligned like
   Add. Not a badge, not a pill, not a dangling stat under the list. */
.tb-review-page .tb-goals__top .tb-goals__fact {
  font-size: var(--tb-text-sm);
  font-weight: var(--tb-weight-medium);
  letter-spacing: var(--tb-track);
  color: var(--tb-quiet);
}

/* What they actually said, under the I can… it proved. Their own words in
   the lesson language, quiet. Same evidence as the Practice badge reason.
   One line per goal, not a transcript. */
.tb-goals__said {
  grid-column: 2;
  margin: 0;
  font-size: var(--tb-text-sm);
  font-weight: var(--tb-weight-medium);
  letter-spacing: var(--tb-track);
  line-height: 1.35;
  color: var(--tb-quiet);
}

/* Two-line goal rows need in-group air between them, and 4px inside a row. */
.tb-goals__list:has(.tb-goals__said) {
  gap: var(--tb-space-3);
}

.tb-goals__list > li:has(.tb-goals__said) {
  row-gap: var(--tb-space-1);
}

/* Lobby is not Create. Same list. Never an X. Never Add. */
.tb-lobby .tb-goals__remove,
.tb-lobby .tb-goals__add {
  display: none;
}


/* ==========================================================================
   KIT — Game
   ========================================================================== */

.tb-game {
  width: 100%;
  min-height: 18rem;
  height: 18rem;
  margin-top: var(--tb-label-gap);
  border-radius: var(--tb-control-radius);
  overflow: hidden;
  background: var(--tb-booth);
}

.tb-game[hidden] {
  display: none !important;
}

.tb-game__frame {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
  overflow: hidden;
  background: var(--tb-booth);
  pointer-events: auto;
}

.tb-create:has(.tb-game:not([hidden])) {
  flex: 1 1 auto;
  min-height: 0;
}

.tb-create .tb-who[data-prompt="game"]:has(.tb-game:not([hidden])) {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.tb-create .tb-who[data-prompt="game"]:has(.tb-game:not([hidden])) .tb-who__body {
  flex: 1 1 auto;
  min-height: 0;
}

.tb-create .tb-game:not([hidden]) {
  flex: 1 1 auto;
  min-height: 12rem;
  height: auto;
}

.tb-practice.is-game {
  max-width: none;
}

.tb-practice.is-game > .tb-practice__who,
.tb-practice.is-game > .tb-talk-wrap,
.tb-practice.is-game > .tb-talk-bar {
  display: none !important;
}

.tb-practice.is-game > .tb-game {
  order: 3;
  flex: 1 1 auto;
  min-height: 0;
  height: auto;
  margin-top: var(--tb-section-gap);
  margin-inline: calc(-1 * var(--tb-page-pad));
  margin-bottom: calc(-1 * var(--tb-page-pad));
  border-radius: 0;
  background: var(--tb-booth);
}

.tb-practice.is-game > .tb-goals--empty ~ .tb-game,
.tb-practice.is-game > .tb-goals[hidden] ~ .tb-game {
  margin-top: 0;
}

.tb-practice.is-game > .tb-game .tb-game__frame {
  background: var(--tb-booth);
}

.tb-home__game-face {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--tb-who-portrait);
  height: var(--tb-who-portrait);
  min-width: var(--tb-who-portrait);
  border-radius: 50%;
  background: var(--tb-wash);
  color: var(--tb-on-booth);
}

.tb-home__game-face svg {
  width: var(--tb-icon);
  height: var(--tb-icon);
}


/* ==========================================================================
   KIT — Talk
   ========================================================================== */

.tb-voice-pick .tb-lang-pick__btn > .tb-speak {
  margin-right: auto;
}

.tb-voice-pick__row .tb-speak {
  flex: 0 0 auto;
  margin: 0;
}

.tb-voice-pick .tb-voice-pick__voices .tb-voice-pick__row:has(.tb-lang-pick__option.is-active) .tb-speak {
  color: var(--tb-booth);
}

/* Practice is the whole page. Content lives on the play ground — a white card
   floating on purple made the one screen students live in feel like a form. */
.tb-practice {
  flex: 1;
  display: flex;
  flex-direction: column;
  /* Goals, then the circle and name as one unit, centered between
     Goals and talk, then a capped talk band. Section-gap under Goals
     and under the person. Section-gap above Hold to talk (margin on
     the talk bar). Dock pad stays even around the brick. */
  gap: 0;
  width: 100%;
  max-width: 520px;
  min-height: 0;
  margin: 0 auto;
  padding: 0;
  overflow: hidden;
  /* Hold and tap play the line. A native highlight fights that.
     Type mode is the one place they need to select. */
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

.tb-practice .tb-talk-wrap {
  order: 3;
  /* A capped band above Hold to talk. Do not grow against the circle. */
  flex: 0 1 var(--tb-talk-max);
  min-height: var(--tb-talk-min);
  max-height: var(--tb-talk-max);
  width: 100%;
}

.tb-practice .tb-talk-wrap > .tb-talk {
  min-height: 0;
}

.tb-practice .tb-talk {
  --tb-look-fade: calc(var(--tb-section-gap) * 2);
  --tb-look-fade-mask: linear-gradient(to bottom, rgb(0 0 0 / 0) 0, rgb(0 0 0) var(--tb-look-fade));
  flex: 1 1 auto;
  min-height: var(--tb-talk-min);
  -webkit-mask-image: var(--tb-look-fade-mask);
  mask-image: var(--tb-look-fade-mask);
}

.tb-practice > .tb-talk {
  order: 3;
  flex: 0 1 var(--tb-talk-max);
  min-height: var(--tb-talk-min);
  max-height: var(--tb-talk-max);
}

.tb-practice .tb-msg__who {
  display: none;
}

.tb-practice .tb-bubble:has(.tb-msg__who):not(:has(.tb-speak)) {
  gap: 0;
}

.tb-practice > .tb-talk-bar {
  order: 4;
  margin-top: var(--tb-section-gap);
}

.tb-practice--talk {
  max-width: 640px;
}

/* Chronological talk. Newest at bottom. This column is the only scroller.
   Stick-to-bottom law (do not break): pinned follows the latest line.
   Scroll up unlocks. Incoming partner lines do not yank.
   The student's own send, or the down arrow, pins again.
   Practice talk has no rail: wheel, touch, and the jump still scroll.
   Other product scrollers keep the quiet Hint-color rail. */
.tb-talk-wrap {
  position: relative;
  display: flex;
  flex-direction: column;
  min-height: 0;
  flex: 1;
  width: 100%;
}

.tb-talk {
  flex: 1;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  /* Gap 0 so the stick-to-bottom ::before does not park a 16px stripe
     under Goals. Turn change is margin on the next message. */
  gap: 0;
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
  /* Talk is full width of Practice. Page pad is the only inset. Extra
     inline pad is only the tail hang, so leftover air to the partner
     tail matches leftover air to the student tail. The rest above
     Hold to talk is section-gap on the talk bar, not padding here. */
  padding-left: var(--tb-bubble-tail);
  padding-right: var(--tb-bubble-tail);
  padding-bottom: 0;
}

.tb-talk::before {
  content: "";
  flex: 1 0 0;
  min-height: 0;
}

.tb-talk > .tb-msg + .tb-msg {
  margin-top: var(--tb-space-4);
}

/* Jump: white Lucide arrow in a circle. Same opaque fill as a partner
   bubble, not a dark plate and not a 48px FAB. Not a tb-btn. Not a
   yellow brick. */
.tb-talk__jump {
  --tb-bubble-fill: color-mix(in srgb, var(--tb-on-booth) 12%, var(--tb-booth));
  position: absolute;
  left: 50%;
  bottom: var(--tb-dock-pad);
  transform: translateX(-50%);
  z-index: 2;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: calc(var(--tb-icon) * 2);
  height: calc(var(--tb-icon) * 2);
  min-width: calc(var(--tb-icon) * 2);
  min-height: calc(var(--tb-icon) * 2);
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--tb-bubble-fill);
  box-shadow: none;
  color: var(--tb-on-booth);
  cursor: pointer;
  appearance: none;
  -webkit-appearance: none;
}

.tb-talk__jump:hover,
.tb-talk__jump:focus-visible {
  --tb-bubble-fill: color-mix(in srgb, var(--tb-on-booth) 18%, var(--tb-booth));
  background: var(--tb-bubble-fill);
  box-shadow: none;
  color: var(--tb-on-booth);
}

.tb-talk__jump svg {
  width: var(--tb-icon);
  height: var(--tb-icon);
}

.tb-talk__jump[hidden] {
  display: none !important;
}

.tb-msg {
  display: flex;
  flex-direction: column;
  gap: var(--tb-space-1);
  width: fit-content;
  max-width: 85%;
}

.tb-msg--partner {
  display: flex;
  flex-direction: column;
  gap: var(--tb-space-1);
  align-items: flex-start;
  align-self: flex-start;
  width: fit-content;
  max-width: 92%;
}

/* Goal done is a quiet badge under that student message: ritual check
   + the I can…. Not a plate. Not a chat bubble. Not a yellow brick.
   Click shows the reason; badge grows down. Hidden on Monitor tiles. */
.tb-msg__got {
  display: inline-flex;
  flex-direction: row;
  align-items: flex-start;
  gap: var(--tb-space-1);
  margin: var(--tb-space-1) 0 0;
  padding: 0;
  width: fit-content;
  max-width: 85%;
  box-sizing: border-box;
  border: 0;
  background: transparent;
  text-align: start;
  cursor: pointer;
}

.tb-msg__got-mark {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.1em;
  height: 1.25em;
  color: var(--tb-ritual);
}

.tb-msg__got-mark i,
.tb-msg__got-mark svg {
  display: block;
  width: 1em;
  height: 1em;
  margin: 0;
}

.tb-msg__got-line {
  margin: 0;
  min-width: 0;
  max-width: 100%;
  line-height: 1.25;
  color: var(--tb-quiet);
}

.tb-msg__got.is-en {
  cursor: pointer;
  overflow: visible;
  height: auto;
  align-items: flex-start;
}

.tb-msg__got-why {
  margin: 0;
  min-width: 0;
  max-width: 100%;
  color: var(--tb-quiet);
  text-align: start;
  line-height: 1.35;
  white-space: normal;
  overflow-wrap: anywhere;
}

.tb-msg__face {
  display: block;
  width: 40px;
  height: 40px;
  flex: none;
  align-self: end;
  object-fit: cover;
  /* Same visual roundness as the partner portrait: --tb-radius-lg on
     that square is ~27%, so --tb-radius (12px) on this 40px face.
     --tb-radius-lg here would read as a circle. */
  border-radius: var(--tb-radius);
  box-shadow: inset 0 0 0 2px var(--tb-hair);
}

.tb-msg__stack {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--tb-space-1);
  min-width: 0;
}

.tb-msg--you {
  align-self: flex-end;
  align-items: flex-end;
  gap: var(--tb-space-1);
}

.tb-msg--you .tb-msg__stack {
  align-items: flex-end;
  gap: var(--tb-space-1);
}

.tb-practice .tb-msg--you > .tb-msg__got {
  align-self: flex-end;
}

/* Who is speaking is signage, not the line. Partner and teacher only.
   Student turns have no You label. Side is enough. Teacher is ritual
   yellow because that is a different kind of speaker. */
.tb-msg__who {
  margin: 0;
  text-transform: none;
  color: var(--tb-quiet);
  line-height: 1.2;
}

/* Hear stays on the text row, vertically centered. Who sits on its own
   row above. Never a column sibling under the line. */
.tb-bubble:has(.tb-speak) {
  display: inline-flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: var(--tb-space-2);
}

.tb-bubble:has(.tb-msg__who):has(.tb-speak) {
  display: grid;
  grid-template-columns: minmax(0, max-content) auto;
  grid-template-areas:
    "who who"
    "body speak";
  align-items: center;
  column-gap: var(--tb-space-2);
  row-gap: var(--tb-space-1);
}

.tb-bubble:has(.tb-msg__who):not(:has(.tb-speak)) {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--tb-space-1);
}

.tb-bubble:has(.tb-msg__who):has(.tb-speak) > .tb-msg__who {
  grid-area: who;
}

.tb-bubble:has(.tb-msg__who):has(.tb-speak) > .tb-bubble__body {
  grid-area: body;
}

.tb-bubble:has(.tb-msg__who):has(.tb-speak) > .tb-speak {
  grid-area: speak;
}

.tb-msg--partner .tb-bubble,
.tb-msg--you .tb-bubble:not(.tb-bubble--live) {
  cursor: pointer;
}

.tb-bubble__body {
  min-width: 0;
  cursor: inherit;
  -webkit-tap-highlight-color: transparent;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: geometricPrecision;
  font-synthesis: none;
}

.tb-word {
  display: inline;
  margin: 0;
  padding: 0;
  border: 0;
  color: inherit;
  font: inherit;
  font-size: inherit;
  font-weight: inherit;
  font-style: inherit;
  line-height: inherit;
  letter-spacing: inherit;
  word-spacing: inherit;
  text-decoration: none;
  text-shadow: none;
  background: none;
  box-shadow: none;
  cursor: inherit;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

.tb-word.is-on,
.tb-word.is-open {
  color: var(--tb-ritual);
}

.tb-bubble:has(.tb-speak) > span:not(.tb-msg__who) {
  min-width: 0;
}

.tb-speak {
  flex: 0 0 auto;
  align-self: center;
  width: 28px;
  min-width: 28px;
  min-height: 28px;
  height: 28px;
  padding: 0;
  justify-content: center;
  color: var(--tb-quiet);
  opacity: 1;
  outline: none;
  box-shadow: none;
  border: none;
  -webkit-tap-highlight-color: transparent;
  user-select: none;
}

.tb-speak:focus,
.tb-speak:focus-visible,
.tb-speak:active {
  outline: none;
  box-shadow: none;
}

.tb-speak svg {
  width: 16px;
  height: 16px;
}

/* Pause is two solid bars, not Lucide's hollow rounded strokes.
   Same on Create Hear, the voice menu, word-tip Hear, and leftover dialect play. */
.tb-speak[aria-pressed="true"] svg,
.tb-voice__play[aria-pressed="true"] svg {
  fill: currentColor;
  stroke: none;
}

.tb-speak[aria-pressed="true"] svg rect,
.tb-voice__play[aria-pressed="true"] svg rect {
  fill: currentColor;
  stroke: none;
}

.tb-speak:hover,
.tb-speak.is-on {
  opacity: 1;
  color: var(--tb-on-booth);
}

.tb-bubble {
  position: relative;
  margin: 0;
  width: fit-content;
  max-width: 100%;
  box-sizing: border-box;
  padding: var(--tb-space-3) var(--tb-space-4);
  border-radius: var(--tb-radius-lg);
  font-size: var(--tb-text);
  font-weight: var(--tb-weight-bold);
  line-height: 1.35;
  color: var(--tb-on-booth);
  /* Partner and you share this fill. Side is who is speaking. */
  --tb-bubble-fill: color-mix(in srgb, var(--tb-on-booth) 12%, var(--tb-booth));
  --tb-bubble-tilt: 4deg;
  background: var(--tb-bubble-fill);
  transform-origin: 50% 80%;
}

/* Tap to play: a 4deg lean, left or right at random, then settle.
   Same --tb-move as the jump slide. Not a loop. Hold-to-see does not tilt. */
.tb-bubble.is-tilt {
  animation: tb-bubble-tilt var(--tb-move) var(--tb-move-ease);
}

@media (prefers-reduced-motion: reduce) {
  .tb-bubble.is-tilt { animation: none; }

}

.tb-msg--partner .tb-msg__stack > .tb-bubble:last-of-type,
.tb-msg--partner > .tb-bubble:last-of-type {
  border-bottom-left-radius: 0;
}

.tb-msg--partner .tb-msg__stack > .tb-bubble:last-of-type::after,
.tb-msg--partner > .tb-bubble:last-of-type::after {
  content: "";
  position: absolute;
  left: calc(-1 * var(--tb-bubble-tail));
  bottom: 0;
  width: calc(var(--tb-bubble-tail) + var(--tb-bubble-tail-stem));
  height: 13px;
  background: var(--tb-bubble-fill);
  transform: scaleX(-1);
  clip-path: path("M 0 0 L 2 0 C 7 6 10 10 12 13 L 2 13 L 0 13 Z");
}

.tb-msg--you .tb-msg__stack > .tb-bubble:last-of-type,
.tb-msg--you > .tb-bubble:last-of-type {
  border-bottom-right-radius: 0;
}

.tb-msg--you .tb-msg__stack > .tb-bubble:last-of-type::after,
.tb-msg--you > .tb-bubble:last-of-type::after {
  content: "";
  position: absolute;
  right: calc(-1 * var(--tb-bubble-tail));
  bottom: 0;
  width: calc(var(--tb-bubble-tail) + var(--tb-bubble-tail-stem));
  height: 13px;
  background: var(--tb-bubble-fill);
  clip-path: path("M 0 0 L 2 0 C 7 6 10 10 12 13 L 2 13 L 0 13 Z");
}

/* Older lines keep .tb-bubble type. Do not fade or thin them. */

/* Partner is still loading this turn, or the student is speaking
   while they record. Three quiet dots in the talk column,
   not a fake bubble. Partner left, student right. No fill, no tail,
   no name, no Hear. */
.tb-msg--typing {
  align-self: flex-start;
  align-items: flex-start;
  width: fit-content;
  max-width: none;
  padding: 0;
  background: none;
  border: 0;
  box-shadow: none;
  color: var(--tb-quiet);
}

.tb-msg--typing.tb-msg--you {
  align-self: flex-end;
  align-items: flex-end;
}

/* Live type-out while listening. Provisional. Same bubble, not an error.
   No empty pill: the bubble only exists once there is text. Until then
   the student-side dots sit in the talk column.
   Locked VAD segments are Body white. The current partial is Hint quiet
   (`--tb-quiet`), same 16 / same weight, so the line does not jump when
   that segment commits. */
.tb-bubble--live:has(.tb-bubble__body:empty),
.tb-bubble--live:empty {
  display: none;
}

.tb-bubble--live .tb-bubble__sure {
  color: var(--tb-on-booth);
}

.tb-bubble--live .tb-bubble__guess {
  color: var(--tb-quiet);
}

/* Whole-line English. Same 16 as Body, Hint quiet color, so the
   bubble does not jump and the original is obviously gone. */
.tb-bubble.is-en .tb-bubble__body {
  color: var(--tb-quiet);
}

.tb-bubble--live .tb-bubble__sure + .tb-bubble__guess::before {
  content: " ";
}

.tb-typing {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 1.35em;
  padding: 2px 0;
}

.tb-typing span {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.35;
  animation: tb-typing 1.05s ease-in-out infinite;
}

.tb-typing span:nth-child(2) { animation-delay: 0.15s; }

.tb-typing span:nth-child(3) { animation-delay: 0.3s; }

@media (prefers-reduced-motion: reduce) {
  .tb-typing span {
    animation: none;
    opacity: 0.7;
    transform: none;
  }

}

/* Practice talk bar: Hold to talk is full width. No type compose on
   student Practice. Same even dock-pad as Home / Create / Lobby. Brick
   lip lives on the button. In-flow under talk, never sticky/fixed over
   the last bubble. iOS Safari already insets the visual viewport for
   its toolbar; env(safe-area-inset-bottom) here doubled that and left
   a second purple stripe above the chrome. Section-gap above the brick
   stays on .tb-practice > .tb-talk-bar. */
.tb-talk-bar {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0;
  width: 100%;
  max-width: 28em;
  margin: 0 auto;
  flex-shrink: 0;
  min-height: calc(var(--tb-talk-bar-h) + (2 * var(--tb-dock-pad)));
  position: relative;
  z-index: 5;
  padding-top: var(--tb-dock-pad);
  padding-bottom: var(--tb-dock-pad);
  background: var(--tb-booth);
}

.tb-talk-bar:has(.tb-voice-note) {
  gap: var(--tb-label-gap);
}

/* Listening: same button, same size, same yellow brick, same place.
   Idle is a filled Lucide mic, centered. Listening hides it for ~19 bars.
   Tap latches. Hold is push-to-talk. */
.tb-ptt {
  position: relative;
  width: 100%;
  min-width: 0;
  min-height: calc(var(--tb-talk-bar-h) - var(--tb-brick-lip));
  height: calc(var(--tb-talk-bar-h) - var(--tb-brick-lip));
  justify-content: center;
  gap: 0;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
}

.tb-ptt__mic {
  position: relative;
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  width: var(--tb-ptt-viz-h);
  height: var(--tb-ptt-viz-h);
  color: var(--tb-ink);
}

.tb-ptt.is-on .tb-ptt__mic {
  display: none;
}

.tb-ptt__viz {
  display: none;
  align-items: center;
  justify-content: center;
  gap: var(--tb-ptt-bar-gap);
  height: var(--tb-ptt-viz-h);
  pointer-events: none;
}

.tb-ptt.is-on .tb-ptt__viz {
  display: inline-flex;
}

.tb-ptt__viz > span {
  display: block;
  flex-shrink: 0;
  width: var(--tb-ptt-bar-w);
  height: calc(var(--h, 0.16) * var(--tb-ptt-viz-h));
  min-height: var(--tb-ptt-bar-w);
  border-radius: 999px;
  background: var(--tb-ink);
  /* Height is live --h from the mic. JS eases attack and release.
     A CSS transition here would lag speech. */
}

.tb-ptt.is-on:active:not(:disabled) {
  background: var(--tb-yellow);
}

.tb-ptt__mic svg {
  position: relative;
  z-index: 1;
  width: var(--tb-ptt-viz-h);
  height: var(--tb-ptt-viz-h);
  fill: none;
}

/* Capsule only. Cradle and stem stay strokes so the U does not blob. */
.tb-ptt__mic svg rect,
.tb-ptt__mic svg path:first-of-type {
  fill: currentColor;
}

.tb-character .tb-practice__who-top .tb-voice-pick--fact .tb-lang-pick__btn > .tb-speak svg {
  width: 1em;
  height: 1em;
}

.tb-voice-sit .tb-talk-bar {
  width: 100%;
  max-width: none;
}

/* Names stay off the Board. No roster on the wall. */
.tb-talk-bar[hidden] {
  display: none !important;
}

.tb-hold {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--tb-section-gap);
  width: 100%;
  max-width: var(--tb-column);
  margin: 0 auto;
}

.tb-hold__status {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  flex: 1;
  gap: var(--tb-section-gap);
  width: 100%;
  min-height: 0;
}

/* Waiting fills the talk slot on the call page. */
.tb-practice > .tb-hold {
  order: 3;
  flex: 1 1 auto;
  justify-content: flex-end;
  min-height: 0;
  gap: 0;
  max-width: none;
}

.tb-practice > .tb-hold .tb-hold__status {
  flex: 1;
}

/* Teacher line in a student thread. Monitor Chat send. */
.tb-msg--teacher {
  align-self: flex-start;
  align-items: flex-start;
}

.tb-msg--teacher .tb-msg__who,
.tb-msg--teacher .tb-bubble > .tb-msg__who {
  color: var(--tb-ritual);
}

.tb-msg--teacher > .tb-bubble:last-of-type {
  border-bottom-left-radius: 0;
}

.tb-bubble--teacher,
.tb-msg--teacher .tb-bubble {
  --tb-bubble-fill: color-mix(in srgb, var(--tb-care) 32%, var(--tb-booth));
  background: var(--tb-bubble-fill);
}

.tb-msg--teacher > .tb-bubble:last-of-type::after {
  content: "";
  position: absolute;
  left: calc(-1 * var(--tb-bubble-tail));
  bottom: 0;
  width: calc(var(--tb-bubble-tail) + var(--tb-bubble-tail-stem));
  height: 13px;
  background: var(--tb-bubble-fill);
  transform: scaleX(-1);
  clip-path: path("M 0 0 L 2 0 C 7 6 10 10 12 13 L 2 13 L 0 13 Z");
}


/* ==========================================================================
   SCREEN — Home
   ========================================================================== */

/* Page scrollers: hide the native bar so Windows never steals width from
   the right. The Hint overlay (tb-rail.js) paints in the right page-pad.
   Left and right content insets stay equal when the column overflows.
   Do not use scrollbar-gutter: stable: the UA thin-bar column is fatter
   than --tb-scroll and shoves the page off-center. Dropdowns stay
   overlay-on-plates. Talk has no rail. */
.tb-create-col,
.tb-home,
.tb-settings,
.tb-ai,
.tb-review-page,
.tb-notice,
.tb-lobby__scroll,
.tb-monitor-scroll {
  scrollbar-width: none;
}

/* Teacher Home: a fast, playful door into the next lesson. */
.tb-home {
  max-width: var(--tb-home);
  margin: 0 auto;
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: var(--tb-section-gap);
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--tb-booth);
}

.tb-home__block {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--tb-label-gap);
}

.tb-home__block[hidden],
.tb-home__activity[hidden] {
  display: none !important;
}

.tb-home__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--tb-space-2);
  width: 100%;
  min-height: var(--tb-text-label);
}

/* ----- My lessons -----
   Same units stacked. Show every saved lesson for that language.
   Home is the scroller. Quiet Hint-color rail. */
.tb-home__list,
.tb-home__recent {
  display: flex;
  flex-direction: column;
  gap: var(--tb-home-unit-gap);
  max-height: none;
  overflow: visible;
}

/* Hide the scroller when every saved row is gone so the label does not
   sit over an empty rail. Catalog hide when that language has no rows. */
.tb-home__recent:not(:has(.tb-home__activity:not([hidden]))),
.tb-home__list:empty {
  display: none;
}

/* Home dock: square gray Settings brick to the left of yellow
   Create. Gap is the dock's --tb-space-2. Both are bricks, same lip. */
.tb-home__go {
  display: flex;
  align-items: flex-end;
  gap: var(--tb-space-2);
  width: 100%;
}

/* Import from is parked off Home. CSS stays so the tip can land
   elsewhere later: ghost text plus a Lucide chevron, then a
   booth-deep tip of sources. Not a yellow brick. Not on the canvas. */
.tb-home__import {
  position: relative;
  width: 100%;
}

.tb-home__import-help {
  margin: 0;
  line-height: 1.35;
  color: var(--tb-quiet);
}

.tb-home__import-help[hidden] {
  display: none;
}

/* Portrait + text stack. Text column matches classes: Body title,
   then Hint line, gap --tb-space-1. Face must NOT span two rows or it
   stretches that gap past the token. */
.tb-home__activity {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  justify-items: start;
  column-gap: var(--tb-space-3);
  width: 100%;
  margin: 0;
  padding: 0;
  border: none;
  border-radius: var(--tb-control-radius);
  background: transparent;
  color: inherit;
  font-family: var(--tb-font);
  text-align: left;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.tb-home__activity:hover,
.tb-home__activity:focus-visible {
  background: transparent;
}

.tb-home__activity:hover .tb-home__region-name,
.tb-home__activity:focus-visible .tb-home__region-name {
  color: var(--tb-quiet);
}

.tb-home__activity [data-who] {
  display: contents;
}

/* Lesson name then dialect plus CEFR. */
.tb-home__activity-copy {
  grid-column: 2;
  grid-row: 1;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--tb-space-1);
  min-width: 0;
  width: 100%;
}

/* Body: lesson name. Second line is region flag + region + CEFR. */
.tb-home__name {
  margin: 0;
  padding: 0;
  min-width: 0;
  width: 100%;
  overflow-wrap: anywhere;
  line-height: 1.25;
  color: var(--tb-on-booth);
}

.tb-home__activity-copy > .tb-home__name {
  grid-column: unset;
  grid-row: unset;
  justify-self: unset;
}

/* Flag + dialect, including Standard, then a space and A2.
   One Hint line. No Level word. No extra gap. No middle dot.
   Never Upper Intermediate. */
.tb-home__meta {
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  align-items: center;
  min-width: 0;
  width: 100%;
  overflow: hidden;
}

.tb-home__region {
  display: inline-flex;
  flex: 0 1 auto;
  align-items: center;
  gap: var(--tb-space-2);
  min-width: 0;
}

.tb-home__region-name {
  margin: 0;
  padding: 0;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--tb-quiet);
  line-height: 1.2;
}

.tb-home__paste {
  min-height: 10em;
  max-height: 14em;
  overflow-y: auto;
  field-sizing: fixed;
}

.tb-home__paste[hidden] {
  display: none;
}

/* Room code on Lobby / Monitor */
.tb-lesson {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--tb-space-3);
  min-width: 0;
  font-size: var(--tb-text-sm);
}

.tb-lesson__code {
  font-weight: var(--tb-weight-black);
  letter-spacing: var(--tb-track-wide);
  color: var(--tb-on-purple);
}

.tb-monitor-head .tb-lesson {
  gap: var(--tb-space-2);
  flex-wrap: nowrap;
}


/* ==========================================================================
   SCREEN — Create
   ========================================================================== */

/* Create is the purple page. Nothing wraps the form; the fields are the
   page, and the yellow brick is the only thing shouting. Lobby stamps
   the same .tb-create block under the code. */
.tb-create-col {
  max-width: var(--tb-home);
  margin: 0 auto;
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: var(--tb-section-gap);
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.tb-create {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: var(--tb-section-gap);
  flex-shrink: 0;
}

/* Inside a pair is tighter. Between labeled fields is section-gap. */
.tb-create__compact {
  display: flex;
  flex-direction: column;
  gap: var(--tb-section-gap);
}

/* Goals stay a block of sentences. Create leftover chrome is hidden. */
.tb-lobby .tb-create {
  flex: none;
  max-width: none;
  gap: var(--tb-section-gap);
}

/* Create dock: Teacher mode ghost checkbox, then one yellow brick. */
.tb-create__teacher {
  display: inline-flex;
  align-items: center;
  gap: var(--tb-space-2);
}


/* ==========================================================================
   SCREEN — Settings
   ========================================================================== */

.tb-page--fill > .tb-ai,
.tb-page--fill > .tb-settings {
  flex: 1;
  min-height: 0;
}

/* Settings is Create's section grammar: a quiet label, then a wash
   control. Preferences (App language, Model configuration), then Extras
   (Submit your voice). Account (name, email, Sign out) sits in the dock
   above Back, same job as Teacher mode above Practice.
   --tb-drop-pad lives on .tb-drop. Repeat it here so Sign out inset
   matches the App language wash (space-4). */
.tb-settings {
  --tb-drop-pad: var(--tb-space-4);
  max-width: var(--tb-home);
  margin: 0 auto;
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: var(--tb-section-gap);
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-bottom: 0;
}

.tb-settings__section {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--tb-label-gap);
  width: 100%;
}

/* Account in the dock. 16px to Back, like Practice above Hold to talk. */
.tb-page:has(> .tb-settings) > .tb-dock {
  gap: var(--tb-section-gap);
}

.tb-settings__extras {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--tb-space-3);
  width: 100%;
}

.tb-settings__fact {
  margin: 0;
  color: var(--tb-on-booth);
}

/* Name and email are one fact in Account, so they sit tight.
   Sign out sits under them. App language and Model configuration sit under
   Preferences. Submit your voice is Extras. */
.tb-settings__id {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--tb-space-1);
  width: 100%;
}

/* Settings actions are Create wash controls: full width, --tb-btn-h,
   --tb-space-4 inset (same as closed Region), quiet Lucide then the words.
   App language is the picker. Sign out matches that closed face.
   App language and Model configuration are wash units under Preferences.
   Sign out is that same wash unit in Account. Submit your voice is that
   same wash unit in Extras. */
.tb-settings__control {
  width: 100%;
  align-self: stretch;
}

.tb-settings__unit {
  appearance: none;
  -webkit-appearance: none;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  align-self: stretch;
  gap: var(--tb-space-2);
  box-sizing: border-box;
  width: 100%;
  height: var(--tb-btn-h);
  min-height: var(--tb-btn-h);
  margin: 0;
  padding: 0 var(--tb-space-4);
  border: none;
  border-radius: var(--tb-control-radius);
  background: var(--tb-wash);
  color: var(--tb-on-booth);
  font-family: var(--tb-font);
  font-size: var(--tb-text);
  font-weight: var(--tb-weight-black);
  letter-spacing: var(--tb-track);
  text-align: left;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.tb-settings__unit:hover,
.tb-settings__unit:focus-visible {
  background: var(--tb-wash-hover);
  color: var(--tb-on-booth);
}

.tb-settings__unit svg {
  flex: none;
  display: block;
  margin-left: 0;
  width: var(--tb-icon);
  height: var(--tb-icon);
  color: var(--tb-quiet);
}

/* Settings Model configuration. Same jobs as the Experience sidebar,
   on play purple. Monthly cost first, then labeled task sections. */
.tb-ai {
  max-width: var(--tb-home);
  margin: 0 auto;
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: var(--tb-section-gap);
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.tb-ai__month {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--tb-label-gap);
}

.tb-ai__month-total {
  font-variant-numeric: tabular-nums;
}

.tb-ai__month > .tb-ai__details {
  width: 100%;
}

.tb-ai__month > .tb-ai__details > .tb-ai__break {
  margin-top: var(--tb-label-gap);
}

.tb-ai__break {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--tb-space-2);
  width: 100%;
}

.tb-ai__break li {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--tb-space-1) var(--tb-space-3);
  align-items: baseline;
}

.tb-ai__break-task {
  grid-column: 1;
  color: var(--tb-on-booth);
  font-size: var(--tb-text-sm);
  font-weight: var(--tb-weight-semibold);
  line-height: 1.3;
}

.tb-ai__break-pick {
  grid-column: 1;
  color: var(--tb-quiet);
  font-size: var(--tb-text-sm);
  font-weight: var(--tb-weight-medium);
  line-height: 1.3;
}

.tb-ai__break-amt {
  grid-column: 2;
  grid-row: 1 / span 2;
  align-self: center;
  color: var(--tb-on-booth);
  font-size: var(--tb-text-sm);
  font-weight: var(--tb-weight-black);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.tb-ai__break-amt.is-zero {
  color: var(--tb-quiet);
  font-weight: var(--tb-weight-medium);
}

.tb-ai__row {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--tb-label-gap);
}

.tb-ai__row > label {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--tb-space-2);
  margin: 0;
  color: var(--tb-on-booth);
  font-size: var(--tb-text);
  font-weight: var(--tb-weight-black);
  letter-spacing: var(--tb-track);
  line-height: 1.2;
}

.tb-ai__row > label > span {
  flex: none;
}

.tb-ai__row > label small {
  display: inline;
  margin: 0;
  min-width: 0;
  text-align: right;
  color: var(--tb-quiet);
  font-size: var(--tb-text-sm);
  font-weight: var(--tb-weight-medium);
  letter-spacing: 0;
}

.tb-ai__select {
  appearance: none;
  -webkit-appearance: none;
  display: block;
  box-sizing: border-box;
  width: 100%;
  height: var(--tb-btn-h);
  min-height: var(--tb-btn-h);
  margin: 0;
  padding: 0 var(--tb-space-4);
  border: none;
  border-radius: var(--tb-control-radius);
  background: var(--tb-wash);
  color: var(--tb-on-booth);
  font-family: var(--tb-font);
  font-size: var(--tb-text);
  font-weight: var(--tb-weight-black);
  letter-spacing: var(--tb-track);
  cursor: pointer;
  color-scheme: dark;
}

.tb-ai__select option,
.tb-ai__select optgroup {
  background: var(--tb-booth-deep);
  color: var(--tb-on-booth);
}

.tb-ai__select--proxy {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
  pointer-events: none;
}

.tb-ai__section {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--tb-label-gap);
  width: 100%;
}

.tb-ai__stack {
  display: flex;
  flex-direction: column;
  gap: var(--tb-section-gap);
  width: 100%;
}

.tb-ai__select:hover,
.tb-ai__select:focus-visible {
  background: var(--tb-wash-hover);
  outline: none;
}

.tb-ai__select:disabled {
  opacity: 0.45;
}

/* Cost/mo sits in the wash picker, to the right of the model name.
   Not a Hint line under the field. The select stays full width so
   a click on the cost still opens it. */
.tb-ai__pick {
  position: relative;
  display: block;
}

.tb-ai__cost {
  position: absolute;
  top: 0;
  bottom: 0;
  right: var(--tb-space-4);
  display: flex;
  align-items: center;
  margin: 0;
  pointer-events: none;
  color: var(--tb-quiet);
  font-size: var(--tb-text-sm);
  font-weight: var(--tb-weight-medium);
  font-variant-numeric: tabular-nums;
  line-height: 1;
  white-space: nowrap;
}

.tb-ai__cost[hidden] {
  display: none;
}

.tb-ai__details {
  margin: 0;
}

.tb-ai__details > summary {
  list-style: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: var(--tb-space-2);
  color: var(--tb-quiet);
  font-size: var(--tb-text-sm);
  font-weight: var(--tb-weight-semibold);
  user-select: none;
}

.tb-ai__details > summary::-webkit-details-marker,
.tb-ai__details > summary::marker {
  display: none;
  content: none;
}

.tb-ai__details > summary:hover {
  color: var(--tb-on-booth);
}

.tb-ai__ctx {
  display: grid;
  gap: var(--tb-space-4);
  margin-top: var(--tb-label-gap);
}

.tb-ai__k {
  margin: 0 0 var(--tb-space-2);
  color: var(--tb-quiet);
}

.tb-ai__json {
  margin: 0;
  padding: var(--tb-space-3);
  border-radius: var(--tb-control-radius);
  background: var(--tb-wash);
  color: var(--tb-on-booth);
  font-family: var(--tb-font);
  font-size: var(--tb-text-sm);
  font-weight: var(--tb-weight-medium);
  line-height: 1.4;
  white-space: pre-wrap;
  word-break: break-word;
}

.tb-ai__actions {
  margin-top: var(--tb-space-3);
}


/* ==========================================================================
   KIT — Pop and Tip
   ========================================================================== */

.tb-home__import .tb-tip {
  left: 0;
  right: auto;
  width: max-content;
  max-width: 100%;
  min-width: 0;
  --tb-tip-caret-x: 1.25rem;
}

[data-pop="import"] .tb-pop__card {
  display: flex;
  flex-direction: column;
  gap: var(--tb-space-4);
}

[data-pop="import"] .tb-pop__actions {
  margin-top: 0;
}

[data-pop="import"] [data-import-lang] {
  min-width: 0;
}

[data-pop="import"][data-import-mode="news"] .tb-pop__card {
  overflow: visible;
}

.tb-pop__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--tb-space-3);
  width: 100%;
  margin-top: var(--tb-space-4);
}

.tb-tip {
  position: absolute;
  right: 0;
  bottom: calc(100% + var(--tb-space-2));
  z-index: 50;
  display: grid;
  gap: var(--tb-label-gap);
  min-width: 7.5rem;
  padding: var(--tb-space-3) var(--tb-space-4);
  border-radius: var(--tb-control-radius);
  /* Drop menus use booth-deep. Wash is 12% white: the page shows through. */
  background: var(--tb-booth-deep);
  isolation: isolate;
  box-shadow: none;
  --tb-tip-caret-x: calc(100% - 1.25rem);
}

.tb-tip[hidden] {
  display: none;
}

/* Caret on the same booth-deep fill. Points at the control, not a hairline. */
.tb-tip::after {
  content: "";
  position: absolute;
  left: var(--tb-tip-caret-x);
  bottom: -5px;
  width: 10px;
  height: 10px;
  background: var(--tb-booth-deep);
  transform: translateX(-50%) rotate(45deg);
  pointer-events: none;
}

.tb-tip.is-flip::after {
  bottom: auto;
  top: -5px;
}

.tb-tip.tb-tip--word {
  position: absolute;
  right: auto;
  bottom: auto;
  z-index: 80;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  /* One vertical rhythm. Word, then English gloss. */
  gap: var(--tb-space-3);
  width: max-content;
  /* Beat .tb-tip min-width (7.5rem). A short word must not stretch the row. */
  min-width: 0;
  max-width: min(16rem, calc(100% - 24px));
  padding: var(--tb-space-4);
  /* Caret diamond sits 5px into the box. Last line keeps the same 16px air. */
  padding-bottom: calc(var(--tb-space-4) + 6px);
  --tb-tip-caret-x: 50%;
}

/* display:flex above is the same specificity as .tb-tip[hidden].
   Without this, click off sets hidden and the card stays painted. */
.tb-tip.tb-tip--word[hidden] {
  display: none;
}

.tb-tip--word.is-flip {
  padding-top: calc(var(--tb-space-4) + 6px);
  padding-bottom: var(--tb-space-4);
}

.tb-tip__head {
  display: flex;
  flex-wrap: nowrap;
  align-items: baseline;
  justify-content: flex-start;
  align-self: flex-start;
  gap: var(--tb-space-2);
  width: max-content;
  max-width: 100%;
  min-width: 0;
}

.tb-tip__word {
  margin: 0;
  flex: none;
  min-width: 0;
  font-size: var(--tb-text);
  font-weight: var(--tb-weight-semibold);
  line-height: 1.2;
  color: var(--tb-on-booth);
}

.tb-tip__type {
  margin: 0;
  margin-inline-start: 0;
  flex: none;
  line-height: 1.2;
  white-space: nowrap;
}

.tb-tip__glossary,
.tb-tip__base,
.tb-tip__empty {
  margin: 0;
  max-width: 100%;
  line-height: 1.2;
}

.tb-tip__glossary {
  color: var(--tb-on-booth);
}

/* Popup overlay. Veil is `.tb-pop`. The panel is `.tb-pop__card`: play
   purple, same ground as the page. The fade is see-through so the
   current screen stays visible. Never a solid plate. Use it for
   Preview, Photo pop on Create, Chat portrait view, Restart confirm,
   Monitor Chat and End, Home paste, other import options, and org
   Invite. Do not use it as page layout. */
.tb-pop-host {
  position: relative;
  overflow: hidden;
}

.tb-pop {
  position: absolute;
  inset: 0;
  /* Above the sticky dock and Monitor mosaic container. Same z as
     the dock lets Create lesson poke through the veil. */
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--tb-page-pad) var(--tb-page-pad);
  background: var(--tb-pop-veil);
  /* The fade is the dismiss control. Click it (not the card) closes
     every pop. Same for Preview, Photo, Restart, import, Invite. */
}

.tb-pop[data-pop="face"] {
  /* Chat profile: the card only. No shade over the sitting. */
  background: transparent;
}

.tb-pop[hidden] {
  display: none;
}

.tb-pop__card {
  position: relative;
  width: 100%;
  max-width: var(--tb-column);
  max-height: 100%;
  overflow-x: hidden;
  overflow-y: auto;
  background: var(--tb-booth);
  color: var(--tb-on-booth);
  border-radius: var(--tb-radius-lg);
  padding: var(--tb-page-pad);
}

/* Photo pop: the face they tapped, larger, same round square as Create.
   No hairline plate. Hidden until there is a picture. */
.tb-pop__photo {
  display: block;
  width: 10rem;
  height: 10rem;
  margin: 0 auto;
  object-fit: cover;
  border-radius: var(--tb-radius-lg);
}

.tb-pop__photo[hidden] {
  display: none;
}

.tb-pop[data-pop="portrait"] [data-portrait-note] {
  text-align: start;
  min-height: calc(var(--tb-btn-h) * 2);
}

/* Chat: tap the partner circle. Same person as the page, larger and
   readable: photo, name with the dialect flag, then the full role
   sentence. Play purple, the see-through fade is the frame. Not a
   white panel. Not a lightbox: the card body is for reading, so only
   the veil and Escape close. No X. Not Create Photo (that one has
   New photo / Keep this image). */
.tb-pop[data-pop="face"] .tb-pop__card {
  width: 100%;
  max-width: var(--tb-column);
  padding: var(--tb-page-pad);
  cursor: default;
}

/* Same circle as the call face, one control step larger. */
.tb-pop[data-pop="face"] .tb-pop__photo {
  width: min(calc(var(--tb-who-call) + var(--tb-btn-h)), 100%);
  height: auto;
  aspect-ratio: 1 / 1;
  margin: 0 0 var(--tb-space-2);
  border-radius: 50%;
}

.tb-pop__close {
  width: 44px;
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  color: var(--tb-on-booth);
  opacity: 1;
}

.tb-pop__close:hover {
  color: var(--tb-on-booth);
}

.tb-pop__close svg {
  width: 22px;
  height: 22px;
}

/* ==========================================================================
   SCREEN — Steps, Mic blocked, Ended
   ========================================================================== */

.tb-note {
  margin: var(--tb-space-2) 0 0;
  line-height: 1.45;
  text-wrap: pretty;
}

.tb-ended {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--tb-section-gap);
  width: 100%;
  max-width: var(--tb-column);
  margin: 0 auto;
}

/* ----- Mic blocked (student) -----
   Job is get the mic working. Chat is a quiet mode switch, never the
   urged escape hatch. No card, no red, no shame. */
.tb-blocked {
  position: relative;
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: var(--tb-space-4);
  width: 100%;
  max-width: var(--tb-column);
  margin: 0 auto;
}

.tb-blocked__icon {
  display: inline-flex;
  color: var(--tb-on-booth);
  opacity: 1;
}

/* The mic-off is the subject of the page, not a bullet beside the title. */
.tb-blocked__icon svg {
  width: calc(var(--tb-icon) * 2);
  height: calc(var(--tb-icon) * 2);
}

/* Numbered how-to. Mic blocked and Home import share this. */
.tb-steps,
.tb-blocked__steps {
  margin: 0;
  padding: 0;
  list-style: none;
  width: 100%;
  text-align: left;
  font-size: var(--tb-text-sm);
  font-weight: var(--tb-weight-medium);
  color: var(--tb-dim);
  line-height: 1.55;
}

.tb-steps[hidden],
.tb-blocked__steps[hidden] {
  display: none;
}

.tb-steps li,
.tb-blocked__steps li {
  display: flex;
  gap: var(--tb-space-2);
  padding: 4px 0;
}

.tb-steps span,
.tb-blocked__steps span {
  flex-shrink: 0;
  width: 1.25em;
  font-weight: var(--tb-weight-black);
  color: var(--tb-quiet);
  font-variant-numeric: tabular-nums;
}

.tb-notice {
  max-width: var(--tb-home);
  margin: 0 auto;
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: var(--tb-section-gap);
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-bottom: var(--tb-space-4);
}

.tb-notice__lede,
.tb-notice__updated {
  margin: 0;
}

.tb-notice section {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--tb-label-gap);
  width: 100%;
}

.tb-notice p {
  margin: 0;
  max-width: 36em;
}

.tb-notice a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

.tb-lobby .tb-note {
  margin-top: var(--tb-space-2);
}

.tb-restart {
  width: 100%;
  max-width: var(--tb-column);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--tb-section-gap);
}

.tb-restart__spin {
  display: block;
  width: 2rem;
  height: 2rem;
  margin: 0;
  line-height: 0;
  flex: none;
  border: 3px solid var(--tb-hair);
  border-top-color: var(--tb-ritual);
  border-radius: 50%;
  animation: tb-restart-spin 0.9s linear infinite;
}

@media (prefers-reduced-motion: reduce) {
  .tb-restart__spin { animation: none; }

}


/* ==========================================================================
   SCREEN — Review
   ========================================================================== */

/* Personal Lesson review after Practice. The page is the scroller. */
.tb-review-page {
  width: 100%;
  max-width: min(720px, 100%);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: var(--tb-section-gap);
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* Not sticky. Title, then partner, sit in this page scroller, not the topbar. */
.tb-review-head {
  display: flex;
  flex-direction: column;
  gap: var(--tb-space-4);
  flex-shrink: 0;
  position: static;
}

/* Personal Lesson review after Practice. The page says what it is, then
   which lesson, then the same partner. Goals split into two labeled blocks:
   what they got, then what is still open. No table. No score. No export. */
.tb-review-id {
  display: flex;
  flex-direction: column;
  /* Title and the lesson name are one lockup, tighter than a label gap. */
  gap: var(--tb-space-1);
  min-width: 0;
}

/* The lesson name is Body under the title, not a second title. */
.tb-review-lesson {
  margin: 0;
  line-height: 1.25;
  color: var(--tb-on-booth);
}

/* ==========================================================================
   SCREEN — Voice portal
   ========================================================================== */

.tb-voice__play.is-on {
  background: var(--tb-surface);
  color: var(--tb-booth);
}

.tb-voice__redo {
  display: inline-flex;
  align-items: center;
}

/* First sign-in: one question per page. Beta on Create uses `.tb-pop__card`.
   Same centered column as End all confirm. */
.tb-voice-start {
  width: 100%;
  max-width: var(--tb-column);
  margin: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--tb-space-4);
}

.tb-voice-start .tb-voice-lang,
.tb-voice-start .tb-voice-agree {
  width: 100%;
  text-align: left;
}

.tb-pop__card .tb-voice-start {
  max-width: none;
  margin: 0;
}

.tb-voice-lang {
  width: 100%;
}

.tb-voice-agree {
  width: 100%;
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--tb-space-3);
}

.tb-page--fill > .tb-voice-sit {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  width: 100%;
  margin: 0;
  padding: 0;
  border: none;
  border-radius: 0;
  background: transparent;
  cursor: default;
}

.tb-voice__top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--tb-space-3);
  flex-shrink: 0;
}

.tb-voice__line {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  min-height: 0;
  margin: 0;
  padding: var(--tb-section-gap) 0;
}

/* Privacy (and Terms on the door): underlined links.
   Sign in and Join: Privacy and Terms of service at the bottom, not in
   the sign-in stack. Not on Chat. Not on Name. No middle dot between the two. */
.tb-voice-note {
  display: block;
  margin: 0;
  padding: 0;
  width: 100%;
  min-height: 0;
  height: auto;
  font-size: var(--tb-text-sm);
  font-weight: var(--tb-weight-medium);
  line-height: 1.3;
  color: var(--tb-quiet);
  text-align: center;
  text-decoration: underline;
  text-underline-offset: 0.15em;
  background: transparent;
  box-shadow: none;
  text-wrap: balance;
}

.tb-voice-note:hover,
.tb-voice-note:focus-visible {
  color: var(--tb-quiet-hover);
  background: transparent;
}

.tb-voice-notes {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--tb-space-4);
  margin: 0;
}

.tb-voice-notes .tb-voice-note {
  width: auto;
}

.tb-landing:has(> .tb-voice-note),
.tb-landing:has(> .tb-voice-notes) {
  padding-bottom: calc(var(--tb-page-pad) + 2.75em);
}

.tb-landing > .tb-voice-note,
.tb-landing > .tb-voice-notes {
  position: absolute;
  left: var(--tb-page-pad);
  right: var(--tb-page-pad);
  bottom: var(--tb-page-pad);
  width: auto;
  max-width: 22em;
}


/* ==========================================================================
   SCREEN — Rooms (Code, Seats, Lobby, Board, Monitor)
   ========================================================================== */

/* Desk End all confirm matches the phone kill-switch grammar: the question
   owns the screen, with nothing under it to explain. */
.tb-monitor-confirm {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--tb-space-4);
  flex: 1;
  width: 100%;
  max-width: var(--tb-column);
  margin: 0 auto;
  text-align: center;
}

.tb-pop__card .tb-monitor-confirm {
  max-width: none;
  margin: 0;
}

/* ---------- Room (teacher-hosted period) ----------
   Join code, Lobby, Board, Monitor, class Review.
   Student chat is the same Practice call page. Names live on that
   Chromebook. Monitor is live glass: discard after Done.
   Restored from the 2026-09-06 classroom freeze.
   ---------------------------------------------- */
.tb-code {
  margin: 0;
  text-align: center;
}

.tb-code--on-play {
  color: var(--tb-ritual);
}

/* Code + QR as one join cluster. Code sits on the QR (in-group), not
   in the Talkbox title. Sizes stay on the tokens. Air stays air. */
.tb-board-join {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--tb-space-3);
  flex: 0 0 auto;
  width: 100%;
}

/* Giant code, centered. Lobby's code is the Open board brick. Do not shrink this. */
.tb-page--board-fill .tb-code {
  flex: 0 0 auto;
  font-size: var(--tb-text-lobby-code);
  line-height: 0.92;
  letter-spacing: var(--tb-track-wide);
}

/* Decent scan square. Quiet zone is in the SVG. No plate. */
.tb-board-qr-slot {
  flex: 0 0 auto;
  width: var(--tb-board-qr);
  aspect-ratio: 1;
  display: grid;
  place-items: center;
}

/* Inverted QR: white modules on play purple. */
.tb-board-qr {
  display: block;
  width: 100%;
  height: 100%;
  aspect-ratio: 1;
  object-fit: contain;
  padding: 0;
  margin: 0;
  color: var(--tb-on-booth);
  background: transparent;
  box-sizing: border-box;
  image-rendering: pixelated;
}

.tb-board-url {
  line-height: 1.1;
}

.tb-board-count {
  margin: 0;
  line-height: 1.2;
  color: var(--tb-quiet);
}

/* Doubled class so the stacked foot beats the centred base rule: the quiet
   moves belong on the column's left edge, under the names, not floating in
   the middle above the kill switch. */
.tb-monitor-foot.tb-monitor-foot--stack {
  flex-direction: column;
  align-items: stretch;
  gap: var(--tb-space-2);
}

.tb-lobby {
  flex: 1;
  min-height: 0;
  width: 100%;
  max-width: none;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  gap: 0;
  padding-top: 0;
  padding-bottom: 0;
  overflow: hidden;
}

.tb-lobby__scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-x: clip;
  overflow-y: auto;
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  gap: var(--tb-section-gap);
  width: 100%;
  max-width: 640px;
  margin-inline: auto;
}

.tb-lobby__ritual {
  display: flex;
  flex-direction: column;
  gap: var(--tb-section-gap);
}

.tb-lobby__code-row {
  display: flex;
  justify-content: flex-start;
  align-items: center;
  gap: var(--tb-space-3);
  width: 100%;
  max-width: 100%;
}

.tb-lobby__code {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--tb-section-gap);
}

/* Same height as Open board so they read as one row. Giant is Board only. */
.tb-lobby__ritual .tb-code {
  text-align: left;
  font-size: var(--tb-btn-h);
  line-height: 1;
}

/* Create leftover from the shared setup stamp. Lobby keeps Goals only. */
.tb-lobby__who {
  margin: 0;
}

/* Count left, time right. Time is a Hint control like the count, not a
   labeled Time section and not a yellow brick. */
.tb-lobby__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--tb-space-3);
  width: 100%;
  /* Hug the Hint line. This row inherits Body 16; a 16px strut would
     sit under the 14px count and throw the names off. */
  font-size: var(--tb-text-sm);
  line-height: 1;
}

.tb-lobby__time {
  appearance: none;
  -webkit-appearance: none;
  display: inline-flex;
  align-items: center;
  margin: 0;
  padding: 0;
  min-height: 0;
  border: none;
  border-radius: 0;
  background: none;
  box-shadow: none;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  color: var(--tb-quiet);
  text-align: right;
  white-space: nowrap;
  cursor: pointer;
}

.tb-lobby__time:hover,
.tb-lobby__time:focus-visible {
  color: var(--tb-quiet-hover);
  background: none;
  box-shadow: none;
  outline: none;
}

.tb-lobby__when {
  position: relative;
  flex-shrink: 0;
}

.tb-lobby__minutes {
  width: 100%;
  min-height: var(--tb-btn-h);
  text-align: center;
  font-variant-numeric: tabular-nums;
  /* Opaque wash-on-deep. Alpha wash here would punch a hole in the tip. */
  background: color-mix(in srgb, var(--tb-on-booth) 14%, var(--tb-booth-deep));
}

.tb-lobby__minutes:focus {
  background: color-mix(in srgb, var(--tb-on-booth) 20%, var(--tb-booth-deep));
}

/* Count, names, Start: one thumb-zone block. A long class scrolls
   inside the roster. One in-group gap above the names, between wrap
   rows, and above Start. Do not split those. */
.tb-lobby__roster {
  display: flex;
  flex-direction: column;
  max-height: 8.5rem;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  margin: 0;
}

/* The count is supporting copy, so it reads as one Hint line. The number
   carries weight, never a second size. */
.tb-lobby__count {
  margin: 0;
  flex-shrink: 0;
  line-height: 1;
  color: var(--tb-quiet);
  font-variant-numeric: tabular-nums;
}

.tb-lobby__count b {
  font-size: inherit;
  font-weight: var(--tb-weight-black);
  line-height: 1;
  color: inherit;
}

/* Names wrap as bubbles so thirty students scan as chips, not a gutter.
   Wrap gap is the same in-group as the dock stack, not a tighter row. */
.tb-lobby__names {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--tb-space-3);
  color: var(--tb-on-booth);
}

.tb-lobby__names li {
  display: inline-flex;
  align-items: center;
  padding: var(--tb-space-2) var(--tb-space-3);
  border-radius: var(--tb-radius-pill);
  background: var(--tb-wash);
  line-height: 1;
}

/* Count and time share the bar. Names and Start sit under. Start is the
   only yellow block. The band behind this block is the page ground, not
   a plate. Even dock-pad above the count and below the brick. Lip is
   on the button. In-group gap matches the wrapping names. */
.tb-lobby__go {
  margin-top: 0;
  display: flex;
  flex-direction: column;
  gap: var(--tb-space-3);
  flex-shrink: 0;
  overflow: visible;
  background: var(--tb-booth);
}

.tb-lobby__names .empty {
  color: var(--tb-quiet);
  font-weight: var(--tb-weight-medium);
  background: transparent;
  padding: var(--tb-space-2) 0;
}

/* Duration is a tb-mode pick: wash at rest, accent yellow when selected.
   Onboard keeps white. Create lesson and Start stay the yellow bricks. */
.tb-board-foot {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--tb-space-1);
}

.tb-monitor-page {
  width: 100%;
  max-width: none;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  flex: 1;
  /* Keep this layer under `.tb-pop`. */
  position: relative;
  z-index: 0;
}

.tb-monitor-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  gap: var(--tb-section-gap);
}

.tb-seats {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--tb-space-3);
}

.tb-seat {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--tb-space-3);
}

.tb-monitor-head {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  flex-shrink: 0;
  /* padding-block keeps top and bottom equal. Do not split.
     No extra margin: that read as header bottom pad. */
  padding-block: var(--tb-chrome-pad);
  margin-bottom: 0;
}

.tb-monitor-head__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--tb-space-3);
  flex-wrap: wrap;
  width: 100%;
}

.tb-monitor-foot {
  margin-top: auto;
  display: flex;
  flex-wrap: wrap;
  gap: var(--tb-space-3);
  justify-content: space-between;
  align-items: center;
  flex-shrink: 0;
}

#monitor:has(.tb-pop:not([hidden])) .tb-monitor-foot {
  visibility: hidden;
}


/* ==========================================================================
   KIT — Icons
   ========================================================================== */

/* Lucide is always solid. Never stroke-opacity, never a see-through color. */
svg.lucide {
  opacity: 1;
  stroke-opacity: 1;
  fill-opacity: 1;
}


/* ==========================================================================
   KIT — Motion
   ========================================================================== */
@keyframes tb-bubble-tilt {
  0%, 100% { transform: rotate(0deg); }
  35% { transform: rotate(var(--tb-bubble-tilt)); }

}

@keyframes tb-typing {
  0%, 80%, 100% { opacity: 0.28; transform: translateY(0); }
  40% { opacity: 1; transform: translateY(-3px); }

}

@keyframes tb-spin {
  to { transform: rotate(360deg); }

}

@keyframes tb-restart-spin {
  to { transform: rotate(360deg); }

}

/* Arrival fade. A name landing in the lobby, a late student asking in. */
@keyframes tb-arrive {
  from { opacity: 0; }
  to { opacity: 1; }

}
