/*
 * convoready.ai — the landing page and the web free sample.
 *
 * Tokens are `docs/design-system.md`'s LIGHT palette, and the page commits to Light outright:
 * Convoready opens in Light and offers Dark only as a deliberate in-app choice, so a website that
 * followed the operating system would not match the product it is selling.
 *
 * Two values here are not free choices and should not be "tidied":
 *  - the bubble corner radii (`16px 16px 4px 16px` for the learner, mirrored for the character),
 *    which are the app's own;
 *  - `--gold-fill`, which is reserved for exactly one thing, the Texting/Speaking mode pill.
 *
 * `--error` and `--success` are the design system's two scoped exception colors, and they are spent
 * on exactly what they mean: the struck-through word a correction removes, and the word it adds.
 *
 * Motion is ~140ms ease-out throughout, and never a spring.
 */

:root {
  color-scheme: light;
  --paper:#F7EFE6; --surface:#FFFDF9; --ink:#241E1C; --ink-soft:#77706A; --line:#E6DCCC;
  --accent:#A6244F; --accent-deep:#8C1D42; --accent-wash:rgba(166,36,79,.08);
  /* The app's `backgroundSelected`: the faded garnet the star popup's tab thumb is filled with. */
  --accent-selected:#F3E1E5;
  --gold:#B8791E; --gold-fill:#D9A125; --gold-wash:rgba(184,121,30,.15);
  --warning:#9A6414; --error:#C1442B; --success:#3F7A4E;
  --objectives:#3B6B8C; --objectives-wash:rgba(59,107,140,.15);
  --scrim:rgba(36,30,28,.45);
  --shadow:0 28px 60px -28px rgba(36,30,28,.30), 0 2px 5px rgba(36,30,28,.04);
  --shadow-dialog:0 24px 48px -16px rgba(36,30,28,.35);
  --font:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}

/* The browser's own `[hidden] { display:none }` loses to ANY author rule that sets display, so
   `.speakrow { display:flex }` kept the Speaking controls on screen in Texting mode. This restores
   the attribute's meaning for every element at once. */
[hidden] { display:none !important; }

html, body { height:100%; }
body {
  margin:0; background:var(--paper); color:var(--ink); font-family:var(--font); font-size:16px;
  -webkit-font-smoothing:antialiased; -moz-osx-font-smoothing:grayscale;
}
* { -webkit-tap-highlight-color:transparent; }
a:focus-visible, button:focus-visible { outline:2px solid var(--accent); outline-offset:3px; border-radius:6px; }
button { font:inherit; color:inherit; background:none; border:none; padding:0; cursor:pointer; }

/* A percentage min-height only resolves against a parent with a definite height, so each view is
   itself the flex container and its child takes flex:1. */
.view { display:none; }
.view.on { display:flex; flex-direction:column; min-height:100%; }
.view.on.fill { height:100%; }

.tile { border-radius:22.4%; display:block; flex:none; }
.btn {
  display:inline-flex; align-items:center; justify-content:center; gap:10px;
  font-weight:700; font-size:1.0625rem; line-height:1; text-decoration:none;
  border-radius:14px; padding:16px 26px; border:1.5px solid transparent;
  transition:background-color .14s ease-out, border-color .14s ease-out, color .14s ease-out;
}
.btn-primary { background:var(--accent); border-color:var(--accent); color:var(--paper); }
.btn-primary:hover { background:var(--accent-deep); border-color:var(--accent-deep); }
.btn-outline { background:transparent; border-color:var(--accent); color:var(--accent); }
.btn-outline:hover { background:var(--accent-wash); }
.btn-quiet { background:transparent; border-color:var(--line); color:var(--ink-soft); }
.btn-quiet:hover { background:var(--paper); color:var(--ink); }
.btn-sm { font-size:.9375rem; padding:12px 18px; border-radius:12px; }
.btn[disabled] { opacity:.45; pointer-events:none; }
.apple { width:16px; height:16px; flex:none; }
.btn .apple { width:18px; height:18px; margin-top:-2px; }

/* ===================== landing ===================== */

body::before {
  content:""; position:fixed; inset:0; z-index:0; pointer-events:none;
  background:
    radial-gradient(68% 52% at 10% -6%, rgba(166,36,79,.10), transparent 62%),
    radial-gradient(52% 44% at 96% 8%, rgba(184,121,30,.10), transparent 60%);
}
/* border-box, because `width:100%` plus `padding-inline` under the default content-box made this
   wider than the viewport on every screen under 1184px — the landing page scrolled sideways by 38px
   on a phone. max-width is the old 1120 content plus both paddings, so desktop is pixel-identical. */
.page {
  position:relative; z-index:1; flex:1; display:flex; flex-direction:column;
  box-sizing:border-box; width:100%; max-width:1184px; margin-inline:auto; padding-inline:clamp(16px,5vw,32px);
}
.masthead { padding-block:clamp(22px,4vw,32px) 0; }
.brand { display:inline-flex; align-items:center; gap:11px; text-decoration:none; color:inherit; }
.brand .tile { width:36px; height:36px; }
.wordmark { font-size:1.1875rem; font-weight:700; letter-spacing:-.015em; }

.hero {
  flex:1; display:grid; grid-template-columns:1fr; gap:clamp(36px,6vw,72px);
  align-items:center; padding-block:clamp(18px,3vw,30px) clamp(30px,5vw,54px);
}
/* Two columns, and the hero sits HIGH in the window rather than in its middle (Ben, 2026-09-25:
   the card grew a translation line and a correction, and there was room going spare above it).
   The content is one row between two flexible ones that split whatever height is left 1:3, so a
   tall window puts most of its spare room under the hero rather than around it. The row gaps are
   zeroed because the spare room IS the gap now; only the column gap is spacing. */
@media (min-width:920px) {
  .hero {
    grid-template-columns:minmax(0,1.05fr) minmax(0,424px); grid-template-rows:minmax(0,1fr) auto minmax(0,3fr);
    row-gap:0; padding-block:clamp(8px,1.2vw,16px) clamp(24px,3vw,40px);
  }
  .hero > .pitch, .hero > .demo { grid-row:2; }
}
h1 {
  margin:0; font-size:clamp(2.125rem,5.3vw,3.375rem); line-height:1.06;
  letter-spacing:-.032em; font-weight:700; text-wrap:balance; max-width:16ch;
}
.lede { margin:20px 0 0; font-size:clamp(1.0625rem,1.5vw,1.1875rem); line-height:1.6; color:var(--ink-soft); max-width:48ch; }
.cta-row { display:flex; flex-wrap:wrap; align-items:center; gap:16px 24px; margin-top:clamp(28px,4vw,36px); }
.appstore-text { display:inline-flex; align-items:center; gap:9px; font-size:.9375rem; font-weight:500; color:var(--ink-soft); text-decoration:none; transition:color .14s ease-out; }
.appstore-text:hover { color:var(--ink); text-decoration:underline; text-underline-offset:.25em; }
.assurance { margin:22px 0 0; font-size:.9375rem; line-height:1.55; color:var(--ink-soft); max-width:42ch; }
/* The pills' own height plus the gap under them — what `.demo` reserves at the top of the
   two-column hero, and mirrors at the bottom. */
.demo { --pill-lane:47px; justify-self:center; width:100%; max-width:424px; }

/* The language pills above the card. `color` and `background` are the only things that move on
   selection: a border that thickens would shift every other pill by a pixel as the row reflows. */
.demo-langs { display:flex; flex-wrap:wrap; justify-content:center; gap:8px; margin-bottom:14px; }
/* On the two-column hero, the CARD is what lines up with the left column's center (Ben,
   2026-09-24) — not the card and the pills together, which pushed the card low and left the space
   above the pills empty. Taking the pills out of flow makes `.demo` exactly as tall as the card,
   so the grid's `align-items:center` centers the thing that should be centered, and the pills ride
   at the TOP of the row, in the space that was already going spare.

   The pills are anchored to the row's top rather than offset upward from the card, which is what
   an earlier cut did: an offset big enough to look deliberate on a tall window ran them under the
   masthead on a 700px one, because the gap it was reaching into does not exist there. The row's
   top is a place that always exists, and the hero's own top padding is what holds them off the
   masthead. Since the row became content-sized (2026-09-25, `.hero` above), the row's top IS the
   pill lane's top, so the pills sit a fixed 12px over the card at every window height.

   One column (below 920px) keeps them in flow: there is no spare height there, and a float would
   put them over the lede. */
@media (min-width:920px) {
  /* The padding is SYMMETRIC, and that is what keeps the card on the left column's center line
     while still reserving a lane for the pills. Top padding alone would clear them and push the
     card down by half of it; padding on both sides grows the box around a card that stays in the
     middle of it. Without the reservation the pills overlap the card on any window short enough
     that the row is no taller than the card itself (measured at 1024x700). */
  .demo { position:relative; align-self:stretch; display:flex; flex-direction:column; justify-content:center; padding-block:var(--pill-lane); }
  .demo-langs { position:absolute; top:0; left:0; right:0; margin-bottom:0; }
  /* Held at the card's resting height once the script has measured it (`lockDemoLayout`), so a card
     that grows on a click grows DOWN past its box and nothing beside it moves. Top-aligned, which at
     rest is identical to centered: the box is exactly the card plus a lane either side. Without the
     script both variables are unset and this is the ordinary centered box. */
  .demo { box-sizing:border-box; height:var(--demo-box, auto); justify-content:flex-start; }
  #demo-thread { flex-shrink:0; }
  .hero { padding-bottom:calc(clamp(24px,3vw,40px) + var(--demo-reserve, 0px)); }
}
.pill {
  border:1px solid var(--line); border-radius:999px; padding:8px 17px;
  background:var(--surface); color:var(--ink-soft); font-size:.875rem; font-weight:700;
  transition:background-color .14s ease-out, border-color .14s ease-out, color .14s ease-out;
}
.pill:hover { color:var(--ink); border-color:var(--ink-soft); }
.pill.sel { background:var(--accent); border-color:var(--accent); color:var(--paper); }
.pill.sel:hover { background:var(--accent-deep); border-color:var(--accent-deep); color:var(--paper); }

.site-footer { border-top:1px solid var(--line); padding-block:26px 34px; }
.site-footer p { margin:0; display:flex; flex-wrap:wrap; justify-content:center; align-items:center; gap:4px 12px; font-size:.875rem; color:var(--ink-soft); }
.site-footer .copyright { opacity:.8; }
/* The three items are one dot-separated row, and below 630px they cannot be: the row wraps and the
   second separator is left dangling at the end of the first line with nothing after it. 630 is
   measured rather than guessed, and it is where the row stops fitting WITH the separator in it.
   Below that the copyright takes a row of its own outright, because letting it wrap on its own
   leaves a band of widths where it shares the line with no dot in front of it. */
@media (max-width:629px) {
  .dot-copyright { display:none; }
  .site-footer .copyright { flex-basis:100%; text-align:center; }
}
.site-footer a { color:inherit; text-decoration:underline; text-underline-offset:.2em; transition:color .14s ease-out; }
.site-footer a:hover { color:var(--ink); }
.dot { opacity:.5; }

/* ===================== bubbles (landing card + live thread) ===================== */

.thread { background:var(--paper); border:1px solid var(--line); border-radius:22px; box-shadow:var(--shadow); padding:18px 16px 16px; display:flex; flex-direction:column; gap:12px; }
/* Above the turns that follow it, so the objectives popover that drops out of it is too: on the
   landing card every turn is animated, and an animated element paints as its own layer in DOM order. */
.thread-top { position:relative; z-index:2; display:flex; justify-content:space-between; gap:10px; }
.chip { background:var(--surface); border:1px solid var(--line); border-radius:12px; padding:7px 13px; font-size:.8125rem; font-weight:700; color:var(--ink); }
/* The objectives flag beside the scenario pill, as the app draws it (`conversation-settings-header.tsx`):
   the count, then a filled flag, both gray and on no ground, set close enough to read as one mark.
   It travels with the scenario pill at the row's right end — out on its own in the middle it would
   read as belonging to the level. */
.scenario-group { display:flex; align-items:center; gap:8px; min-width:0; }
.objectives { display:inline-flex; align-items:center; gap:4px; color:var(--ink-soft); }
.objcount { font-size:.75rem; font-weight:500; line-height:1; font-variant-numeric:tabular-nums; }
.objflag { width:15px; height:15px; display:block; }
/* The flag is a button (2026-09-25), in the sample and on the landing card alike: as tall as the
   row, so the tap target is the row's rather than the glyph's. */
button.objectives { height:34px; padding:0 2px; border-radius:8px; transition:opacity .14s ease-out; }
button.objectives:hover, button.objectives[aria-expanded="true"] { color:var(--ink); }
/* The popover it opens: a small card under the row, right-aligned with the scenario pill, its caret
   on the flag. A popover rather than the app's sheet, because there is nothing here to edit. */
.scenario-group { position:relative; }
.objpop {
  position:absolute; top:calc(100% + 10px); right:0; z-index:6; box-sizing:border-box;
  width:max-content; max-width:min(300px, calc(100vw - 32px));
  background:var(--surface); border:1px solid var(--line); border-radius:14px; box-shadow:var(--shadow-dialog);
  padding:12px 16px 14px;
}
.objpop::before {
  content:""; position:absolute; top:-6px; right:var(--caret-right, 18px); width:11px; height:11px;
  background:var(--surface); border-left:1px solid var(--line); border-top:1px solid var(--line);
  border-radius:2px 0 0 0; transform:rotate(45deg);
}
.objpop .caplabel { margin:0 0 8px; }
.objlist { margin:0; padding:0; list-style:none; display:flex; flex-direction:column; gap:8px; }
.objlist li { display:flex; align-items:center; gap:9px; font-size:.9375rem; line-height:1.35; color:var(--ink); }
.objlist .objflag { width:13px; height:13px; flex:none; color:var(--ink-soft); }
.scene { margin:0; font-size:.8125rem; line-height:1.5; font-style:italic; color:var(--ink-soft); }

.turn { display:flex; align-items:flex-end; gap:8px; }
.turn.them { justify-content:flex-start; }
.turn.me { justify-content:flex-end; }
.bubble { max-width:85%; padding:10px 13px; position:relative; }
.bubble p { margin:0; font-size:.9375rem; line-height:1.45; }
.them-bubble { background:var(--surface); border-radius:16px 16px 16px 4px; padding-right:34px; }
.me-bubble { background:var(--accent); color:var(--paper); border-radius:16px 16px 4px 16px; }
.who { display:block; margin-bottom:3px; font-size:.625rem; font-weight:700; letter-spacing:.08em; text-transform:uppercase; color:var(--ink-soft); }
.speaker { position:absolute; top:7px; right:7px; width:26px; height:26px; border-radius:50%; display:grid; place-items:center; color:var(--ink-soft); opacity:.65; transition:opacity .14s ease-out, background-color .14s ease-out, color .14s ease-out; }
.speaker:hover { opacity:1; background:rgba(36,30,28,.05); }
.speaker svg { width:13px; height:13px; }
.speaker.playing { color:var(--accent); opacity:1; background:var(--accent-wash); }
.english { margin-top:6px; padding-top:6px; border-top:1px solid rgba(36,30,28,.12); font-size:.8125rem; line-height:1.45; opacity:.85; }
.me-bubble .english { border-top-color:rgba(247,239,230,.28); }
/* A character bubble keeps 34px on its right for the speaker, which sits at the TOP. The translation
   is at the bottom, clear of it, so its hairline runs the bubble's full width as the app's does. */
.them-bubble .english { margin-right:-21px; }
/* The app's `StarRating`: gold filled stars and gray outlines, with the faded pill behind the row
   only on a 3-star message, and everything gray on a hint-assisted one. */
.stars { display:inline-flex; align-items:center; gap:2px; flex:none; border-radius:999px; padding:4px 7px; color:var(--gold); cursor:pointer; }
.stars.perfect { background:var(--gold-wash); }
.stars.hinted { color:var(--ink-soft); }
.stars.hinted.perfect { background:rgba(119,112,106,.15); }
.star { width:11px; height:11px; display:block; }
.star.empty { color:var(--ink-soft); }
/* The landing card's stars are a picture of a grade, not a way into the popup. */
#demo-thread .stars { cursor:default; }
/* Its bubbles show and hide their translations, like every bubble in the app. */
#demo-thread .bubble { cursor:pointer; }
#demo-thread .bubble:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }
.turn-group { display:flex; flex-direction:column; gap:6px; }
/* The inline correction (`docs/message-feedback.md`): the learner's line again, under their bubble
   and right-aligned with it, in the quiet gray with only the fixes in color. Capped at the bubble's
   own width so it reads as a note on that line rather than as a new turn. The -2px brings it to
   the app's 4px under the bubble; everything else in a turn group sits 6px apart. */
.correction { margin:-2px 0 0; align-self:flex-end; max-width:85%; text-align:right; font-size:.8125rem; line-height:1.45; color:var(--ink-soft); overflow-wrap:anywhere; }
.corr del { color:var(--error); text-decoration:line-through; text-decoration-color:var(--error); }
.corr ins { color:var(--success); text-decoration:none; }
/* For a screen reader only — the corrected sentence, since a strikethrough cannot be heard. */
.vh { position:absolute; width:1px; height:1px; margin:-1px; padding:0; overflow:hidden; clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap; border:0; }

.composer { display:flex; align-items:center; gap:8px; margin-top:4px; padding-top:13px; border-top:1px solid var(--line); }
.field { flex:1; min-width:0; background:var(--surface); border:1px solid var(--line); border-radius:12px; padding:10px 12px; font-size:.875rem; color:var(--ink-soft); }
.round { width:34px; height:34px; border-radius:50%; display:grid; place-items:center; flex:none; }
.send { background:var(--accent); color:var(--paper); }
.mic { background:var(--surface); border:1px solid var(--line); color:var(--ink); }
.round svg { width:16px; height:16px; display:block; }

/* ===================== app shell ===================== */

.shell { position:relative; z-index:1; flex:1; min-height:0; display:flex; flex-direction:column; background:var(--paper); }
.appbar { flex:none; display:flex; align-items:center; justify-content:space-between; gap:12px; padding:12px clamp(14px,4vw,20px); border-bottom:1px solid var(--line); }
.appbar .brand .tile { width:30px; height:30px; }
.appbar .wordmark { font-size:1rem; }

.counter { display:flex; flex-direction:column; gap:2px; min-width:0; }
.counter b { font-size:.9375rem; font-weight:700; letter-spacing:-.005em; }
.counter span { font-size:.6875rem; font-weight:700; letter-spacing:.07em; text-transform:uppercase; color:var(--ink-soft); }
.counter.low b { color:var(--warning); }
.counter.out b { color:var(--error); }

.getapp { display:inline-flex; align-items:center; gap:7px; font-size:.875rem; font-weight:700; color:var(--accent); border:1.5px solid var(--accent); border-radius:12px; padding:8px 13px; text-decoration:none; flex:none; transition:background-color .14s ease-out; }
.getapp:hover { background:var(--accent-wash); }
.getapp .apple { width:14px; height:14px; }

.scroller { flex:1; min-height:0; overflow-y:auto; -webkit-overflow-scrolling:touch; }
.scroller-inner { max-width:620px; margin-inline:auto; padding:16px clamp(14px,4vw,20px) 86px; display:flex; flex-direction:column; gap:12px; }

.dock { position:relative; flex:none; border-top:1px solid var(--line); background:var(--surface); padding-bottom:env(safe-area-inset-bottom, 0px); }
.dock-inner { position:relative; max-width:620px; margin-inline:auto; padding:12px clamp(14px,4vw,20px); display:flex; flex-direction:column; gap:10px; }
.dock-row { display:flex; align-items:center; justify-content:space-between; gap:10px; }

/* the mode toggle: gold fill is the token reserved for exactly this pill */
.modes { display:inline-flex; background:var(--paper); border:1px solid var(--line); border-radius:12px; padding:3px; gap:3px; }
.modes button { padding:6px 13px; border-radius:9px; font-size:.8125rem; font-weight:700; color:var(--ink-soft); transition:background-color .14s ease-out, color .14s ease-out; }
.modes button.sel { background:var(--gold-fill); color:var(--ink); }

.endbtn { display:inline-flex; align-items:center; gap:7px; font-size:.8125rem; font-weight:700; color:var(--ink-soft); border:1px solid var(--line); border-radius:12px; padding:8px 12px; background:var(--paper); }
.endbtn:hover { color:var(--ink); }
.endbtn svg { width:11px; height:11px; }

/* The progress meter (docs/progress-meter.md). 124x10 and a 15px star are the app's own numbers.
   The fill takes 520ms, the app's FILL_MS: slow enough to read as a thing that moved rather than a
   value that changed, which is the whole of what the bar communicates per turn. */
/* **It hovers over the command bar, not inside it** (Ben, 2026-09-24), which is where the app's
   sits. Absolute against `.dock-inner`, so the dock's own height never changes with it and the
   thread cannot push it around; `left` matches the dock's horizontal padding so it lines up with
   the mode pills underneath. `.scroller-inner`'s bottom padding is raised to match, so the last
   message comes to rest above the bar rather than under it.

   **The 8px of clearance is the point of the offset** (Ben, 2026-09-24). Sat flush against the
   dock's top border it read as a piece of the command bar drawn slightly wrong rather than as
   something hovering over the thread — which is exactly what an absolutely positioned element one
   pixel off its container looks like. 14px was the first try and floated too free of it; the bar
   belongs to the command bar's corner, so it wants enough air to look deliberate and no more. */
.meterwrap { position:absolute; bottom:calc(100% + 8px); left:clamp(14px,4vw,20px); z-index:3; }
/* **It fades out when the learner scrolls away from the bottom**, which is the app's own rule and
   the only one that works: a bar floating over a thread WILL sit on a message otherwise.

   Two backdrops were tried first and both were worse than the problem. A soft radial wash of the
   thread's background erases the middle of a character's line and reads as a rendering fault. An
   opaque pill reads as a deliberate control but still covers the text under it. At the bottom of
   the thread there is nothing behind the bar at all, because `.scroller-inner` reserves the room —
   so the honest answer is to show it exactly there and get out of the way everywhere else.

   140ms in, 160ms out: the app's own FADE_IN_MS and FADE_OUT_MS. It fades rather than cutting
   because what makes it come and go is a SCROLL, and a control that pops as the thread moves under
   it reads as a glitch. */
.meterwrap { opacity:1; transition:opacity .14s ease-out; }
.meterwrap.away { opacity:0; pointer-events:none; transition-duration:.16s; }
@media (prefers-reduced-motion:reduce) { .meterwrap { transition:none; } }
.meter { display:inline-flex; align-items:center; gap:4px; padding:2px 0; }
.meterbar { display:block; box-sizing:border-box; width:124px; height:10px; border:1px solid var(--line); border-radius:999px; background:var(--surface); overflow:hidden; }
.meterfill { display:block; height:100%; width:0; border-radius:999px; background:var(--accent); transition:width .52s cubic-bezier(.2,.7,.2,1); }
.meterstar { width:15px; height:15px; color:var(--line); transition:color .2s ease-out; }
.meterstar path { fill:none; stroke:currentColor; stroke-width:2.2; stroke-linejoin:round; }
.meter.full .meterstar { color:var(--accent); }
.meter.full .meterstar path { fill:currentColor; stroke:none; }
/* Above the bar, like the app's. Absolutely positioned so opening it never resizes the dock and
   pushes the conversation up. */
.metertip {
  position:absolute; bottom:calc(100% + 8px); left:0; z-index:5; width:max-content; max-width:min(320px,74vw);
  background:var(--ink); color:var(--paper); border-radius:14px; padding:12px 38px 12px 14px;
  font-size:.8125rem; line-height:1.5; box-shadow:0 10px 26px -12px rgba(36,30,28,.5);
  display:flex; flex-direction:column; gap:2px;
}
.metertip b { font-weight:700; }
.metertip span { opacity:.72; }
.metertip::after {
  content:""; position:absolute; bottom:-5px; left:22px; width:11px; height:11px;
  background:var(--ink); border-radius:2px; transform:rotate(45deg);
}
@media (prefers-reduced-motion:reduce) { .meterfill { transition:none; } }

.live-composer { display:flex; align-items:flex-end; gap:8px; }
.live-field { box-sizing:border-box; flex:1; min-width:0; background:var(--paper); border:1px solid var(--line); border-radius:12px; padding:11px 12px; font-size:1rem; font-family:inherit; color:var(--ink); resize:none; max-height:120px; line-height:1.4; overflow-y:hidden; }
.live-field::placeholder { color:var(--ink-soft); }
.live-field:focus { outline:none; border-color:var(--accent); }
.live-field:disabled { opacity:.6; }
.iconbtn { width:38px; height:38px; border-radius:50%; display:grid; place-items:center; flex:none; background:var(--paper); border:1px solid var(--line); color:var(--ink); transition:background-color .14s ease-out, opacity .14s ease-out; }
.iconbtn svg { width:17px; height:17px; }
.iconbtn.primary { background:var(--accent); border-color:var(--accent); color:var(--paper); }
.iconbtn.primary:hover { background:var(--accent-deep); }
.iconbtn[disabled] { opacity:.4; }
.iconbtn.off { opacity:.4; }

.speakrow { display:flex; align-items:center; gap:10px; }
.bigmic { flex:1; display:inline-flex; align-items:center; justify-content:center; gap:10px; background:var(--accent); color:var(--paper); border-radius:14px; padding:15px 20px; font-size:1rem; font-weight:700; transition:background-color .14s ease-out; }
.bigmic:hover { background:var(--accent-deep); }
.bigmic svg { width:18px; height:18px; }
.bigmic.listening { background:var(--gold-fill); color:var(--ink); }
.bigmic[disabled] { opacity:.45; pointer-events:none; }

.messages { display:flex; flex-direction:column; gap:12px; }

/* The Turnstile widget. It solves in the background while the learner picks a language, so it is
   deliberately unobtrusive rather than hidden — a bot check nobody can see is a bot check nobody
   can tell has failed. Collapses to nothing when the API supplies no site key. */
.turnstile { display:flex; justify-content:center; min-height:0; }
.turnstile:empty { display:none; }

/* hints */
.hintwrap { display:flex; flex-direction:column; align-items:flex-end; gap:8px; }
.hintpill { display:inline-flex; align-items:center; gap:7px; font-size:.8125rem; font-weight:700; color:var(--accent); background:var(--accent-wash); border-radius:12px; padding:8px 13px; }
.hintpill svg { width:13px; height:13px; }
.hintcard { box-sizing:border-box; width:100%; background:var(--surface); border:1px solid var(--line); border-radius:14px; padding:12px; display:flex; flex-direction:column; gap:8px; }
.hintopt { text-align:left; background:var(--paper); border:1px solid var(--line); border-radius:12px; padding:10px 12px; display:flex; flex-direction:column; gap:3px; transition:border-color .14s ease-out; }
.hintopt:hover { border-color:var(--accent); }
.hintopt b { font-size:.9375rem; font-weight:600; line-height:1.4; }
.hintopt span { font-size:.75rem; color:var(--ink-soft); line-height:1.4; }
.hintintent { display:flex; gap:8px; }
.hintintent input { flex:1; min-width:0; background:var(--paper); border:1px solid var(--line); border-radius:12px; padding:9px 11px; font:inherit; font-size:.875rem; color:var(--ink); }
.hintintent input:focus { outline:none; border-color:var(--accent); }
.hintclose { align-self:center; font-size:.75rem; color:var(--ink-soft); }
.hintclose:hover { color:var(--ink); }

.upsell { background:var(--surface); border:1px solid var(--line); border-radius:16px; padding:16px; display:flex; flex-direction:column; gap:10px; box-shadow:var(--shadow); }
.upsell h3 { margin:0; font-size:1.0625rem; font-weight:700; letter-spacing:-.01em; }
.upsell p { margin:0; font-size:.875rem; line-height:1.55; color:var(--ink-soft); }
.upsell .btn { align-self:flex-start; }

.thinking { display:inline-flex; gap:4px; align-items:center; padding:4px 0; }
.thinking i { width:6px; height:6px; border-radius:50%; background:var(--ink-soft); opacity:.4; animation:blink 1.1s infinite; }
.thinking i:nth-child(2) { animation-delay:.18s; }
.thinking i:nth-child(3) { animation-delay:.36s; }
@keyframes blink { 0%,60%,100%{opacity:.25} 30%{opacity:.85} }

/* ===================== picker + gate ===================== */

.center-wrap { flex:1; min-height:0; overflow-y:auto; display:flex; }
.center-inner { box-sizing:border-box; margin:auto; width:100%; max-width:568px; padding:32px clamp(16px,5vw,24px) 40px; display:flex; flex-direction:column; gap:26px; }
.center-inner h2 { margin:0; font-size:clamp(1.5rem,4.4vw,1.875rem); font-weight:700; letter-spacing:-.025em; line-height:1.2; }
.center-inner .sub { margin:8px 0 0; font-size:.9375rem; line-height:1.55; color:var(--ink-soft); }
.caplabel { font-size:.6875rem; font-weight:700; letter-spacing:.08em; text-transform:uppercase; color:var(--ink-soft); margin:0 0 10px; }

.langgrid { display:grid; gap:10px; }
.langcard { display:flex; align-items:center; gap:14px; text-align:left; padding:15px 16px; background:var(--surface); border:1.5px solid var(--line); border-radius:14px; transition:border-color .14s ease-out, background-color .14s ease-out; }
.langcard:hover { border-color:var(--accent); }
.langcard.sel { border-color:var(--accent); background:var(--accent-wash); }
.langhi { width:44px; height:44px; border-radius:12px; flex:none; display:grid; place-items:center; background:var(--paper); border:1px solid var(--line); font-size:.9375rem; font-weight:700; color:var(--accent); }
.langcard.sel .langhi { background:var(--surface); }
.langcard b { display:block; font-size:1rem; font-weight:700; }
.langcard span { display:block; font-size:.8125rem; color:var(--ink-soft); margin-top:2px; line-height:1.4; }

.levels { display:flex; flex-wrap:wrap; gap:8px; }
.levelbtn { padding:9px 15px; border-radius:12px; border:1.5px solid var(--line); background:var(--surface); font-size:.875rem; font-weight:700; transition:border-color .14s ease-out, background-color .14s ease-out, color .14s ease-out; }
.levelbtn:hover { border-color:var(--accent); }
.levelbtn.sel { background:var(--accent); border-color:var(--accent); color:var(--paper); }
.levelhint { margin:10px 0 0; font-size:.8125rem; color:var(--ink-soft); line-height:1.5; }

.gate-card { background:var(--surface); border:1px solid var(--line); border-radius:18px; padding:26px 22px; box-shadow:var(--shadow); display:flex; flex-direction:column; gap:14px; text-align:center; align-items:center; }
.gate-card h2 { margin:0; font-size:1.375rem; }
.gate-card p { margin:0; font-size:.9375rem; line-height:1.6; color:var(--ink-soft); max-width:40ch; }
.gate-icon { width:46px; height:46px; border-radius:50%; display:grid; place-items:center; background:var(--accent-wash); color:var(--accent); }
.gate-icon svg { width:22px; height:22px; }

/* ===================== overlays ===================== */

.scrim { position:fixed; inset:0; z-index:40; background:var(--scrim); display:flex; align-items:center; justify-content:center; padding:20px; opacity:0; pointer-events:none; transition:opacity .14s ease-out; }
.scrim.on { opacity:1; pointer-events:auto; }
.dialog { box-sizing:border-box; background:var(--surface); border-radius:14px; box-shadow:var(--shadow-dialog); width:100%; max-width:424px; padding:22px; display:flex; flex-direction:column; gap:12px; transform:translateY(8px); transition:transform .14s ease-out; }
.scrim.on .dialog { transform:none; }
.dialog h3 { margin:0; font-size:1.125rem; font-weight:700; line-height:1.3; }
.dialog p { margin:0; font-size:.9375rem; line-height:1.58; color:var(--ink-soft); }
.dialog .acts { display:flex; flex-direction:column; gap:9px; margin-top:6px; }
.dialog .acts .btn { width:100%; }
/* A centered popup, not a bottom sheet: on the web a sheet stuck to the bottom of a desktop
   window reads as a phone pattern in the wrong place. */
.sheet { box-sizing:border-box; background:var(--surface); border-radius:14px; box-shadow:var(--shadow-dialog); width:100%; max-width:464px; padding:22px; max-height:calc(100vh - 40px); max-height:calc(100dvh - 40px); overflow-y:auto; display:flex; flex-direction:column; gap:14px; transform:translateY(8px); transition:transform .14s ease-out; }
/* The results: only the feedback scrolls, so the score and the Download button never become the
   part that is cut off. `.more` fades the last lines while there is more below, the only cue a
   scroll region gets where the system hides scrollbars. */
.sheet.report { overflow:hidden; }
.sheet.report > * { flex-shrink:0; }
.sheet.report > .report-body { flex:0 1 auto; min-height:0; overflow-y:auto; display:flex; flex-direction:column; gap:14px; }
.report-body.more { -webkit-mask-image:linear-gradient(to bottom, #000 calc(100% - 44px), transparent); mask-image:linear-gradient(to bottom, #000 calc(100% - 44px), transparent); }
.sheet .upsell .btn { align-self:center; white-space:nowrap; }
.scrim.on .sheet { transform:none; }
.sheet h3 { margin:0; text-align:center; font-size:1.25rem; font-weight:700; }

.stat { background:var(--paper); border-radius:14px; padding:14px 26px; text-align:center; }
/* The box behind the READY star is a pale garnet rather than the shared cream (Ben, 2026-09-24).
   `--paper` is a warm cream, which sits fine under a gold star and reads as yellow under a garnet
   one — two inks in one box, and the box wins. */
.stat.ready { background:var(--accent-wash); }
.statchip { display:inline-flex; align-items:center; gap:5px; border-radius:999px; padding:5px 12px; font-size:1.125rem; font-weight:700; font-variant-numeric:tabular-nums; background:var(--gold-wash); color:var(--gold); }
.statchip svg { width:14px; height:14px; }
.stat small { display:block; margin-top:7px; font-size:.75rem; color:var(--ink-soft); }
.feedback { margin:0; font-size:.9375rem; line-height:1.6; color:var(--ink-soft); }

.toast { position:fixed; left:50%; bottom:22px; transform:translate(-50%,10px); z-index:60; background:var(--ink); color:var(--paper); border-radius:12px; padding:11px 16px; font-size:.8125rem; font-weight:600; box-sizing:border-box; max-width:min(432px, calc(100vw - 32px)); text-align:center; opacity:0; pointer-events:none; transition:opacity .14s ease-out, transform .14s ease-out; }
.toast.on { opacity:1; transform:translate(-50%,0); }

/* the two onboarding callouts, same copy and sequence as the app's thread tips */
.tipcard {
  position:relative; align-self:stretch; background:var(--ink); color:var(--paper);
  border-radius:14px; padding:12px 38px 12px 14px; font-size:.8125rem; line-height:1.5;
  box-shadow:0 10px 26px -12px rgba(36,30,28,.5);
}
.tipcard.right { align-self:flex-end; max-width:min(300px,86%); }
.tipcard.left { align-self:flex-start; max-width:min(300px,86%); }
.tipcard b { font-weight:700; }
.tipcard .caret {
  position:absolute; top:-5px; width:11px; height:11px; background:var(--ink); border-radius:2px;
  transform:rotate(45deg);
}
.tipcard.left .caret { left:20px; }
.tipcard.right .caret { right:24px; }
.tipx {
  position:absolute; top:6px; right:8px; width:24px; height:24px; border-radius:50%;
  display:grid; place-items:center; color:var(--paper); opacity:.6; font-size:1rem; line-height:1;
}
.tipx:hover { opacity:1; }

.statbar { display:flex; justify-content:center; gap:10px; }

/* ===================== the star popup ===================== */

/* The app's two-tab popup (`star-feedback-popup.tsx`). Only the panes' viewport can shrink, so the
   grade and the toggle stay on screen whatever the height. */
.sheet.starpop { overflow:hidden; gap:16px; }
.sheet.starpop > * { flex-shrink:0; }
.sheet.starpop > .starpop-view { flex:0 1 auto; min-height:0; overflow-x:hidden; overflow-y:auto; }
.starpop-head { display:flex; align-items:center; justify-content:space-between; gap:12px; }
.sheet .starpop-head h3 { text-align:left; }
.starpop-head .stars { cursor:default; }
/* Two panes side by side in a row twice the viewport's width, sliding with the toggle's thumb.
   Feedback is the only pane in flow, so it alone sets the height; Alternatives is pinned over the
   same box and scrolls inside it. */
.starpop-panes { position:relative; display:flex; align-items:flex-start; width:200%; transition:transform .22s cubic-bezier(.33,1,.68,1); }
.starpop-panes.alt { transform:translateX(-50%); }
.starpop-pane { box-sizing:border-box; width:50%; }
.starpop-pane.scroll { position:absolute; top:0; bottom:0; left:50%; overflow-y:auto; overscroll-behavior:contain; }
.starpop-pane.scroll.more { -webkit-mask-image:linear-gradient(to bottom, #000 calc(100% - 36px), transparent); mask-image:linear-gradient(to bottom, #000 calc(100% - 36px), transparent); }
.panebody { display:flex; flex-direction:column; gap:16px; }
.explain, .panenote { margin:0; font-size:.9375rem; line-height:1.45; color:var(--ink-soft); }
.paneload { display:flex; align-items:center; gap:10px; font-size:.875rem; color:var(--ink-soft); }
.paneerr { display:flex; flex-direction:column; align-items:flex-start; gap:6px; }
.paneerr p { margin:0; font-size:.875rem; line-height:1.45; color:var(--ink-soft); }
.retry { font-size:.875rem; font-weight:700; color:var(--accent); padding:2px 0; }
.retry:hover { color:var(--accent-deep); }
/* The learner's own line, quoted back on the recessed ground, heavier than the explanation above it:
   it is the thing on this tab to actually study. */
.msgbox { margin:0; background:var(--paper); border-radius:12px; padding:10px 12px; font-size:1rem; line-height:1.45; font-weight:600; color:var(--ink); overflow-wrap:anywhere; }
/* The Hint card without the send arrow: nothing here is sent. */
.altcards { display:flex; flex-direction:column; gap:8px; }
.altcard { background:var(--paper); border:1px solid var(--line); border-radius:12px; padding:10px 12px; display:flex; flex-direction:column; gap:2px; }
.altcard b { font-size:1rem; font-weight:600; line-height:1.4; }
.altcard span { font-size:.875rem; line-height:1.4; color:var(--ink-soft); }
/* A skeleton card is exactly as tall as the one-line card that replaces it. */
.skeltext { height:45px; display:flex; flex-direction:column; justify-content:center; gap:8px; }
.skeltext i { display:block; height:12px; border-radius:4px; background:var(--line); }
.skeltext i + i { height:10px; }
.altcards.skel { animation:skel .7s ease-in-out infinite alternate; }
@keyframes skel { from { opacity:1; } to { opacity:.45; } }
.starpop-foot { display:flex; flex-direction:column; gap:14px; }
.starpop-foot .btn { width:100%; }
/* The toggle is the app's sliding switch: a hairline track on the page's cream, a faded-garnet thumb,
   the selected label in garnet. The thumb is a segment wide — half the track's inside, less its 3px
   padding — so moving it by its own width lands it on the second segment exactly. 220ms, longer than
   the ~140ms everywhere else, because it moves a whole pane and not just a thumb. */
.tabs { position:relative; display:flex; background:var(--paper); border:1px solid var(--line); border-radius:12px; padding:3px; }
.tabs .thumb { position:absolute; top:3px; bottom:3px; left:3px; width:calc(50% - 3px); border-radius:9px; background:var(--accent-selected); transition:transform .22s cubic-bezier(.33,1,.68,1); }
.tabs.alt .thumb { transform:translateX(100%); }
.tabs button { position:relative; flex:1; padding:7px 0; font-size:.875rem; font-weight:700; color:var(--ink-soft); border-radius:9px; transition:color .14s ease-out; }
.tabs button[aria-selected="true"] { color:var(--accent); }
/* Opening on a remembered tab SNAPS there, rather than sliding in from the other one. */
.snap .starpop-panes, .snap .tabs .thumb { transition:none; }
/* The second reported statistic is the READY star, in the accent, since 2026-09-23 — it
   replaced the objectives count, which was scored by a judgment that no longer exists. */
.statchip.ready { background:rgba(166,36,79,.16); color:var(--accent); }
.starnote { margin:0; font-size:.9375rem; line-height:1.6; }

/* ===================== motion ===================== */

@keyframes rise { from { opacity:0; transform:translateY(10px); } to { opacity:1; transform:none; } }
.anim { animation:rise .42s cubic-bezier(.22,.61,.36,1) both; animation-delay:var(--d,0s); }
@keyframes pop { from { opacity:0; transform:translateY(6px); } to { opacity:1; transform:none; } }
.pop { animation:pop .22s ease-out both; }
@media (prefers-reduced-motion:reduce) {
  .anim, .pop, .altcards.skel { animation:none; }
  .scrim, .dialog, .sheet, .toast, .starpop-panes, .tabs .thumb { transition:none; }
}
