/* ==========================================================================
   Readuet marketing site — the whole stylesheet.

   There is deliberately no second CSS file and no inline <style>: the apex
   vhost (ops/caddy/Caddyfile) serves this site under `style-src 'self'`, so an
   inline style block or a style="" attribute is blocked by the CSP. Every
   visual tweak has to become a class in here.

   Palette comes from the app icon (app/assets/icon/app_icon.png), not from the
   Material seed: navy #1B2A4A, cream #EDE5D2, orange #EF962A, amber highlight.
   That is what a visitor sees on the store badge and the favicon, so that is
   what the site has to match. The sage #4E6E5D from lib/main.dart survives as a
   supporting accent, because it is what the app chrome inside the screenshots
   is tinted with.
   ========================================================================== */

/* --------------------------------------------------------------- 1. Fonts */
/* Self-hosted: Google Fonts would force fonts.googleapis.com into the CSP,
   add two handshakes on the critical path, and is a GDPR grey zone in the EU.
   Literata is SIL OFL 1.1 — fonts/OFL.txt ships alongside, as required. */

@font-face {
  font-family: "Literata";
  src: url("/fonts/literata-latin.woff2") format("woff2");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Polish ł/ą, Turkish ğ/ş … — the hero example word is `biegłem`, so this is
   not optional. Split out so latin-only visitors never download it. */
@font-face {
  font-family: "Literata";
  src: url("/fonts/literata-latin-ext.woff2") format("woff2");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Cyrillic body text — /ru/ and /uk/. Split like latin-ext so nobody else pays
   for it: the unicode-range means a browser fetches this face only if a glyph
   on the page falls inside it, so an English visitor never downloads it (see
   also .langs a, which keeps the switcher's "Русский" off Literata entirely).
   Ukrainian is covered here — і U+0456, ї U+0457, є U+0454 sit in U+0400-045F
   and ґ is U+0491 — so the cyrillic-ext subset is deliberately NOT shipped;
   nothing on this site uses a codepoint from it. */
@font-face {
  font-family: "Literata";
  src: url("/fonts/literata-cyrillic.woff2") format("woff2");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}


/* --------------------------------------------------------------- 2. Tokens */

:root {
  color-scheme: light dark;

  --font-display: "Literata", Georgia, "Times New Roman", serif;
  /* Same stack as app/web/privacy.html — house style, and zero bytes. */
  --font-ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
    Arial, sans-serif;

  /* Surfaces — warm paper, straight off the icon's left-hand page. */
  --bg: #fbf8f1;
  --bg-alt: #f4eee1;
  --bg-sunk: #1b2a4a; /* the dark band; navy is a *surface* here, not just ink */
  --surface: #ffffff;
  --border: #e5dcc8;
  --border-strong: #cfc2a6;

  /* Ink */
  --text: #1b2a4a; /* 13.8:1 on --bg */
  --text-muted: #4a5468; /*  7.2:1 on --bg */
  --text-faint: #616a7c; /*  5.1:1 on --bg, 4.7:1 on --bg-alt — safe for fine print */
  --on-dark: #f2ece0;
  --on-dark-muted: #a8b2c4;

  /* Brand */
  --orange: #ef962a; /* fills only — 2.4:1 on cream, never text */
  --orange-deep: #d97b0c;
  --orange-ink: #9a5200; /* orange *as text* on --bg: 4.8:1 */
  --on-orange: #1b2a4a; /* navy on orange: 5.9:1 — matches the icon */
  --orange-tint: #fdefdb;

  --navy: #1b2a4a;
  --navy-deep: #101a30;
  --sage: #4e6e5d; /* ties to the app chrome inside the screenshots */
  --sage-tint: #e6ede8;

  /* The vocabulary highlight, lifted from lib/features/vocab/word_spans.dart */
  --amber: #ffd54f;
  --amber-ink: #4a3600; /* text over --amber: 8.9:1 */

  /* Progress-dashboard chart colours (features/progress/progress_screen.dart)
     so the w3 screenshot sits inside a palette that already matches it. */
  --chart-blue: #3987e5;
  --chart-green: #199e70;
  --chart-gold: #c98500;

  --focus: #0b4fa8;

  --shadow-1: 0 1px 2px rgba(27, 42, 74, 0.06), 0 4px 14px -6px rgba(27, 42, 74, 0.14);
  --shadow-2: 0 30px 70px -30px rgba(27, 42, 74, 0.45);

  /* Space & measure */
  --sp-1: 0.25rem;
  --sp-2: 0.5rem;
  --sp-3: 0.75rem;
  --sp-4: 1rem;
  --sp-6: 1.5rem;
  --sp-8: 2rem;
  --sp-12: 3rem;
  --sp-16: 4rem;
  --sp-24: 6rem;
  --gutter: clamp(1.25rem, 4vw, 2rem);
  --container: 1120px;
  --measure: 62ch;
  --section-y: clamp(3.5rem, 7vw, 7rem);

  --radius-sm: 8px;
  --radius: 14px;
  --radius-lg: 22px;
  --radius-pill: 999px;

  --header-h: 64px;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #101a30;
    --bg-alt: #16223c;
    --bg-sunk: #0a1223;
    --surface: #1a2740;
    --border: #2b3a58;
    --border-strong: #3d4e70;

    --text: #ede5d2;
    --text-muted: #a8b2c4;
    --text-faint: #8590a6;
    --on-dark: #f2ece0;
    --on-dark-muted: #a8b2c4;

    --orange: #f5a43c;
    --orange-deep: #ef962a;
    --orange-ink: #f7b45c; /* 8.4:1 on --bg */
    --on-orange: #16223c;
    --orange-tint: #2a2416;

    --sage: #8fbca3;
    --sage-tint: #1c2c26;

    --amber: #ffb300;
    --amber-ink: #1a1206;

    --chart-blue: #2a78d6;
    --chart-green: #1baf7a;
    --chart-gold: #eda100;

    --focus: #9ec5ff;

    --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.55);
    --shadow-2: 0 30px 70px -30px rgba(0, 0, 0, 0.8);
  }
}

/* ---------------------------------------------------------------- 3. Reset */

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

html {
  scroll-behavior: smooth;
  /* Anchor links must not land under the sticky header. */
  scroll-padding-top: calc(var(--header-h) + var(--sp-4));
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-ui);
  font-size: 1rem;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img,
svg {
  max-width: 100%;
}

h1,
h2,
h3 {
  margin: 0 0 var(--sp-4);
  text-wrap: balance;
}

p {
  margin: 0 0 var(--sp-4);
  max-width: var(--measure);
}

ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

a {
  color: var(--orange-ink);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}

a:hover {
  text-decoration-thickness: 2px;
}

:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
  border-radius: 3px;
}

/* ------------------------------------------------------------ 4. Utilities */

.container {
  width: min(var(--container), 100% - 2 * var(--gutter));
  margin-inline: auto;
}

.container--narrow {
  width: min(760px, 100% - 2 * var(--gutter));
}

/* Visually hidden but still focusable and still announced. Never use
   display:none for the pricing checkbox — that drops it from the tab order and
   kills the toggle for keyboard users. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip {
  position: absolute;
  left: var(--sp-4);
  top: -100px;
  z-index: 100;
  padding: var(--sp-3) var(--sp-4);
  background: var(--surface);
  color: var(--text);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-1);
  transition: top 0.15s ease;
}

.skip:focus {
  top: var(--sp-4);
}

.eyebrow {
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--orange-ink);
  margin: 0 0 var(--sp-3);
}

.lede {
  font-size: clamp(1.06rem, 0.98rem + 0.4vw, 1.28rem);
  line-height: 1.55;
  color: var(--text-muted);
}

.fineprint {
  font-size: 0.875rem;
  color: var(--text-faint);
  max-width: var(--measure);
}

code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.88em;
  padding: 0.1em 0.35em;
  border-radius: var(--radius-sm);
  background: var(--bg-alt);
  border: 1px solid var(--border);
}

.section--alt code {
  background: var(--surface);
}

/* The reader's vocabulary wash. Only ever a background under text — exactly as
   in lib/features/vocab/word_spans.dart. Used sparingly; overuse and the page
   reads like a highlighter accident. */
.amber {
  background-image: linear-gradient(
    transparent 56%,
    var(--amber) 56%,
    var(--amber) 94%,
    transparent 94%
  );
  padding: 0 0.06em;
}

/* ------------------------------------------------------------- 5. Sections */

.section {
  padding-block: var(--section-y);
}

.section--alt {
  background: var(--bg-alt);
}

.section--dark {
  background: var(--bg-sunk);
  color: var(--on-dark);
}

.section--dark .lede,
.section--dark .fineprint {
  color: var(--on-dark-muted);
}

.section--dark .eyebrow {
  color: var(--orange);
}

/* Prose links only. A .btn is an <a> too, and it brings its own colour pairing
   — without :not() a primary button on a dark section renders orange-on-orange,
   i.e. an invisible label. */
.section--dark a:not(.btn) {
  color: var(--orange);
}

.section__head {
  margin-bottom: var(--sp-12);
}

/* Closing call to action: centred, because it's the one section with nothing
   to sit beside. */
.section--cta {
  text-align: center;
}

.section--cta p,
.section--cta .badges {
  margin-inline: auto;
  justify-content: center;
}

.section--cta .badges {
  margin-top: var(--sp-8);
}

h2 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.8rem, 1.25rem + 2vw, 2.7rem);
  line-height: 1.15;
  letter-spacing: -0.015em;
}

h3 {
  font-size: 1.16rem;
  font-weight: 700;
  letter-spacing: -0.005em;
}

/* Two-column copy/visual band. Alternating .split--flip makes the page
   zig-zag instead of marching down one side. */
.split {
  display: grid;
  gap: var(--sp-12);
  align-items: center;
}

/* Grid items default to min-width:auto, so a wide child (a phone pair, a long
   code span) can push its track past the viewport instead of shrinking. Every
   grid on the page opts out. */
.split > *,
.grid-3 > *,
.steps > *,
.plans > *,
.footer-grid > * {
  min-width: 0;
}

@media (min-width: 900px) {
  .split {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: clamp(2.5rem, 5vw, 5rem);
  }

  .split--flip > :first-child {
    order: 2;
  }
}

/* --------------------------------------------------------------- 6. Header */

.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  height: var(--header-h);
  display: flex;
  align-items: center;
  background: color-mix(in srgb, var(--bg) 84%, transparent);
  backdrop-filter: saturate(180%) blur(14px);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  /* A permanent hairline rather than a scroll-driven one: `animation-timeline:
     scroll()` is Chromium-only today, and doing it in JS would mean a scroll
     listener for a 1px line. */
  border-bottom: 1px solid var(--border);
}

.site-header .container {
  display: flex;
  align-items: center;
  gap: var(--sp-6);
}

/* Nothing in this row may wrap. The bar is a fixed 64px (--header-h), so a
   flex item that shrinks below its text and wraps to a second line does not
   make the header taller — it spills out of it and lands on top of the hero.
   That is exactly what "Открыть веб-приложение" and "Otwórz aplikację webową"
   did at tablet and phone widths before the widths below were worked out. */
.site-header .brand,
.site-header .btn {
  flex: none;
  white-space: nowrap;
}

@media (max-width: 559px) {
  .site-header .container {
    gap: var(--sp-3);
  }

  .site-header .btn {
    padding: 0.5rem 0.9rem;
    font-size: 0.85rem;
  }
}

/* The wordmark goes before the CTA does: the mark alone is still the app's
   icon and still the link home, whereas a truncated call to action is just
   broken. Below this the longest CTA (Ukrainian, Russian, Polish) needs the
   ~90px it frees. */
@media (max-width: 479px) {
  .site-header .container {
    gap: var(--sp-2);
  }

  .brand__name {
    display: none;
  }

  .brand {
    margin-right: auto;
  }
}

/* 320px — a Galaxy Fold's cover screen and the original SE. The Russian CTA
   ("Открыть веб-приложение") is the one string that still does not fit at the
   size above; every other locale clears it with room to spare. */
@media (max-width: 359px) {
  .site-header .btn {
    padding: 0.5rem 0.7rem;
    font-size: 0.8rem;
  }
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.18rem;
  letter-spacing: -0.01em;
  color: var(--text);
  text-decoration: none;
  margin-right: auto;
}

.brand img {
  width: 32px;
  height: 32px;
  display: block;
}

.nav {
  display: none;
  gap: var(--sp-6);
}

.nav a {
  color: var(--text-muted);
  text-decoration: none;
  font-size: 0.95rem;
  font-weight: 500;
}

.nav a:hover {
  color: var(--text);
  text-decoration: underline;
}

/* 960px, not the 720px this used to be. The nav was never measured against the
   long locales: at 768px the Russian header wrapped "Как это работает" onto two
   lines and pushed the CTA out of the bar, and English merely squashed. The
   widest row is Russian at 944px of content, so 960 is the first width where
   every language fits — below it the nav hides, exactly as it already did on
   phones, and the five links are still in the footer of every page. */
@media (min-width: 960px) {
  .nav {
    display: flex;
  }
}

/* ------------------------------------------------- 6b. Language switcher */

/* A <details> disclosure, so it opens, closes and is keyboard-operable with no
   JavaScript at all — which matters here, because the vhost's CSP forbids an
   inline handler and hand-rolling a menu widget for eleven links would be all
   cost. js/lang.js adds Esc and click-outside dismissal on top; with that file
   absent, blocked or 404ing the menu still works from its own button.

   Unlike .nav, this never hides. Someone reading the wrong language needs it
   most on a phone, and the footer list — which was the only way out until this
   existed — is a whole landing page of scrolling away. What drops on the way
   down is the endonym and the caret, not the control.

   The panel escapes the header fine despite .site-header being a sticky,
   backdrop-filtered stacking context: the header has no overflow clip and sits
   at z-index 50, so an absolutely-positioned child paints over the page. */
.lang {
  position: relative;
  flex: none;
}

.lang > summary {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  min-height: 40px;
  padding: 0.35rem 0.55rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--text-muted);
  font-size: 0.9rem;
  font-weight: 550;
  white-space: nowrap;
  cursor: pointer;
  list-style: none; /* Firefox's disclosure triangle */
}

.lang > summary::-webkit-details-marker {
  display: none; /* Safari's */
}

.lang > summary:hover,
.lang[open] > summary {
  color: var(--text);
  border-color: var(--border-strong);
}

.lang[open] > summary {
  background: var(--surface);
}

/* Hidden by default, restored at 1100px — see the breakpoint at the bottom of
   this section. Below that the globe carries the whole control. */
.lang__label,
.lang__caret {
  display: none;
}

.lang[open] .lang__caret {
  transform: rotate(180deg);
}

.lang__menu {
  position: absolute;
  top: calc(100% + 0.45rem);
  right: 0;
  min-width: 11.5rem;
  max-height: min(70vh, 30rem);
  overflow-y: auto;
  margin: 0;
  padding: var(--sp-2);
  list-style: none;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-1);
}

.lang__menu a {
  display: block;
  padding: 0.4rem 0.65rem;
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  text-decoration: none;
  font-size: 0.95rem;
  white-space: nowrap;
}

.lang__menu a:hover {
  background: var(--bg-alt);
  color: var(--text);
}

.lang__menu [aria-current] {
  color: var(--text);
  font-weight: 700;
}

/* The same reason .langs a is pinned, further down: these labels are endonyms,
   so "Русский" is in the markup of every page on the site. Inheriting the body
   font would pull the Cyrillic Literata subset onto the English homepage for
   one word and regress the LCP the hero's fetchpriority="high" was tuned for.
   The header is the worse place for it — this markup is above the fold. */
.lang > summary,
.lang__menu a {
  font-family: var(--font-ui);
}

/* On a phone the globe is at neither edge — the CTA is to its right, the mark
   to its left — so anchoring the panel to the button overhangs the viewport
   whichever side you pick: 48px off the left in Polish, 7px off the right in
   English. body has overflow-x:hidden, so it does not even get a scrollbar;
   those letters are simply gone.

   Dropping .lang to position:static hands the panel to .site-header instead
   (sticky counts as positioned), which is exactly as wide as the viewport. It
   then sits on the page gutter and cannot overhang either edge in any language.
   Above 560px the button is wide and near the right edge, so anchoring it to
   the button reads better and cannot overhang. */
@media (max-width: 559px) {
  .lang {
    position: static;
  }

  .lang__menu {
    right: auto;
    left: var(--gutter);
  }
}

/* The endonym costs 50-80px depending on the language, and the row cannot
   afford it until well past the 960px where the nav arrives: Russian is the
   widest at 944px of content, and that is with the globe alone. */
@media (min-width: 1100px) {
  .lang__label,
  .lang__caret {
    display: block;
  }

  .lang > summary {
    padding: 0.35rem 0.7rem;
  }

  .lang__caret {
    transition: transform 0.15s ease;
  }
}

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

/* --------------------------------------------------------------- 7. Buttons */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-height: 44px;
  padding: 0.6rem 1.15rem;
  border-radius: var(--radius-pill);
  font: inherit;
  font-weight: 650;
  font-size: 0.95rem;
  text-decoration: none;
  border: 1px solid transparent;
  cursor: pointer;
  transition: transform 0.12s ease, background-color 0.15s ease,
    border-color 0.15s ease;
}

.btn:active {
  transform: translateY(1px);
}

.btn--primary {
  background: var(--orange);
  color: var(--on-orange);
}

.btn--primary:hover {
  background: var(--orange-deep);
}

/* The hero and closing CTAs while the store badges are shelved: a badge is
   ~48px tall and this stands in for two of them, so it has to carry that
   weight rather than look like a nav button. */
.btn--lg {
  min-height: 52px;
  padding: 0.8rem 1.6rem;
  font-size: 1.02rem;
}

.btn--ghost {
  border-color: var(--border-strong);
  color: var(--text);
  background: transparent;
}

.btn--ghost:hover {
  background: var(--bg-alt);
  border-color: var(--text-faint);
}

.section--dark .btn--ghost {
  color: var(--on-dark);
  border-color: rgba(242, 236, 224, 0.35);
}

.section--dark .btn--ghost:hover {
  background: rgba(242, 236, 224, 0.1);
}

.textlink {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-weight: 600;
  color: var(--orange-ink);
  text-decoration: none;
  min-height: 44px;
}

.textlink:hover {
  text-decoration: underline;
  text-underline-offset: 4px;
}

/* ----------------------------------------------------------------- 8. Hero */

.hero {
  position: relative;
  padding-block: clamp(2.5rem, 5vw, 5rem) clamp(3rem, 6vw, 6rem);
  overflow: hidden;
}

/* Warm paper glow behind everything; the second layer is a faint page-edge
   gradient echoing the open book in the logo. */
.hero::before {
  content: "";
  position: absolute;
  inset: -20% -10% auto;
  height: 90%;
  background: radial-gradient(
      55% 70% at 78% 30%,
      color-mix(in srgb, var(--orange) 20%, transparent),
      transparent 68%
    ),
    radial-gradient(
      45% 60% at 12% 12%,
      color-mix(in srgb, var(--sage) 16%, transparent),
      transparent 70%
    );
  z-index: -1;
  pointer-events: none;
}

.hero__grid {
  display: grid;
  gap: var(--sp-12);
  align-items: center;
}

@media (min-width: 900px) {
  .hero__grid {
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
    gap: clamp(2rem, 5vw, 4.5rem);
  }
}

.hero h1 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(2.4rem, 1.5rem + 3.7vw, 4.1rem);
  line-height: 1.04;
  letter-spacing: -0.025em;
  margin-bottom: var(--sp-6);
}

.hero h1 span {
  display: block;
}

.hero h1 .hero__line2 {
  color: var(--orange-ink);
}

.hero .lede {
  margin-bottom: var(--sp-8);
}

/* Store badges. Apple's SVG is the button edge-to-edge; Google's PNG bakes in
   its mandated clear space (the button is 564×168 inside a 646×250 canvas, i.e.
   67.2% of the height). So to land both buttons at the same visual 48px, Apple
   gets height:48px and Google gets 48 / 0.672 ≈ 71px. The small gap plus
   Google's own padding gives the spacing. Do not crop or redraw either — both
   brand guidelines forbid it. */
.badges {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-1);
  margin: 0 0 var(--sp-4);
}

.badges a {
  display: inline-flex;
  border-radius: var(--radius-sm);
}

.badge--apple {
  height: 48px;
  width: auto;
  display: block;
}

.badge--play {
  height: 71px;
  width: auto;
  display: block;
}

.hero__secondary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-4);
  margin-bottom: var(--sp-4);
}

/* Hero visual ----------------------------------------------------------- */

.hero__visual {
  position: relative;
  display: flex;
  justify-content: center;
  min-width: 0;
}

.hero__stack {
  position: relative;
  max-width: 100%;
}

/* The floating gloss chip: pure HTML/CSS, zero image bytes, and it carries the
   entire vocabulary pitch above the fold. Hidden on narrow screens where it
   would collide with the frame. */
.gloss-chip {
  display: none;
  position: absolute;
  /* Sits off the frame's lower-left corner. Kept low enough that it overlaps
     the bezel rather than the player controls in the screenshot behind it. */
  left: -24%;
  bottom: 4%;
  z-index: 2;
  padding: 0.7rem 0.95rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-1), var(--shadow-2);
  font-size: 0.95rem;
  line-height: 1.35;
  text-align: left;
  white-space: nowrap;
}

.gloss-chip__word {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.1rem;
  color: var(--text);
}

.gloss-chip__tr {
  display: block;
  margin-top: 0.3rem;
  font-weight: 600;
  color: var(--text);
}

.gloss-chip__lemma {
  display: block;
  font-size: 0.82rem;
  color: var(--text-faint);
}

@media (min-width: 900px) {
  .gloss-chip {
    display: block;
  }
}

/* Decorative waveform, echoing the right-hand page of the logo. */
.waveform {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  height: 34px;
  margin-top: var(--sp-6);
}

.waveform span {
  display: block;
  width: 4px;
  border-radius: 2px;
  background: var(--orange);
  opacity: 0.85;
  height: 30%;
  animation: wave 1.4s ease-in-out infinite;
}

.waveform span:nth-child(6n + 1) { animation-delay: 0s;    height: 40%; }
.waveform span:nth-child(6n + 2) { animation-delay: 0.12s; height: 75%; }
.waveform span:nth-child(6n + 3) { animation-delay: 0.24s; height: 55%; }
.waveform span:nth-child(6n + 4) { animation-delay: 0.36s; height: 95%; }
.waveform span:nth-child(6n + 5) { animation-delay: 0.48s; height: 45%; }
.waveform span:nth-child(6n + 6) { animation-delay: 0.6s;  height: 68%; }

@keyframes wave {
  0%, 100% { transform: scaleY(0.55); }
  50%      { transform: scaleY(1); }
}

/* ------------------------------------------------------- 9. Device frames */

/* A screenshot detail: a crop of a real capture shown at roughly twice the
   scale the same pixels get inside a .phone frame. The vocabulary section
   needs one because its whole subject — the gloss bubble under the tapped
   word — is a ~10 px sliver on a whole-phone shot, i.e. the one thing a
   visitor cannot see is the one thing being sold.

   Deliberately *not* a magnifying-glass overlay on the phone: the crop is
   already the app's own page surface, so a lens ornament would only add
   chrome that isn't in the product. It gets a hairline and a shadow so it
   reads as a piece of screen rather than as a diagram. */
.detail {
  margin: var(--sp-8) 0 0;
  max-width: 540px;
}

.detail img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-1);
  /* Matches the reader's paper so the crop's own margins don't show a seam. */
  background: var(--surface);
}

.detail figcaption {
  margin-top: var(--sp-3);
  font-size: 0.92rem;
  line-height: 1.5;
  color: var(--text-muted);
}

/* The two glossed words are quoted from the screenshot above, so they are set
   like the book they came from rather than like the surrounding UI copy. */
.detail figcaption span {
  font-family: var(--font-display);
  font-weight: 600;
  color: var(--text);
}

.section--dark .detail figcaption {
  color: var(--on-dark-muted);
}

/* No fake notch and no fake status bar: the real device screenshots already
   contain both, and drawing a second one on top is the single most reliable
   way to make a page like this look cheap. */
.phone {
  --phone-w: 268px;
  width: var(--phone-w);
  max-width: 100%;
  padding: 0.5rem;
  border-radius: 2.6rem;
  background: linear-gradient(155deg, #38455f 0%, #16223c 55%, #2b3a58 100%);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.12), var(--shadow-2);
}

/* No forced aspect-ratio: the iOS and Android capture pipelines don't
   produce quite the same ratio (iOS shots are ~720x1565, Android ~720x1600),
   and forcing one via object-fit: cover cropped the status bar off the top
   and the player controls off the bottom of whichever platform didn't
   match. Each image keeps its own natural ratio instead. */
.phone img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 2.15rem;
  background: var(--bg-alt);
}

.phone--lg {
  --phone-w: 300px;
}

/* flex-start, not the default stretch: the second frame is nudged down by a
   margin-top below, which makes the flex line taller than the first frame's
   image. Stretched, that surplus height turns into empty bezel under the
   first screenshot instead of the frame hugging it. */
.phone-pair {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  gap: var(--sp-4);
  min-width: 0;
}

/* Fluid so two frames always fit the column they're in, capped so they never
   balloon on a wide screen. */
.phone-pair .phone {
  --phone-w: calc((100% - var(--sp-4)) / 2);
  max-width: 244px;
  flex: 1 1 0;
}

/* A lone frame in the wrapper gets the full single-phone width. */
.phone-pair > .phone:only-child {
  --phone-w: 268px;
  max-width: 268px;
  flex: 0 1 auto;
}

@media (min-width: 560px) {
  .phone-pair .phone:last-child:not(:only-child) {
    margin-top: var(--sp-12);
  }
}

.browser {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--surface);
  box-shadow: var(--shadow-2);
}

.browser__bar {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  height: 38px;
  padding: 0 0.75rem;
  background: var(--bg-alt);
  border-bottom: 1px solid var(--border);
}

.browser__dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--border-strong);
  flex: 0 0 auto;
}

.browser__url {
  margin-left: 0.5rem;
  padding: 0.1rem 0.7rem;
  border-radius: var(--radius-pill);
  background: var(--bg);
  color: var(--text-faint);
  font-size: 0.74rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.browser img {
  display: block;
  width: 100%;
  height: auto;
  background: var(--bg-alt);
}

.section--dark .browser {
  border-color: #2b3a58;
}

.section--dark .browser__bar {
  background: #16223c;
  border-bottom-color: #2b3a58;
}

.section--dark .browser__url {
  background: #0a1223;
  color: var(--on-dark-muted);
}

/* ------------------------------------------------------------- 10. Steps */

.steps {
  display: grid;
  gap: var(--sp-8);
  counter-reset: step;
}

@media (min-width: 760px) {
  .steps {
    grid-template-columns: repeat(3, 1fr);
    gap: var(--sp-12);
  }
}

.step h3 {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  margin-bottom: var(--sp-2);
}

.step h3::before {
  counter-increment: step;
  content: counter(step);
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--orange-tint);
  color: var(--orange-ink);
  font-family: var(--font-display);
  font-size: 1rem;
  font-weight: 700;
}

.step p {
  margin: 0;
  color: var(--text-muted);
}

/* -------------------------------------------------------- 11. Chips, pills */

.stats {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin: var(--sp-6) 0 0;
}

.stat {
  padding: 0.4rem 0.85rem;
  border-radius: var(--radius-pill);
  background: var(--surface);
  border: 1px solid var(--border);
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--text);
  box-shadow: var(--shadow-1);
}

.stat b {
  color: var(--orange-ink);
}

.pills {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin: var(--sp-6) 0 0;
}

.pill {
  padding: 0.3rem 0.75rem;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-strong);
  background: var(--bg);
  font-size: 0.82rem;
  font-weight: 650;
  letter-spacing: 0.02em;
  color: var(--text-muted);
}

.pill--cefr {
  background: var(--sage-tint);
  border-color: transparent;
  color: var(--sage);
  font-family: var(--font-display);
}

@media (prefers-color-scheme: dark) {
  .pill--cefr {
    color: var(--sage);
  }
}

.pill--fmt {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.78rem;
}

/* --------------------------------------------------------- 12. Feature list */

.features {
  display: grid;
  gap: var(--sp-8);
  margin-top: var(--sp-8);
}

.feature h3 {
  margin-bottom: var(--sp-2);
}

.feature p {
  margin: 0;
  color: var(--text-muted);
}

/* A feature card is usually one paragraph; where it runs to two, they need
   separating or they read as one wall. */
.feature p + p {
  margin-top: var(--sp-4);
}

.section--dark .feature p {
  color: var(--on-dark-muted);
}

.grid-3 {
  display: grid;
  gap: var(--sp-8);
}

@media (min-width: 700px) {
  .grid-3 {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* ------------------------------------------------------------ 13. Pricing */

.plans {
  display: grid;
  gap: var(--sp-6);
  margin-top: var(--sp-8);
  align-items: start;
}

/* Three plans go straight from stacked to three-up: an intermediate two-column
   step would orphan the third card on a row of its own, and a pricing table
   reads worst exactly where it looks lopsided. 1120px container / 3 tracks is
   ~340px a card, so the padding comes in a notch at the same breakpoint. */
@media (min-width: 1000px) {
  .plans {
    grid-template-columns: repeat(3, 1fr);
    gap: var(--sp-6);
  }
}

.plan {
  position: relative;
  padding: var(--sp-8);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-1);
}

.plan--featured {
  border-color: var(--orange);
  border-width: 2px;
  box-shadow: var(--shadow-2);
}

.plan__ribbon {
  position: absolute;
  top: -0.85rem;
  left: var(--sp-8);
  padding: 0.2rem 0.7rem;
  border-radius: var(--radius-pill);
  background: var(--orange);
  color: var(--on-orange);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* Three-up leaves ~340px a card, so the padding comes in a notch at the same
   breakpoint. After .plan and .plan__ribbon, or the base rules would win. */
@media (min-width: 1000px) {
  .plan {
    padding: var(--sp-6);
  }

  .plan__ribbon {
    left: var(--sp-6);
  }
}

.plan__name {
  font-family: var(--font-display);
  font-size: 1.5rem;
  font-weight: 700;
  margin-bottom: var(--sp-1);
}

.plan__tagline {
  color: var(--text-muted);
  font-size: 0.95rem;
  margin-bottom: var(--sp-6);
}

.price {
  display: flex;
  align-items: baseline;
  gap: 0.35rem;
  margin-bottom: var(--sp-2);
}

.price__amount {
  font-family: var(--font-display);
  font-size: 2.6rem;
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.02em;
}

.price__per {
  color: var(--text-muted);
  font-size: 0.95rem;
}

.price__note {
  min-height: 1.4em;
  font-size: 0.85rem;
  color: var(--orange-ink);
  font-weight: 650;
}

/* The block owns the gap down to the feature list, not the note — so a trial
   pill can sit between them without any card needing different spacing. The
   hidden half of the monthly/annual pair contributes nothing either way. */
.price-block {
  margin-bottom: var(--sp-6);
}

.plan__trial {
  display: inline-block;
  margin-bottom: var(--sp-6);
  padding: 0.3rem 0.75rem;
  border-radius: var(--radius-pill);
  background: var(--sage-tint);
  color: var(--sage);
  font-size: 0.82rem;
  font-weight: 650;
}

.plan__features li {
  display: flex;
  gap: 0.6rem;
  padding: 0.32rem 0;
  color: var(--text-muted);
  font-size: 0.95rem;
}

.plan__features li::before {
  content: "✓";
  flex: 0 0 auto;
  color: var(--sage);
  font-weight: 700;
}

.plan__features li.is-excluded {
  color: var(--text-faint);
}

.plan__features li.is-excluded::before {
  content: "✕";
  color: var(--text-faint);
  font-weight: 600;
}

/* The "Everything in Listen, plus:" row is a heading, not a feature — no tick. */
.plan__features li.plan__grouprow {
  color: var(--text);
  font-weight: 700;
  padding-bottom: var(--sp-1);
}

.plan__features li.plan__grouprow::before {
  content: none;
}

/* The difference table. The cards list only what each tier *adds*, so this is
   where the one axis that actually costs money is shown whole — and where the
   missing tick in "read-along in your own books" has to be impossible to miss.
   A real <table> because it is one: three plans against four claims. */
.compare {
  width: 100%;
  margin-top: var(--sp-8);
  border-collapse: collapse;
  font-size: 0.95rem;
}

.compare caption {
  margin-bottom: var(--sp-3);
  color: var(--text-muted);
  font-size: 0.9rem;
  text-align: left;
}

.compare th,
.compare td {
  padding: var(--sp-3) var(--sp-2);
  border-bottom: 1px solid var(--border);
  text-align: center;
}

.compare thead th {
  font-family: var(--font-display);
  font-size: 1rem;
  color: var(--text);
}

/* The row label column: left-aligned, and the only cell that may wrap freely. */
.compare tbody th {
  width: 46%;
  text-align: left;
  font-weight: 400;
  color: var(--text-muted);
}

.compare .yes {
  color: var(--sage);
  font-weight: 700;
}

.compare .no {
  color: var(--text-faint);
}

/* A cell that answers in words rather than with a tick. Only the sync row uses
   it: Solo and Duet both line the text up in your own books, and "on request"
   versus "as it plays" is the whole difference — two ticks would hide it and a
   tick plus a dash would deny Solo something it does. */
.compare .says {
  font-size: 0.85rem;
  line-height: 1.25;
  color: var(--sage);
  font-weight: 700;
}

/* The row the upgrade is actually about. */
.compare tr.compare__key th,
.compare tr.compare__key td {
  background: var(--orange-tint);
  font-weight: 700;
  color: var(--text);
}

.compare tr.compare__key .no {
  color: var(--text-faint);
  font-weight: 400;
}

.compare tr.compare__key .says {
  color: var(--text);
}

@media (max-width: 560px) {
  .compare {
    font-size: 0.88rem;
  }

  .compare th,
  .compare td {
    padding: var(--sp-2) var(--sp-1);
  }

  .compare tbody th {
    width: 40%;
  }
}

/* Billing toggle — pure CSS, because script-src 'self' means no inline JS and
   a whole external file for one checkbox is silly. The checkbox lives BEFORE
   the cards in the DOM so a general sibling selector can reach them. */
.billing {
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: var(--sp-8);
}

.billing__toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.25rem;
  border-radius: var(--radius-pill);
  background: var(--bg-alt);
  border: 1px solid var(--border);
  cursor: pointer;
  user-select: none;
}

.billing__opt {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  min-height: 38px;
  padding: 0 0.95rem;
  border-radius: var(--radius-pill);
  font-size: 0.9rem;
  font-weight: 650;
  color: var(--text-muted);
  transition: background-color 0.15s ease, color 0.15s ease;
}

.billing__save {
  font-size: 0.75rem;
  font-weight: 700;
  padding: 0.1rem 0.45rem;
  border-radius: var(--radius-pill);
  background: var(--sage-tint);
  color: var(--sage);
}

.billing__input:not(:checked) ~ .billing .billing__opt--m,
.billing__input:checked ~ .billing .billing__opt--a {
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow-1);
}

.billing__input:focus-visible ~ .billing .billing__toggle {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
}

/* Swap which price is in the DOM flow. display:none (not opacity/visibility)
   so a screen reader announces exactly one price per plan. */
.billing__input:not(:checked) ~ .plans .price-block--annual,
.billing__input:checked ~ .plans .price-block--monthly {
  display: none;
}

/* --------------------------------------------------------------- 14. FAQ */

.faq {
  margin-top: var(--sp-8);
  border-top: 1px solid var(--border);
}

.faq details {
  border-bottom: 1px solid var(--border);
}

.faq summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  min-height: 56px;
  padding: var(--sp-4) 0;
  font-weight: 650;
  font-size: 1.02rem;
  cursor: pointer;
  list-style: none;
}

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

.faq summary::after {
  content: "+";
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--bg-alt);
  color: var(--text-muted);
  font-size: 1.15rem;
  line-height: 1;
  transition: transform 0.18s ease;
}

.faq details[open] summary::after {
  content: "−";
  transform: rotate(180deg);
}

.faq details > *:not(summary) {
  margin-bottom: var(--sp-4);
  color: var(--text-muted);
}

/* ------------------------------------------------------------- 15. Footer */

.site-footer {
  background: var(--bg-alt);
  border-top: 1px solid var(--border);
  padding-block: var(--sp-16) var(--sp-8);
  font-size: 0.92rem;
}

.footer-grid {
  display: grid;
  gap: var(--sp-8);
}

@media (min-width: 640px) {
  .footer-grid {
    grid-template-columns: 1.4fr 1fr 1fr;
    gap: var(--sp-12);
  }
}

.site-footer h3 {
  font-family: var(--font-ui);
  font-size: 0.78rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: var(--sp-3);
}

.site-footer li {
  padding: 0.22rem 0;
}

.site-footer a {
  color: var(--text-muted);
  text-decoration: none;
}

.site-footer a:hover {
  color: var(--text);
  text-decoration: underline;
}

/* The language switcher. Plain links, not a dropdown: .nav is display:none
   below 720px and .site-header is a sticky, backdrop-filtered stacking context,
   so a popover up there is a layout fight — and a CSS-only one has no keyboard
   dismissal, which script-src 'self' makes awkward to add. Eleven footer links
   are also eleven crawlable links, which is what gets the hreflang cluster
   discovered in the first place. */
.langs {
  margin-top: var(--sp-12);
}

.langs ul {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-4);
  list-style: none;
  padding: 0;
}

.langs [aria-current] {
  color: var(--text);
  font-weight: 700;
  text-decoration: underline;
}

/* The system stack, NOT Literata. These labels are endonyms, so "Русский" sits
   in the footer of every page on the site — inheriting the body font would pull
   the Cyrillic subset onto the English homepage for two words and regress the
   LCP the hero's fetchpriority="high" was tuned for. Font matching only walks
   the declared family list, so pointing this at the UI stack (which covers
   Cyrillic everywhere, for zero bytes) removes the fetch entirely. */
.langs a {
  font-family: var(--font-ui);
}

.footer-bottom {
  margin-top: var(--sp-12);
  padding-top: var(--sp-6);
  border-top: 1px solid var(--border);
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
  justify-content: space-between;
  color: var(--text-faint);
  font-size: 0.85rem;
}

.footer-bottom p {
  margin: 0;
  max-width: none;
}

/* --------------------------------------------------- 16. Support / legal */

.doc {
  padding-block: var(--sp-16);
}

.doc h1 {
  font-family: var(--font-display);
  font-size: clamp(2rem, 1.5rem + 2vw, 2.8rem);
  line-height: 1.12;
  letter-spacing: -0.02em;
}

.doc h2 {
  font-size: clamp(1.3rem, 1.15rem + 0.7vw, 1.6rem);
  margin-top: var(--sp-12);
}

.doc h3 {
  margin-top: var(--sp-8);
}

.doc ul:not(.plan__features) {
  margin: 0 0 var(--sp-4);
  padding-left: 1.15rem;
  list-style: disc;
  max-width: var(--measure);
  color: var(--text-muted);
}

.doc ul:not(.plan__features) li {
  padding: 0.15rem 0;
}

.doc ol {
  margin: 0 0 var(--sp-4);
  padding-left: 1.3rem;
  max-width: var(--measure);
  color: var(--text-muted);
}

.doc ol li {
  padding: 0.15rem 0;
}

.callout {
  margin: var(--sp-6) 0;
  padding: var(--sp-6);
  border-radius: var(--radius);
  border: 1px solid var(--border);
  border-left: 4px solid var(--orange);
  background: var(--surface);
}

.callout p:last-child {
  margin-bottom: 0;
}

/* --------------------------------------------------------------- 17. 404 */

.notfound {
  min-height: 60vh;
  display: grid;
  place-items: center;
  text-align: center;
  padding-block: var(--sp-24);
}

.notfound h1 {
  font-family: var(--font-display);
  font-size: clamp(2rem, 1.5rem + 2vw, 2.8rem);
}

.notfound p {
  margin-inline: auto;
}

.notfound .btn + .btn {
  margin-left: var(--sp-2);
}

/* ------------------------------------------------------------ 18. Reveal */

/* The hiding rule is gated behind a class that js/reveal.js adds on its FIRST
   line. If the script 404s, is blocked by an extension, or trips the CSP, the
   page renders fully visible instead of blank. Never invert this. */
.reveal-on .reveal {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.5s ease, transform 0.5s cubic-bezier(0.22, 0.61, 0.36, 1);
}

.reveal-on .reveal.is-in {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  .reveal-on .reveal {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

@media print {
  .site-header,
  .waveform,
  .gloss-chip {
    display: none;
  }
}
