/* ============================================================
   style.css  —  THE "LOOK" OF THE READER
   ------------------------------------------------------------
   Grouped into sections so you can find things easily.

   ONE RULE ABOVE ALL OTHERS, since milestone 44: no colour is written
   into a rule below. Every colour in this file is a VARIABLE named for
   its JOB ("--ink-soft", "--surface", "--danger") rather than for what
   it looks like ("#6b6b6b"). That is what makes a dark theme possible:
   SECTION 1B re-states the same forty-odd names in darker values, and
   every rule under it changes without being touched.

   If you find yourself typing a "#" below SECTION 1B, stop — the colour
   you want either already has a name or needs one.
   ============================================================ */


/* ------------------------------------------------------------
   SECTION 1 — THE DESIGN TOKENS (light theme)
   ------------------------------------------------------------
   Six families, and everything in the app is built from them:

     READING     the two fonts + sizes the ☰ menu changes
     INK         text, from strongest to faintest
     SURFACE     the page and the things sitting on it
     ACCENT      the one blue that means "this is the app talking"
     SIGNAL      warm / good / danger / caution — meanings, not decoration
     FORM        radius, shadow, spacing and type sizes

   The INK values are picked so every one of them clears WCAG AA (4.5:1)
   against the surface it is used on. --ink-faint is the single exception,
   and since milestone 55 it is also the single UNUSED one: the page-turning
   row was its only home, and page turning is gone. It is kept declared (in
   all three palette blocks, so they stay identical) as the ink to reach for
   if something ever genuinely needs to look switched off.
   ------------------------------------------------------------ */

:root {
  color-scheme: light dark;

  /* ---- READING: the settings the ☰ menu writes to ----
     The story text and the translation text each have their OWN font and
     size, so you can tune them separately. "scale" is a multiplier:
     1 = 100% (default), 0.9 = 90%, 1.8 = 180%. */
  --text-font: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI",
               Helvetica, Arial, sans-serif;
  --trans-font: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI",
                Helvetica, Arial, sans-serif;
  --text-scale: 1;
  --trans-scale: 1;

  /* Inter carries far more ink at the same nominal size than Georgia did
     (a much taller x-height), so the story text drops from 1.5rem to 1.35
     to LOOK the same size. The translation size is deliberately left at
     1.05rem: the translation box's height is derived from it, and that
     height is a recorded number the jiggle check in CLAUDE.md §8 watches. */
  --text-size: calc(1.35rem * var(--text-scale));   /* story text size  */
  --trans-size: calc(1.05rem * var(--trans-scale)); /* translation size */

  /* THE STORY FONT'S OWN LINE BOX, as a multiple of its font-size. The fourth
     variable JavaScript writes, and the second it works out by MEASURING
     rather than by being told (app.js PART 6's `measureLineBox`): how tall a
     line the chosen typeface actually draws. SECTION 5's line-height formula
     is built on it — see the long note there for why a single constant could
     not do this job. 1.37 is the tallest of the five, so this default
     over-reserves rather than under-reserves before the first measurement. */
  --text-lead: 1.37;

  /* HOW MANY LINES TALL THE TRANSLATION BOX IS. The third variable
     JavaScript writes (the fonts and scales above are the other two), and
     the only one it works out by MEASURING rather than by being told: when a
     text is opened, app.js PART 5 renders that text's longest translations
     into the box, sees how many lines they need, and writes the answer here.
     Two is the floor, which is what the two short stories get — so for them
     this is exactly the 2.8em the box has always been. See SECTION 4. */
  --trans-lines: 2;

  /* The INTERFACE font — buttons, menus, panels. Deliberately separate from
     the two reading fonts above: those change with the settings menu, this
     one never does, so the app's furniture stays put while you read.
     Every stack here names Inter first and then real system fonts, so the
     app looks like itself online and merely plainer offline. */
  --ui-font: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI",
             Helvetica, Arial, sans-serif;
  --mono-font: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;

  /* The DISPLAY font — page titles and nothing else. A serif here is doing
     one job: it says "this is a place for reading" before you have read a
     word of it. Everything else on the page is Inter, so the contrast
     between the two is what gives the app a voice rather than a stylesheet. */
  --display-font: "Source Serif 4", Georgia, "Iowan Old Style",
                  "Times New Roman", serif;

  /* ---- INK: text, strongest first ----
     The neutrals are very slightly VIOLET rather than pure grey, so they sit
     with the accent instead of beside it. It is a couple of points of
     saturation — you would never name the colour "purple" — but a page of
     pure-grey text under a violet button always looks like two designs. */
  --ink-strong: #191622;   /* headings and numbers — the loudest thing said */
  --ink:        #2c2937;   /* ordinary body text */
  --ink-soft:   #56515f;   /* secondary text: hints, help, captions */
  --ink-mute:   #6b6577;   /* labels, small print — AA on every surface above,
                              INCLUDING --surface-sunk */
  --ink-faint:  #a8a2b2;   /* the one colour that does NOT clear AA. It was for
                              a DISABLED control — which the guidelines exempt
                              — and has had no user since the pager went in
                              milestone 55. Do not reach for it for anything
                              that has to be READ. */

  /* ---- SURFACE: the page, and what sits on it ----
     Three levels, and they get LIGHTER as they come forward. Dark mode
     keeps that direction, which is why elevation still reads there.
     The page is a warm off-white and a card is pure white: that gap is
     what lets a card read as lifted without needing a heavy border. */
  --page:       #f6f4f0;   /* the paper the whole app is printed on */
  --surface:    #ffffff;   /* a card lifted off it */
  --surface-2:  #faf9f7;   /* a quieter card, or a card on a card */
  --surface-sunk: #f0edea; /* pressed IN: inputs, empty calendar days */
  --line:       #e4e0da;   /* a border you are meant to notice */
  --line-soft:  #eeebe6;   /* a hairline between rows */

  /* ---- ACCENT: the app's own voice ----
     A violet-indigo, not the slate blue this app wore until milestone 45.
     The slate was the colour of an admin panel; a violet reads as something
     you chose to open rather than something you have to fill in. */
  --accent:       #5b4bd6;  /* filled buttons, the active state */
  --accent-hover: #4a3ab8;  /* the same, pressed */
  --accent-text:  #4c3bb8;  /* accent-COLOURED TEXT — darker than --accent, so
                               it clears AA everywhere it lands. The tightest
                               pair in the app is this on --accent-line, which
                               is a mid-strength calendar day: 4.81:1. */
  --accent-soft:  #e7e3fb;  /* a tinted background */
  --accent-wash:  #f3f0fe;  /* the faintest tint there is */
  --accent-line:  #cbc3f2;
  --on-accent:    #ffffff;  /* what you write ON --accent. Not "white" —
                               in dark mode the accent is light and this
                               becomes near-black. That is the whole point
                               of giving it a name. */

  /* THE LOUD PANEL. The big filled invitation on the landing page (the
     "Start reading French today" block, and the bar that follows you up
     it). It is NOT simply --accent, and the reason is the whole argument of
     SECTION 1B: a mid-violet button is a small bright thing on cream and
     reads as confident, but a whole panel of the DARK theme's light violet
     would be a slab of daylight in a dark room. So the job gets its own
     name, and the night version goes the other way — a deep violet with
     light text — exactly as "surfaces come forward by getting lighter" says
     it should. --panel-loud-mute is the small print ON it. */
  --panel-loud:      #5b4bd6;
  --panel-loud-ink:  #ffffff;
  --panel-loud-mute: #e4e0fd;   /* MEASURED, not picked. The first try was
                                   #d7d1fa, which is 4.20:1 on the violet and
                                   so fails AA for small text by a margin no
                                   eye would catch — the sweep in §8 found it.
                                   This is 4.79:1, and still clearly dimmer
                                   than the white above it (6.14:1), so the
                                   panel keeps two levels of ink rather than
                                   one. */

  /* ---- SIGNAL: colours that MEAN something ---- */
  --warm:       #c2593a;   /* the second voice: emphasis, "not yet", levels.
                              A terracotta, picked as the warm complement of
                              the violet rather than as "orange". */
  --warm-text:  #a2452a;   /* warm TEXT, darker so it clears AA on --warm-soft */
  --warm-soft:  #fbe5dd;
  --warm-wash:  #fdf3ef;
  --warm-line:  #f2cbbd;

  --good:       #197a4b;   /* "read 7+ times", "Good", "Saved." */
  --good-soft:  #ddf1e5;
  --good-line:  #b4ddc6;

  --danger:     #b32e28;   /* delete, clear, "Again" */
  --danger-soft:#fce8e6;
  --danger-line:#f2c9c5;

  --caution:    #92590f;   /* "Hard" */
  --caution-soft:#fbeed8;
  --caution-line:#ecdcc2;

  --mark:       #ffe6a3;   /* the highlighter under the word you're hovering */
  --speak:      #4634bd;   /* KARAOKE: the word being spoken */
  --speak-dot:  #5b4bd6;   /* ...and the dot beneath it */
  --speak-wash: rgba(91, 75, 214, 0.06);  /* the sentence being spoken */

  /* A story cover with no picture is its first letter on a coloured tile.
     app.js picks only the HUE (from the title); these four decide how that
     hue is turned into a colour, so the same tile can be pale-on-cream here
     and deep-and-muted in the dark theme. */
  --cover-bg-s: 46%;  --cover-bg-l: 88%;
  --cover-fg-s: 44%;  --cover-fg-l: 33%;

  /* ---- FORM: the shapes and depths, so nothing is invented per-rule ---- */
  --r-xs: 7px;
  --r-sm: 10px;
  --r-md: 14px;
  --r-lg: 20px;
  --r-xl: 26px;
  --r-pill: 999px;

  /* Three levels of "off the page", and one for a thing being dragged over
     everything else. Two shadows each: a tight one for the edge, a wide one
     for the ambient light. The tint is the accent's own hue rather than
     black, which is what stops a stack of white cards looking sooty. */
  --shadow-1: 0 1px 2px rgba(35, 28, 62, 0.05),
              0 1px 3px rgba(35, 28, 62, 0.05);
  --shadow-2: 0 2px 4px rgba(35, 28, 62, 0.05),
              0 8px 20px rgba(35, 28, 62, 0.08);
  --shadow-3: 0 8px 22px rgba(35, 28, 62, 0.10),
              0 26px 56px rgba(35, 28, 62, 0.14);

  /* ---- THE BUTTON FACE ----
     "Depth" is two things at once, and a button needs BOTH: a face that is a
     step away from whatever it is sitting on, and an EDGE you can actually
     see. A shadow alone is not enough — on a pale page it is nearly
     invisible, and in the dark theme it is invisible altogether (there is no
     light to cast one), which is exactly why the borders below get stronger
     at night rather than weaker. These three names mean every button in the
     app is raised the same way, and none of them has to invent its own. */
  --btn-face:       #ffffff;   /* the top of the button */
  --btn-face-hover: #f7f4ef;   /* on paper, hovering DARKENS it slightly */
  --btn-edge:       #d5cec4;   /* the rim, clearly visible on cream and on white */
  --btn-lip-neutral: #c0b6a4;  /* the ordinary lip, under a plain button. It
                                  has its own name only so that a variant which
                                  swapped in the accent one can put it back. */
  --btn-lip:        #c0b6a4;   /* THE UNDERSIDE. A solid band along the bottom,
                                  not a blur: it is the side of the button you
                                  would see if it were a real key, which is why
                                  pressing one shortens it instead of moving it */
  --accent-lip:     #3d2fa6;   /* the same, under a FILLED button — a darker
                                  shade of the accent rather than a grey */

  --ring: #8f80ee;         /* the keyboard focus ring */

  /* The two faint washes behind the page (SECTION 2's body rule). They are
     rgba rather than one of the colours above because what matters here is
     how nearly-invisible they are, and an alpha is the only honest way to
     say "6% of this". */
  --glow-cool: rgba(91, 75, 214, 0.07);
  --glow-warm: rgba(194, 89, 58, 0.05);

  /* THE FLAGS (app.js PART 2's FLAGS map, SECTION 17's .flag). The one family
     of colours here that is NOT restated in SECTION 1B, and that is on
     purpose rather than an oversight: a flag is a real-world object, so the
     French tricolour is the same three colours at midnight as at noon. They
     are named here anyway, because §6's rule is that a colour in this app has
     a name and lives in the token block — not that every colour has to have
     two values. Slightly softened from the official specifications, which are
     drawn for a flagpole and read as shouting at 24 pixels across.

     --flag-edge is the hairline the .flag span draws around itself. It is the
     one thing here doing a job rather than naming a colour: without it the
     white band of the French flag disappears into a white panel. A
     translucent dark line rather than a solid one, so it reads as an edge on
     a bright band and as almost nothing on a dark one. */
  --flag-blue:  #2c4bab;
  --flag-white: #f7f6f3;
  --flag-red:   #d8443c;
  --flag-gold:  #edb734;
  --flag-black: #48465a;   /* NOT actually black, and measured rather than
                              chosen: a true black band is 1.04:1 against a
                              dark-theme button face, i.e. Germany's flag
                              losing its top third at night. Lightened until
                              it clears the face (1.49:1, a wider step than
                              the button itself gets from the page) while
                              still holding 2.09:1 against the red band
                              beside it, which is the other thing it has to
                              stay apart from. */
  --flag-green: #2e9163;
  --flag-edge:  rgba(28, 24, 44, 0.24);

  /* THE LECTICAL MARK (SECTION 19's .lp-mark, and the favicon drawn into
     index.html and landing.html). Like the flags, declared ONCE and not
     restated in SECTION 1B: a logo is the same object at midnight as at
     noon, and it has to match the browser-tab icon, which cannot follow the
     theme at all. The mark is a violet tile with a serif L on it, the L
     underlined in terracotta (the same underline the reader puts under the
     word you point at), and a fine keyline just inside the tile's edge.
     --brand-underline-on-light is the darker terracotta for the one place
     the mark is drawn the other way round, white tile and violet L, on the
     violet panel at the bottom of the front page. Measured: the light
     underline is 3.15:1 against the violet tile, the dark one 4.4:1 against
     white, both above the 3:1 a graphic needs to be seen. */
  --brand-tile:              #5b4bd6;
  --brand-ink:               #ffffff;
  --brand-underline:         #f4a882;
  --brand-underline-on-light: #c2593a;

  /* THE CORNER TILES (SECTION 2). How wide one is, and how far it stands off
     its corner. They are variables because the SECOND button on a side is
     positioned as "one width and a gap further in" — so the whole row can be
     shrunk for a phone by changing these three numbers once, instead of
     hunting down six pinning rules. The narrow-screen values are in the
     media query at the end of SECTION 2. */
  --corner-w: 78px;
  --corner-gap: 8px;
  --corner-edge: 14px;

  /* An 8px rhythm. Every margin and gap below is one of these, which is why
     the spacing looks deliberate rather than negotiated rule by rule. */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 24px;
  --sp-6: 32px;
  --sp-7: 48px;
  --sp-8: 64px;

  /* A type scale for the INTERFACE (the reading text has its own two sizes
     above). Each step is about 1.2x the one before, which is close enough to
     a musical interval that the sizes look related rather than arbitrary. */
  --fs-xs:  0.75rem;
  --fs-sm:  0.86rem;
  --fs-md:  1rem;
  --fs-lg:  1.15rem;
  --fs-xl:  1.4rem;
  --fs-2xl: 1.75rem;

  /* The page titles. clamp() means one value covers a phone and a desktop:
     never below the first number, never above the last, and proportional to
     the window in between — so a heading is bold on a laptop without
     wrapping to four lines on a phone. */
  --fs-display: clamp(2.1rem, 1.35rem + 3.2vw, 3rem);

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);   /* the app's one easing curve */
}


/* ------------------------------------------------------------
   SECTION 1B — THE DARK THEME
   ------------------------------------------------------------
   The same names again, in night values. Nothing below this block knows
   there are two themes: it asks for "--surface" and gets whichever one is
   in force.

   Four things are deliberately NOT just "the light theme inverted", because
   inverting is what makes dark modes look cheap:

     1. Nothing is pure black or pure white. Pure white text on pure black
        haloes (the letters bleed into the background for most eyes), so the
        page is a very dark warm-grey and the text stops at #e6e4df.
     2. Surfaces get LIGHTER as they come forward, not darker. In the dark
        there is no shadow to speak of, so lightness is what says "this card
        is above the page".
     3. The accent LIGHTENS and loses saturation. A saturated mid-blue that
        looks calm on cream vibrates against a dark ground.
     4. Hover goes the other way. On paper, hovering darkens; in the dark,
        hovering lightens — which is why --accent-hover exists as a name
        rather than as "the accent, 15% darker".

   Since milestone 47 there are TWO ways this palette comes on, because the
   reading settings now carry a Light / Dark / Auto choice (app.js PART 6):

     * "Auto" — the choice everybody starts with — leaves it to the device,
       which is what the @media block below reads. It steps aside for someone
       who has explicitly asked for the light theme, hence the :not(...).
     * "Dark" writes data-theme="dark" onto <html>, which is the second block.

   The two blocks hold the SAME declarations, character for character, and a
   check in CLAUDE.md §8 compares them so they cannot quietly drift apart.
   Plain CSS has no way to give one block of declarations two triggers, so the
   copy is unavoidable; the second one is indented like the first purely so
   that comparison stays a line-for-line one.
   ------------------------------------------------------------ */

@media (prefers-color-scheme: dark) {
  /* "Follow this device" — unless the reader has chosen light on purpose. */
  :root:not([data-theme="light"]) {
    --ink-strong: #f4f2f8;
    --ink:        #e4e1ea;
    --ink-soft:   #aaa5b6;
    --ink-mute:   #a29cae;   /* lightened in milestone 48, and again in 50:
                                the raised button face (--btn-face) is a
                                lighter ground than any surface this had to
                                clear before, and small print ON a button — a
                                story card's level line, a font's reason — has
                                to stay AA on it. This value and --btn-face
                                MOVE TOGETHER: it is the tightest text pair in
                                the dark theme (4.60:1 on a hovered button), so
                                lightening the face without lightening this
                                takes that pair below AA. */
    --ink-faint:  #5b5668;

    --page:         #131218;
    --surface:      #1c1b24;
    --surface-2:    #24222d;
    --surface-sunk: #0f0e14;
    --line:         #34313f;
    --line-soft:    #27262f;

    --accent:       #a89bf5;
    --accent-hover: #bcb2f9;
    --accent-text:  #b3a7f7;
    --accent-soft:  #2b2647;
    --accent-wash:  #1f1c31;
    --accent-line:  #423b64;
    --on-accent:    #17132b;

    /* The loud panel, at night: a deep violet with light text rather than
       the light violet with dark text the light theme uses. See SECTION 1. */
    --panel-loud:      #272045;
    --panel-loud-ink:  #f4f2f8;
    --panel-loud-mute: #b6aed0;

    --warm:       #ea9370;
    --warm-text:  #ea9370;   /* on a dark ground the emphasis colour is already
                                well clear of its own soft background */
    --warm-soft:  #37231b;
    --warm-wash:  #261813;
    --warm-line:  #4e3125;

    --good:       #6cc48c;
    --good-soft:  #1a2f22;
    --good-line:  #2e4d3a;

    --danger:     #f0908a;
    --danger-soft:#33201e;
    --danger-line:#55322e;

    --caution:    #d9ab63;
    --caution-soft:#302614;
    --caution-line:#4c3d20;

    /* The highlighter can't be yellow here — a bright wash under dark-theme
       text is the one thing that really does hurt. It becomes a warm, dim
       lift instead, and the text on it stays --ink. */
    --mark:       #574722;
    --speak:      #b6a9ff;
    --speak-dot:  #9c8bfb;
    --speak-wash: rgba(255, 255, 255, 0.055);

    --cover-bg-s: 30%;  --cover-bg-l: 21%;
    --cover-fg-s: 46%;  --cover-fg-l: 72%;

    /* Shadows are nearly invisible on a dark ground, so they go much deeper
       and the borders do more of the work of separating things. */
    --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.45);
    --shadow-2: 0 2px 6px rgba(0, 0, 0, 0.42),
                0 8px 22px rgba(0, 0, 0, 0.36);
    --shadow-3: 0 8px 24px rgba(0, 0, 0, 0.5),
                0 24px 56px rgba(0, 0, 0, 0.45);

    /* The button face, again (see SECTION 1). In the dark it works the other
       way round: the face is LIGHTER than the page, hovering lightens it
       further, and the rim does most of the work of saying "this is a
       thing you can press" — the shadows below have almost nothing to
       land on.

       Milestone 50 raised all three a step, because a button at #262532 on a
       #131218 page was 1.23:1 — technically a different colour, and very
       nearly not one. The face is now 1.37:1 against the page and the rim is
       plainly visible. How far this could go was decided by the small print
       sitting ON the face, not by taste: see --ink-mute above. The rim was
       free to move furthest, since a BORDER has no contrast rule to clear. */
    --btn-face:       #2e2c3b;
    --btn-face-hover: #353348;
    --btn-edge:       #565272;
    --btn-lip-neutral: #1b1a26;
    --btn-lip:        #1b1a26;
    --accent-lip:     #6a5cbb;

    --ring: #a89bf5;

    --glow-cool: rgba(168, 155, 245, 0.085);
    --glow-warm: rgba(234, 147, 112, 0.045);
  }
}

/* The same palette again, for someone who chose Dark while their device is
   set to light. KEEP THESE DECLARATIONS IDENTICAL to the ones above. */
:root[data-theme="dark"] {
    --ink-strong: #f4f2f8;
    --ink:        #e4e1ea;
    --ink-soft:   #aaa5b6;
    --ink-mute:   #a29cae;   /* lightened in milestone 48, and again in 50:
                                the raised button face (--btn-face) is a
                                lighter ground than any surface this had to
                                clear before, and small print ON a button — a
                                story card's level line, a font's reason — has
                                to stay AA on it. This value and --btn-face
                                MOVE TOGETHER: it is the tightest text pair in
                                the dark theme (4.60:1 on a hovered button), so
                                lightening the face without lightening this
                                takes that pair below AA. */
    --ink-faint:  #5b5668;

    --page:         #131218;
    --surface:      #1c1b24;
    --surface-2:    #24222d;
    --surface-sunk: #0f0e14;
    --line:         #34313f;
    --line-soft:    #27262f;

    --accent:       #a89bf5;
    --accent-hover: #bcb2f9;
    --accent-text:  #b3a7f7;
    --accent-soft:  #2b2647;
    --accent-wash:  #1f1c31;
    --accent-line:  #423b64;
    --on-accent:    #17132b;

    /* The loud panel, at night: a deep violet with light text rather than
       the light violet with dark text the light theme uses. See SECTION 1. */
    --panel-loud:      #272045;
    --panel-loud-ink:  #f4f2f8;
    --panel-loud-mute: #b6aed0;

    --warm:       #ea9370;
    --warm-text:  #ea9370;   /* on a dark ground the emphasis colour is already
                                well clear of its own soft background */
    --warm-soft:  #37231b;
    --warm-wash:  #261813;
    --warm-line:  #4e3125;

    --good:       #6cc48c;
    --good-soft:  #1a2f22;
    --good-line:  #2e4d3a;

    --danger:     #f0908a;
    --danger-soft:#33201e;
    --danger-line:#55322e;

    --caution:    #d9ab63;
    --caution-soft:#302614;
    --caution-line:#4c3d20;

    /* The highlighter can't be yellow here — a bright wash under dark-theme
       text is the one thing that really does hurt. It becomes a warm, dim
       lift instead, and the text on it stays --ink. */
    --mark:       #574722;
    --speak:      #b6a9ff;
    --speak-dot:  #9c8bfb;
    --speak-wash: rgba(255, 255, 255, 0.055);

    --cover-bg-s: 30%;  --cover-bg-l: 21%;
    --cover-fg-s: 46%;  --cover-fg-l: 72%;

    /* Shadows are nearly invisible on a dark ground, so they go much deeper
       and the borders do more of the work of separating things. */
    --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.45);
    --shadow-2: 0 2px 6px rgba(0, 0, 0, 0.42),
                0 8px 22px rgba(0, 0, 0, 0.36);
    --shadow-3: 0 8px 24px rgba(0, 0, 0, 0.5),
                0 24px 56px rgba(0, 0, 0, 0.45);

    /* The button face, again (see SECTION 1). In the dark it works the other
       way round: the face is LIGHTER than the page, hovering lightens it
       further, and the rim does most of the work of saying "this is a
       thing you can press" — the shadows below have almost nothing to
       land on.

       Milestone 50 raised all three a step, because a button at #262532 on a
       #131218 page was 1.23:1 — technically a different colour, and very
       nearly not one. The face is now 1.37:1 against the page and the rim is
       plainly visible. How far this could go was decided by the small print
       sitting ON the face, not by taste: see --ink-mute above. The rim was
       free to move furthest, since a BORDER has no contrast rule to clear. */
    --btn-face:       #2e2c3b;
    --btn-face-hover: #353348;
    --btn-edge:       #565272;
    --btn-lip-neutral: #1b1a26;
    --btn-lip:        #1b1a26;
    --accent-lip:     #6a5cbb;

    --ring: #a89bf5;

    --glow-cool: rgba(168, 155, 245, 0.085);
    --glow-warm: rgba(234, 147, 112, 0.045);
}

/* And tell the BROWSER which of its own two looks to draw with, so its
   scrollbars, form controls and text selection follow an explicit choice
   too. ("Auto" needs nothing here: SECTION 1's `color-scheme: light dark`
   already says "either, you pick".) */
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"]  { color-scheme: dark; }


/* ------------------------------------------------------------
   SECTION 2 — PAGE BASICS, AND THE LOOKS SHARED EVERYWHERE
   ------------------------------------------------------------
   Everything here is used by nearly every later section: how anything gets
   hidden, the standard .btn button, the .corner-button / .corner-panel pair
   the six top-corner buttons share, the small-caps label, and the focus ring.
   Later sections only say how their thing DIFFERS — which is usually one or
   two lines.
   ------------------------------------------------------------ */

*, *::before, *::after {
  box-sizing: border-box;   /* padding counts INSIDE a width, everywhere */
}

body {
  margin: 0;
  color: var(--ink);
  font-family: var(--text-font);   /* the story + title inherit this */

  /* The page is not one flat colour. Two very faint washes — one accent, one
     warm — sit in opposite corners behind the paper, which is the difference
     between "a cream rectangle" and "a lit room". They are far too weak to
     read as colour; you only notice them if you take them away.
     "fixed" so the light doesn't slide up the screen as you scroll. */
  background-color: var(--page);
  background-image:
    radial-gradient(70rem 46rem at 8% -12%, var(--glow-cool), transparent 62%),
    radial-gradient(62rem 42rem at 104% -4%, var(--glow-warm), transparent 64%);
  background-attachment: fixed;
  background-repeat: no-repeat;

  -webkit-font-smoothing: antialiased;    /* thinner, cleaner type on macOS */
  text-rendering: optimizeLegibility;
}

/* ONE way to hide things, used for both the ten screens and for smaller
   pieces inside them (e.g. the grade buttons before a card is flipped):
   the plain HTML "hidden" attribute. Browsers hide it by default, but any
   "display" rule of ours would quietly win — so we make it !important once,
   here, and never think about it again.
   (The ten screens also carry a "screen" class. It has no styles of its
   own on purpose — it's just a label, so you can spot the top-level
   containers in the HTML and in app.js's ALL_SCREENS list.) */
[hidden] {
  display: none !important;
}

/* ---- WHILE THE APP IS WORKING OUT WHO YOU ARE ----
   index.html's <body> starts with this class, and app.js PART 14 takes it off
   once the gate has decided. It hides the ten SCREENS only, so the <noscript>
   message underneath still shows for somebody with scripts switched off —
   which is the one case where the class is never removed. */
.app-checking .screen {
  display: none !important;
}

/* ---- THE FOCUS RING ----
   One rule, and it is the most valuable accessibility line in the file: if
   you get to a control with the Tab key rather than the mouse, you can SEE
   which one you are on. ":focus-visible" (not ":focus") is what keeps it off
   a button you merely clicked — the browser decides, and it decides well.
   The ring is drawn OUTSIDE the element with an offset, so it never changes
   any layout by appearing. */
:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
  border-radius: var(--r-xs);
}

/* ---- MOTION, FOR PEOPLE WHO WOULD RATHER NOT ----
   Some people get motion sickness from movement on screen, and their system
   has a switch for it. When it's on, every transition and animation here
   becomes instant: the card still flips, the button still highlights, they
   just don't travel. Nothing is lost — no meaning in this app is carried by
   motion alone. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---- The "you need JavaScript" message ----
   Shown ONLY when scripts are switched off, because the whole app is built
   by app.js and the ten screens would otherwise be ten empty boxes. */
.noscript {
  max-width: 32rem;
  margin: 15vh auto 0;
  padding: 0 var(--sp-5);
  font-family: var(--ui-font);
  color: var(--ink-soft);
  line-height: 1.6;
  text-align: center;
}

.noscript h1 {
  font-size: var(--fs-xl);
  color: var(--accent-text);
}


/* ---- THE STANDARD BUTTON ----
   Every button in the app starts from this and then overrides at most a
   couple of things. Two variants cover almost every case:
     .btn-primary  the filled, "this is the main action" version
     .btn-small    the quiet, secondary version

   The size is not arbitrary. 44px is the smallest target most people can hit
   reliably with a thumb (it is Apple's published minimum and roughly what
   Fitts's law predicts for a phone held one-handed), so a full-size button
   is at least that tall: 13px + 13px of padding around a 1rem line clears it.
   (The round ▶ and ＋ buttons are their own thing — SECTIONS 4 and 6.) */
.btn {
  font-family: var(--ui-font);
  font-size: var(--fs-md);
  font-weight: 600;               /* milestone 76: every button's words are a
                                     step bolder and in the strongest ink, so a
                                     label reads at a glance rather than on a
                                     second look */
  line-height: 1.25;
  padding: 13px 24px;
  color: var(--ink-strong);
  background-color: var(--btn-face);
  border: 1px solid var(--btn-edge);
  border-radius: var(--r-md);

  /* THE LIP — the thick bit along the bottom, and the whole reason a button
     here looks like something you could press rather than a coloured
     rectangle. It is a HARD shadow (no blur), so it reads as the side of a
     physical key catching light, with the soft ambient shadow underneath it
     doing the "off the page" part. */
  box-shadow: 0 4px 0 var(--btn-lip), var(--shadow-2);

  cursor: pointer;
  transition: background-color 0.16s var(--ease),
              border-color 0.16s var(--ease),
              color 0.16s var(--ease),
              box-shadow 0.16s var(--ease),
              transform 0.12s var(--ease);
}
/* Hovering LIFTS the button a pixel — and the lip grows by the same pixel, so
   the bottom edge stays put and the button really looks like it rose off the
   page rather than sliding up it. */
.btn:hover {
  background-color: var(--btn-face-hover);
  border-color: var(--accent-line);
  box-shadow: 0 5px 0 var(--btn-lip), var(--shadow-3);
  transform: translateY(-1px);
}
/* Pressing it drives the face DOWN onto its own lip: the button moves 2px
   down while the lip shrinks from 3px to 1, so the bottom edge again stays
   where it is and the whole thing simply gets shorter. That is what a key
   does, and it is the difference between "pressed" and "nudged". */
.btn:active {
  transform: translateY(3px);
  box-shadow: 0 1px 0 var(--btn-lip), var(--shadow-1);
}

.btn-primary {
  color: var(--on-accent);
  background-color: var(--accent);
  border-color: var(--accent);
  font-weight: 600;
  /* A grey lip under a violet button would look like a shadow of the wrong
     thing, so the variable itself is swapped and the rules above go on
     working unchanged. Every filled or tinted button does this. */
  --btn-lip: var(--accent-lip);
}
.btn-primary:hover {
  background-color: var(--accent-hover);
  border-color: var(--accent-hover);
  color: var(--on-accent);
}

/* The quiet, secondary size. It is SMALLER than a .btn and nothing else:
   until milestone 48 it also had its shadow taken away, which made a row of
   them ("Save profile", "Restore all defaults") read as text
   someone had drawn a box around rather than as buttons. It keeps the face,
   the rim and the lift; only the padding and the type size differ. */
.btn-small {
  padding: 9px 16px;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--ink);              /* quieter than a full button, still plainly
                                     readable (it was --ink-soft until 76) */
  border-radius: var(--r-sm);
  box-shadow: 0 3px 0 var(--btn-lip), var(--shadow-1);   /* a shallower lip,
                                                            in keeping with a
                                                            smaller button */
}
.btn-small:hover {
  color: var(--ink-strong);
  box-shadow: 0 4px 0 var(--btn-lip), var(--shadow-2);
}
.btn-small:active {
  transform: translateY(2px);
  box-shadow: 0 1px 0 var(--btn-lip), var(--shadow-1);
}

.btn-wide { width: 100%; }


/* ---- THE CORNER BUTTONS AND THEIR DROP-DOWN PANELS ----
   Six buttons live in the top corners: saved words (★) and the reading
   analysis (📊) on the left, the language shelf (🌐) and your account (👤)
   on the right, and — on the other screens — the back arrow (←) and the
   reading settings (☰). Each is a 46px square pinned to a corner; the panels
   that drop from them share a card look.

   Every one of them holds an INLINE SVG ICON rather than a character, and
   that is a real fix rather than a preference: before milestone 44 three of
   them were text glyphs (★ ← ☰) and three were colour emoji (📊 🌐 👤), so
   the top of the front page was drawn in three different visual languages,
   in whatever the operating system happened to think those characters looked
   like. One set of line icons at one weight is the whole difference. */
/* THE TILE. Two numbers decide where all six of them sit, which is why they
   are variables rather than repeated: --corner-w is how wide one is, and
   --corner-edge how far it stands off its corner. The second button on a side
   is then simply "one width and a gap further in", and the whole row can be
   shrunk for a phone by changing the two numbers in one media query below.

   Each tile carries its ICON ABOVE A WORD saying what it does. It was icon
   only until milestone 48, with the word available solely to a screen reader
   — which meant a sighted first-time visitor had four unexplained line
   drawings across the top of the front page and had to press them to find
   out. The label is why they are stacked rather than in a row: four labelled
   pills side by side do not fit a 320px phone, and four small tiles do. */
.corner-button {
  position: fixed;
  top: 14px;
  z-index: 30;

  display: flex;
  flex-direction: column;      /* the drawing, then the word under it */
  align-items: center;
  justify-content: center;
  gap: 3px;

  width: var(--corner-w);
  min-height: 50px;
  padding: 7px 6px 6px;

  font-family: var(--ui-font);
  font-size: var(--fs-md);
  font-weight: 600;
  line-height: 1;

  color: var(--ink);           /* a step stronger since milestone 76: the word
                                  under each icon is what says what it is */
  background-color: var(--btn-face);
  border: 1px solid var(--btn-edge);
  border-radius: 15px;
  box-shadow: 0 4px 0 var(--btn-lip), var(--shadow-2);   /* the same lip as .btn */
  cursor: pointer;
  transition: background-color 0.16s var(--ease),
              color 0.16s var(--ease),
              border-color 0.16s var(--ease),
              box-shadow 0.16s var(--ease),
              transform 0.12s var(--ease);
}
.corner-button:hover {
  color: var(--accent-text);
  background-color: var(--btn-face-hover);
  border-color: var(--accent-line);
  box-shadow: 0 5px 0 var(--btn-lip), var(--shadow-3);
  transform: translateY(-1px);
}
.corner-button:active {
  transform: translateY(3px);
  box-shadow: 0 1px 0 var(--btn-lip), var(--shadow-1);
}

/* The drawing itself lives in this span rather than straight inside the
   button, and that is load-bearing: two of the six swap what they show
   (the globe becomes "FR", the person becomes your initial) by writing
   into it, and before milestone 48 they wrote over the whole button —
   which would now take the label with it. app.js PART 3D and PART 14
   write to .corner-glyph, never to the button. */
.corner-glyph {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 21px;                /* the same height whether it holds a 21px
                                  icon or the two letters "FR", so the label
                                  underneath never moves */
  line-height: 1;
}

/* The word under the icon. Small, but a real word in a readable weight —
   and it inherits the button's colour, so it lifts on hover with the rest
   of the tile. */
.corner-label {
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: -0.005em;
  line-height: 1.1;
  white-space: nowrap;
}

/* Every corner icon is drawn on a 24x24 grid with a 1.7px stroke and no
   fill, so they all have the same visual weight whatever their shape.
   "currentColor" means an icon is simply the colour of the button holding
   it — which is how the ★ can be accent-coloured and the ← near-black
   without either of them needing its own icon. */
.corner-icon {
  width: 21px;
  height: 21px;
  display: block;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* An icon inside an ordinary button's label (the ⚙ on "Options"), rather
   than alone in a corner square. Same drawing rules, sized to the text and
   nudged onto its baseline. It exists because a ⚙ typed as a CHARACTER is
   drawn by the operating system — monochrome on one machine, a full-colour
   cartoon cog on the next — which is the same problem the corner buttons had. */
.btn-icon {
  width: 1.05em;
  height: 1.05em;
  margin-right: 6px;
  vertical-align: -0.16em;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.corner-panel {
  position: fixed;
  top: 72px;                       /* just under a 50px tile at top: 14px */
  z-index: 30;

  width: 272px;
  max-height: calc(100vh - 92px);  /* never taller than the screen... */
  overflow-y: auto;                /* ...scroll if it somehow is */

  background-color: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-3);
  padding: var(--sp-5);

  display: none;           /* JavaScript adds ".open" to show it */
}
.corner-panel.open {
  display: block;
  animation: panel-in 0.16s var(--ease);
}
/* The panel arrives from just under its button rather than appearing from
   nowhere, which is what makes it read as belonging to the button. */
@keyframes panel-in {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* --- THE PICKLIST: this app's drop-down ---
   **There are no <select>s in this app, and that is the point of this block.**
   A native select's DROP-DOWN LIST is drawn by the operating system, outside
   the page: `style.css` cannot reach it, `data-theme` cannot reach it, and
   §8's contrast sweep cannot measure it. On a Mac it arrives as a grey list
   with a yellow highlight in the middle of an app that is violet and cream —
   or violet and near-black, where it is worse, because the app is dark and
   the list is not.

   So a drop-down here is a button and a panel, and almost nothing about it is
   invented: the panel is `.corner-panel`'s look just above (surface, a visible
   edge, the deepest shadow, and the same `panel-in` animation) and the rows
   are the "list of rows you pick one of" family that the font rows, the theme
   rows, the voice rows and the language rows already share. That is why the
   whole thing needed **no new colours at all** and is right in both themes.

   All eight of them are built by ONE function, `makePicklist` in app.js
   PART 2, which also carries the keyboard a <select> would have given for
   free. Two shapes: the plain one sizes itself to its longest label and hangs
   its panel from its RIGHT edge (for a control at the right of a row), and
   `.picklist-wide` fills its row and hangs the panel down both edges (for a
   form field with its label above it). */

.picklist {
  position: relative;       /* the panel hangs off this, not off the window */
  min-width: 0;
}

/* The closed control: the current choice, and a caret. */
.picklist-button {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);

  min-width: 190px;         /* the widest label plus its caret, so choosing a
                               longer option doesn't shove the row sideways —
                               the same reason the ▶/⏸ pill has one */
  padding: 10px 12px 10px 14px;
  font-size: var(--fs-sm);
  font-weight: 500;
  text-align: left;
}

.picklist-value {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

.picklist-caret {
  flex: none;
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
  color: var(--ink-mute);
  transition: transform 0.16s var(--ease);
}
/* It points UP while the panel is open — the caret says what pressing it will
   do next, the same way the karaoke pill says Play or Pause. */
.picklist[data-open="true"] .picklist-caret { transform: rotate(180deg); }

/* While the panel is open the button stays visibly "pressed in", so the two
   read as one object rather than as a button with a card floating near it. */
.picklist[data-open="true"] .picklist-button {
  border-color: var(--accent-line);
  background-color: var(--btn-face-hover);
}

/* The open list. Same card as a corner panel, but anchored to the button
   instead of pinned to a corner of the window. */
.picklist-panel {
  position: absolute;
  /* 10px, not 6: the button carries a 4px LIP (the solid band that is the
     side of the button, §6), and that lip lives inside this gap — so 6px
     would leave two pixels of daylight between the lip and the panel, which
     reads as the panel resting on the button rather than hanging under it. */
  top: calc(100% + 10px);
  right: 0;                 /* it hangs from the button's RIGHT edge, because
                               a plain picklist sits at the right end of its
                               row and a left-anchored panel would reach off a
                               narrow screen */
  z-index: 40;

  min-width: 100%;
  width: max-content;
  max-width: min(320px, calc(100vw - 2 * var(--sp-4)));

  /* A list can be LONG — the backend's model list comes from the provider and
     is routinely two or three hundred rows — so it scrolls rather than running
     off the bottom of the window. The number here is only the CAP: how much
     room there actually is depends on where the button is on the screen, which
     is a measurement, so `makePicklist` narrows this on every open and flips
     the panel above the button when there is more room up there. */
  max-height: 360px;
  overflow-y: auto;
  overscroll-behavior: contain;   /* scrolling to the end of the list doesn't
                                     then start scrolling the page behind it */

  padding: 6px;
  background-color: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-3);

  display: none;            /* JavaScript sets data-open on the .picklist */
}
.picklist[data-open="true"] .picklist-panel {
  display: flex;
  flex-direction: column;
  gap: 4px;
  animation: panel-in 0.16s var(--ease);
}

/* Opened UPWARDS, because the button was too near the bottom of the window
   for the list to fit under it (makePicklist measures and decides). It also
   arrives from the other direction, so it still reads as coming out of its
   button rather than sliding past it. */
.picklist[data-drop="up"] .picklist-panel {
  top: auto;
  bottom: calc(100% + 10px);
}
.picklist[data-drop="up"][data-open="true"] .picklist-panel {
  animation: panel-in-up 0.16s var(--ease);
}
@keyframes panel-in-up {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ONE ROW. The same shape as a language row, and for the same reason: it is
   the same job. 44px tall, which is about the smallest a thumb hits reliably
   (§6) — a native select hands a phone the OS wheel picker and this does not,
   so the rows have to be comfortable themselves. */
.picklist-option {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;

  flex: none;               /* a flex column SHRINKS its children to fit, which
                               would squash 300 rows into an unreadable stack
                               instead of letting the panel scroll */
  min-height: 44px;
  padding: 10px 14px;
  font-size: var(--fs-sm);
  text-align: left;
  border-radius: var(--r-sm);
  box-shadow: 0 2px 0 var(--btn-lip), var(--shadow-1);
}
.picklist-option:hover {
  transform: none;          /* a list of rows shouldn't bounce under the mouse */
  box-shadow: 0 3px 0 var(--btn-lip), var(--shadow-2);
}
.picklist-option:active {
  transform: translateY(1px);
  box-shadow: 0 1px 0 var(--btn-lip), var(--shadow-1);
}

/* The one you're on, marked exactly the way every other chosen row in the app
   is: filled, with a lip in its own colour. */
.picklist-option[aria-selected="true"] {
  color: var(--on-accent);
  background-color: var(--accent);
  border-color: var(--accent);
  font-weight: 600;
  --btn-lip: var(--accent-lip);
}
.picklist-option[aria-selected="true"]:hover {
  color: var(--on-accent);
  background-color: var(--accent-hover);
  border-color: var(--accent-hover);
}

/* The label. Most are short and ours; this is the guard for the ones that
   aren't — a model id from a provider can be 60 characters. */
.picklist-option-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* A tick on the chosen row, so the choice is not carried by colour alone
   (§6: never colour alone). It keeps its space when empty, so the labels line
   up down the list instead of shuffling by 18px on the selected one. */
.picklist-tick {
  flex: none;
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.2;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0;
}
.picklist-option[aria-selected="true"] .picklist-tick { opacity: 1; }

/* THE WIDE SHAPE — a form field rather than a control at the end of a row.
   It fills its row and its panel hangs down both edges, so the open list is
   exactly as wide as the thing you opened. SECTION 14's ".options-row" rules
   stack the label above it, the same way they do for a wide text box. */
.picklist-wide,
.picklist-wide .picklist-button { width: 100%; }
.picklist-wide .picklist-button { min-width: 0; }
.picklist-wide .picklist-panel {
  left: 0;
  right: 0;
  width: auto;
  max-width: none;
}


/* --- The small heading that titles a panel, a section or a block ---
   Seven places wear this same look: the ☰ menu's two section titles, the 🌐
   and 👤 panel headings, every section title on the options and account
   pages, a chapter card's "Chapter II", and the two little labels on the back
   of a flashcard. Saying it once here means they can't drift apart; each one
   below then declares only what is genuinely ITS OWN — its size, its margins,
   and whether it sits on its own line.

   Until milestone 45 every one of these was TINY GREY CAPITALS WITH WIDE
   TRACKING. That is the house style of a settings panel, a CI dashboard, a
   repository page — and seven of them stacked down a screen is the single
   loudest reason this app read as a developer tool rather than as somewhere
   to read a book. They are now sentence case, a size you can actually read,
   and dark enough to be a heading rather than a caption. Nothing else about
   the layout changed; this one rule did most of the work.

   Deliberately NOT set here: font-family. Two of these sit inside text that
   follows the reader's own translation font, and pinning them to the
   interface font would quietly ignore that setting. */
.menu-section-title,
.account-heading,
.language-heading,
.options-title,
.chapter-number,
.card-context-label,
.card-sentence-label {
  font-weight: 600;
  letter-spacing: -0.005em;
  color: var(--ink-strong);
}


/* ---- THE CORNER TILES ON A PHONE ----
   Four labelled tiles across the top of the front page need about 348px at
   the desktop size, which a 320px screen has not got. Rather than take the
   words away again — which is the one thing this arrangement exists for —
   the tiles get narrower, closer to their corners and closer together, and
   the word shrinks a step. That is 288px at 320, which fits with room to
   spare. Nothing else has to know: every pinning rule is written in terms of
   the three numbers changed here. */
@media (max-width: 430px) {
  :root {
    --corner-w: 64px;
    --corner-gap: 6px;
    --corner-edge: 10px;
  }
  .corner-button {
    padding-left: 3px;
    padding-right: 3px;    /* the word gets the room the padding was using;
                              "Language" is the widest of the six, and this
                              leaves it space even in the fallback font a
                              machine with no internet would draw it in */
  }
  .corner-label {
    font-size: 0.63rem;
  }
}


/* ------------------------------------------------------------
   SECTION 3 — THE READING COLUMN
   ------------------------------------------------------------
   640px is not a round number picked for looks. Comfortable reading happens
   at roughly 45–75 characters a line; at the default 1.5rem serif this column
   holds about 50, which is why the text feels easy even at length. Widen it
   and the eye starts losing its place coming back to the left margin.

   THE READER IS TWO WIDTHS, NOT ONE. `.reader` is the OUTER box, and it is
   wide (1040px of room) purely so the translation box can be that wide — a
   whole English sentence at the reading measure was six or eight lines of a
   two-line box. Everything you actually READ — the title, the story, the row
   at the end of it — sets its own `max-width: 640px` and centres itself inside that
   outer box, so the reading measure above is untouched. If you add another
   element to the reader, it will come out 1040px wide unless you narrow it
   the same way.
   ------------------------------------------------------------ */

.reader {
  max-width: 1088px;       /* = the 1040px translation box + the 24px gutters
                              below, since box-sizing is border-box */
  margin: 0 auto;          /* ...and centered on the page */
  padding: 72px 24px 40px; /* extra top room so the title clears the corners */
}

/* The story itself keeps the reading measure (see the note above). */
#text {
  max-width: 640px;
  margin: 0 auto;
}

#title {
  font-family: var(--display-font);   /* the story's NAME, not its text — so it
                                         keeps the app's display serif whatever
                                         font you choose to read in */
  font-size: clamp(1.9rem, 1.3rem + 2.4vw, 2.6rem);
  font-weight: 600;
  text-align: center;
  letter-spacing: -0.015em;   /* big serif type wants to be drawn together */
  line-height: 1.15;
  color: var(--ink-strong);
  max-width: 640px;        /* the reading measure, not the wider outer box */
  margin: 0 auto 26px;
}

/* A short accent rule under the title. It is doing a real job: it separates
   the title from the sticky translation box that follows, which is otherwise
   two stacked boxes with nothing between them. */
#title::after {
  content: "";
  display: block;
  width: 46px;
  height: 2px;
  margin: 16px auto -8px;
  border-radius: 2px;
  background-color: var(--accent);
  opacity: 0.55;
}

/* A chapter title is often a whole sentence ("Dans lequel Phileas Fogg et
   Passepartout s'acceptent réciproquement..."). At the full size it would wrap
   to three lines and push the text down on EVERY page, so app.js sets this
   class on the long ones. */
#title.title-long {
  font-family: var(--ui-font);   /* a whole sentence, so it reads better as
                                    plain interface text than as display type */
  font-size: var(--fs-lg);
  font-weight: 500;
  line-height: 1.45;
  letter-spacing: 0;
  color: var(--ink-soft);
}
#title.title-long::after {
  margin-top: 14px;
}


/* ------------------------------------------------------------
   SECTION 4 — THE TRANSLATION BOX (above the text)
   ------------------------------------------------------------
   TWO NUMBERS HERE ARE LOAD-BEARING AND MUST NOT DRIFT: the 14px vertical
   padding and the 1.4em-per-line height. At the two-line floor those make the
   box exactly 77.04px tall at 100%, which is the figure the jiggle check in
   CLAUDE.md §8 measures. Change either and that test's recorded number changes
   with it.

   WHAT MUST NOT CHANGE IS THE HEIGHT *WHILE YOU READ*, not the height itself.
   The box may now be more than two lines tall — how many is measured per text
   and written to --trans-lines (SECTION 1) — but it is decided once, when the
   text is opened, and then held. That is the whole reason it is fixed at all:
   a box that grew when you hovered a long sentence would push the story down,
   move the sentence out from under your cursor, and flicker. That is not
   theoretical — sweeping every pointer position down Verne's chapter I with a
   free-growing box finds three that settle into a loop.
   ------------------------------------------------------------ */

.translation-box {
  position: sticky;             /* stays visible at the top as you scroll */
  top: 12px;

  display: flex;                /* the play button (left) sits beside the text */
  align-items: center;
  gap: var(--sp-3);

  background-color: var(--accent-wash);
  border: 1px solid var(--accent-line);
  border-radius: var(--r-lg);
  padding: 14px 52px;           /* equal side room clears BOTH corner buttons
                                   (vocab on the left, settings on the right)
                                   when this box is stuck to the top on scroll */
  margin-bottom: var(--sp-6);
  box-shadow: var(--shadow-1);

  font-family: var(--trans-font);
  font-size: var(--trans-size);
  font-style: italic;
  color: var(--accent-text);    /* muted while it's just a hint */

  /* FIXED HEIGHT = --trans-lines lines tall (two, unless app.js measured this
     text and found it needs more), so filling it never pushes the story text
     down — which would move the sentence out from under your cursor and cause
     a jiggle. "em" is relative to THIS box's font, so the height adapts to
     the translation size. A translation longer than the room reserved still
     scrolls: the TEXT scrolls, not the box.

     THIS IS THE ONE ELEMENT IN THE APP THAT OPTS OUT OF `border-box`, and it
     is deliberate. SECTION 2 puts everything on border-box, under which the
     height below would be the whole box — padding and border included —
     squeezing the lines of text down by one. That is exactly what
     happened when the border-box rule arrived in milestone 44: the box
     measured 47.03px instead of 77.04px and CLAUDE.md §8's jiggle check
     caught it. Writing the padding into a calc() gets the height back, but
     not to the same sub-pixel value — and this is a recorded number other
     sessions compare against, so "near enough" is not what we want. Opting
     out is exact: the height means what it has always meant, which is that
     the TEXT area is that many lines tall and the 28px of padding and 2px of
     border sit outside it. Measured against the previous stylesheet in the same
     browser, both now report the identical 77.03 / 58.22 / 124.08. */
  box-sizing: content-box;
  line-height: 1.4;
  height: calc(var(--trans-lines) * 1.4em);   /* at 2 lines this IS 2.8em */
  overflow: hidden;
}

/* WHERE THE 52px ABOVE STOPS BEING ENOUGH. The corner tiles are pinned to
   the window, the box is pinned to the reading column, and the box slides
   under them as you scroll — so the text inside it has to start further in
   than the tiles reach. 52px covers a phone (a narrow tile 10px off the
   corner reaches 74px, and the box itself starts at 24). It does NOT cover
   the middle widths, where the tile is 78px wide and reaches 92px while the
   box still starts at 24; and it stops mattering again above 1224px, where
   the column is centred and the box begins past the tiles on its own.
   Only the SIDE padding changes here: the 14px top and bottom are what make
   the box 77.04px tall, and that is a recorded number (see above). */
@media (min-width: 431px) and (max-width: 1223px) {
  .translation-box {
    padding-left: 72px;
    padding-right: 72px;
  }
}

/* The words inside the box (kept separate from the play button so we can set
   the text without wiping out the button). */
.translation-text {
  flex: 1;                      /* take the remaining width beside the button */
  min-width: 0;                 /* allow it to shrink so it can scroll */
  max-height: calc(var(--trans-lines) * 1.4em);   /* the same lines as the box */
  overflow-y: auto;             /* a longer translation scrolls inside */
}

/* The karaoke play/pause button, at the left of the box. A pill rather than
   a bare round ▶ since milestone 48: it now says "Play" (and "Pause" while
   it is playing), because a triangle in a circle is only obvious to someone
   who already knows what this app does with audio. Sized in "em" throughout,
   so it still scales with the translation text.

   The min-width is doing a real job: "Pause" is wider than "Play", and
   without it the whole translation beside it would shuffle sideways every
   time you started or stopped the recording. */
.play-button {
  flex: none;                   /* fixed size; never stretch or shrink */
  min-width: 6.1em;
  height: 2.3em;
  padding: 0 0.85em;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.4em;
  font-family: var(--ui-font);
  font-size: 1em;
  font-style: normal;
  line-height: 1;
  color: var(--on-accent);
  background-color: var(--accent);
  border: 1px solid var(--accent);
  border-radius: var(--r-pill);
  box-shadow: 0 3px 0 var(--accent-lip), var(--shadow-2);
  cursor: pointer;
  transition: background-color 0.16s var(--ease),
              box-shadow 0.16s var(--ease),
              transform 0.12s var(--ease);
}
.play-button:hover  {
  background-color: var(--accent-hover);
  border-color: var(--accent-hover);
  box-shadow: 0 4px 0 var(--accent-lip), var(--shadow-3);
}
.play-button:active {
  transform: translateY(2px);
  box-shadow: 0 1px 0 var(--accent-lip), var(--shadow-1);
}

/* The ▶ / ⏸ itself, and the word beside it. The glyph is held at a fixed
   width for the same reason the button has a minimum: ▶ and ⏸ are not the
   same width, and the label should not shuffle. */
.play-glyph {
  display: inline-block;
  width: 0.8em;
  text-align: center;
  font-size: 0.82em;
}
.play-label {
  font-size: 0.78em;
  font-weight: 600;
  letter-spacing: 0.01em;
}

/* When a real translation is loaded in, make it look solid and clear. */
.translation-box.has-text {
  color: var(--ink);
  font-style: normal;
}


/* ------------------------------------------------------------
   SECTION 5 — THE STORY TEXT AND WORDS
   ------------------------------------------------------------
   A paragraph is a block of prose; sentences inside it are inline
   <span>s (so text flows and wraps normally); words are inline
   <span>s you can hover.
   ------------------------------------------------------------ */

.paragraph {
  font-size: var(--text-size);
  color: var(--ink);

  /* ADAPTIVE LINE SPACING (the reliability trick):
     each line must be tall enough to hold the story text AND leave a gap above
     it for the word popup that floats there. Two things drive the popup's
     height: (1) the translation text inside it (grows with --trans-size), and
     (2) the ＋ Save button, which is a FIXED height and doesn't shrink when the
     translation is small. So the clearance is:
        text part   -> var(--text-size) * (the font's own line box + 0.13)
        popup part  -> max( a fixed floor for the button ,
                            the translation-scaled height )
     Using max() means small translation sizes still reserve room for the
     button, and large ones reserve room for the bigger text — no overlap
     either way. (Re-tuned by measurement whenever anything about the popup
     changes — see CLAUDE.md §8's overlap check. DO NOT change these numbers,
     or the tooltip's padding in SECTION 6, without re-running it.)

     THE FIRST TERM IS ABOUT THE FONT, NOT ABOUT TASTE. "The line's own height"
     is the tallest line box the chosen typeface will draw, as a multiple of
     its font-size — a property OF THE TYPEFACE, not something we get to pick.
     Until milestone 49 it was ONE constant (1.45) covering all five choices,
     which meant it had to clear the tallest (Book, at 1.37) and therefore gave
     the compact ones air they did not need — and it was still not enough for
     Book at large translation sizes, where the popup grazed the line above by
     up to 2.86px.

     So the number is no longer guessed: **app.js measures it** (PART 6's
     `measureLineBox`, at every font change and again once the web fonts have
     actually arrived) and writes the answer here as --text-lead. Measured:
     Classic 1.11, Mono 1.17, Clean 1.21, Clear 1.22, Book 1.37. The +0.13 on
     top is the clearance itself — the room the popup needs beyond the line's
     own ink — and it is the same for every font because it is about the popup,
     not about the typeface. Measuring beats a table of five numbers because
     the app can fall back to a system font (offline, §2), and a table would
     then be describing a typeface that isn't on the screen.

     1.37 is the DEFAULT below purely so that a moment before JavaScript has
     measured anything, the reserve is the largest any of the five needs. */
  line-height: calc(var(--text-size) * (var(--text-lead) + 0.13)
                    + max(35px, var(--trans-size) * 1.6 + 8px));

  margin: 0 0 1.5em 0;          /* space BETWEEN paragraphs (scales with text) */
}

.word {
  cursor: pointer;                 /* the pointer hints "hoverable" */
  border-radius: var(--r-xs);
  padding: 0 1px;
  transition: background-color 0.14s var(--ease),
              color 0.14s var(--ease);
  white-space: nowrap;             /* keep each word whole: never let a
                                      hyphenated word like "week-end."
                                      split across two lines */
}

/* Gently highlight the word the mouse is over (also marks the word
   whose meaning is currently showing in the popup). */
.word:hover {
  background-color: var(--mark);   /* highlighter — warm on paper, a dim
                                      warm lift in the dark theme */
}

/* THE PIECE THE POPUP IS DESCRIBING (milestone 74): one word, or a whole
   expression like "il y a" — every word of it underlined, AND the spaces
   between them (.word-gap), so it reads as one line under one thing rather
   than three short lines under three. Underlined in the accent rather than
   only highlighted, because the highlighter above marks just the word under
   the pointer, and what matters here is everything the meaning belongs to.
   An underline is a shape, not only a colour (§6: never colour alone). */
.word.piece-lit,
.word-gap.piece-lit {
  text-decoration-line: underline;
  text-decoration-color: var(--accent);
  text-decoration-thickness: 2px;
  text-underline-offset: 0.2em;
  text-decoration-skip-ink: none;  /* one unbroken line, not one that hops
                                      round every descender */
}

/* KARAOKE — the word being spoken RIGHT NOW: blue, with a small dot centered
   just beneath it. Deliberately its OWN colour rather than the interface
   accent: this is the story talking, not the app. */
.word.speaking {
  color: var(--speak);
  font-weight: 600;                /* colour alone is not enough — some
                                      readers can't tell these two blues
                                      apart, and weight always shows */
  position: relative;              /* so the dot can be placed under the word */
}
.word.speaking::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -0.18em;                 /* just below the letters */
  transform: translateX(-50%);
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background-color: var(--speak-dot);
}

/* KARAOKE — the sentence being spoken now: an extremely light wash. */
.sentence.speaking-sentence {
  background-color: var(--speak-wash);
  border-radius: var(--r-xs);
}


/* ------------------------------------------------------------
   SECTION 6 — THE WORD TOOLTIP (a small card: meaning + ＋ save)
   ------------------------------------------------------------
   It shows translation text, so it uses the translation font + size.
   Because it holds a tappable ＋ button, it's a little card (the plain-text
   look couldn't hold a button you can reliably hit on a phone).

   ITS HEIGHT IS LOAD-BEARING. The padding below and the 22px ＋ button are
   what SECTION 5's line-height formula reserves room for. Growing either
   without re-running the overlap check in CLAUDE.md §8 will put popups on
   top of the words above them.
   ------------------------------------------------------------ */

.tooltip {
  position: absolute;      /* we place it with exact coordinates */
  display: none;           /* hidden until JavaScript shows it */
  align-items: center;     /* meaning text and ＋ button share one line */
  gap: 6px;

  font-family: var(--trans-font);
  font-size: var(--trans-size);
  white-space: nowrap;     /* keep everything on one line */
  z-index: 10;             /* sit above the text */

  /* a soft little card, so the ＋ reads as a button and is easy to tap */
  background-color: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: 2px 4px 4px 9px;   /* the extra 2px at the bottom is room for the
                                 save button's lip (SECTION 6, below) */
  box-shadow: var(--shadow-2);
}

.tooltip.visible {
  display: inline-flex;    /* show the meaning + button as one neat row */
}

/* The English meaning inside the popup. */
.tooltip-word {
  font-weight: 600;
  color: var(--ink);
}

/* The button that saves the word. It was a bare ＋ until milestone 48, which
   is a symbol you have to guess at inside a popup that is already showing
   you a meaning — it now reads "＋ Save". It stays 22px TALL, which matters:
   SECTION 5's line-height formula reserves room above each line for this
   popup, and that reserve is built on the button's height, not its width. */
.tooltip-save {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 3px;
  height: 22px;
  padding: 0 7px;
  font-family: var(--ui-font);
  font-size: 0.72rem;
  font-weight: 700;
  font-style: normal;
  line-height: 1;
  color: var(--on-accent);
  background-color: var(--accent);
  border: none;
  border-radius: var(--r-xs);
  /* A 2px lip, like every other button — the popup's bottom padding was
     opened from 2px to 4px to make room for it, which is the ONE thing in
     here that changes the popup's height and therefore SECTION 5's
     clearance. Both were re-measured together. */
  box-shadow: 0 2px 0 var(--accent-lip);
  cursor: pointer;
  transition: background-color 0.16s var(--ease),
              box-shadow 0.16s var(--ease),
              transform 0.12s var(--ease);
}
.tooltip-save:active {
  transform: translateY(1px);
  box-shadow: 0 1px 0 var(--accent-lip);
}
/* The ＋ sign, a little larger than the word beside it. */
.tooltip-save-plus {
  font-size: 0.95rem;
  line-height: 1;
}
.tooltip-save:hover  { background-color: var(--accent-hover); }
.tooltip-save:active { transform: scale(0.9); }


/* ------------------------------------------------------------
   SECTION 7 — THE SETTINGS MENU
   ------------------------------------------------------------ */

/* Keep the menu's own text in the stable interface font, no matter which
   reading fonts are chosen for the story/translation. */
.menu,
.menu button,
.menu input {
  font-family: var(--ui-font);
}

/* The burger button, pinned to the top-right corner.
   (.corner-button in SECTION 2 supplies everything else.) */
.menu-button {
  right: var(--corner-edge);
  color: var(--ink-strong);
}

/* The panel that drops down under it. */
.menu-panel {
  right: var(--corner-edge);
}

.menu-heading {
  margin: 0 0 var(--sp-1) 0;
  font-size: var(--fs-lg);
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--ink-strong);
}

/* Each settings section (Text / Translation). A thin line separates them. */
.menu-section {
  padding-top: var(--sp-4);
  margin-top: var(--sp-3);
  border-top: 1px solid var(--line-soft);
}
.menu-section-title {          /* the heading look comes from SECTION 2 */
  margin: 0 0 var(--sp-3) 0;
  font-size: var(--fs-sm);
}

.menu-group {
  margin-bottom: var(--sp-3);
}
.menu-group:last-child {
  margin-bottom: 0;
}

.menu-label {
  display: flex;
  justify-content: space-between;  /* pushes the % value to the right */
  align-items: baseline;
  margin: 0 0 var(--sp-2) 0;
  font-size: var(--fs-sm);
  color: var(--ink-soft);
}
/* The live "100%" badge next to a size label. */
.menu-value {
  font-size: var(--fs-xs);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--accent-text);
}

/* The list of font choices. It used to be five buttons wrapping across two
   rows with nothing on them but a typeface name — which quietly assumed you
   already knew what "Palatino" looks like. It is now one choice per ROW,
   with the name set IN that font on the left and the reason you would pick
   it on the right, so the list can be read rather than guessed at. */
.font-buttons {
  display: grid;
  grid-template-columns: 1fr;   /* one choice per row */
  gap: 5px;
}
/* A .btn shaped into a list row. Until milestone 48 it was also FLATTENED
   into one — pressed into the panel with --surface-sunk, no rim and no
   shadow — which read as five inert labels rather than five things you can
   press. It now keeps the standard face, rim and shadow like every other
   button; only the shape (a row, ranged left) is its own. The theme choice
   below borrows the same look, so it is named here rather than having a
   second copy of it.

   The one thing it does NOT take from .btn is the hover lift: a stack of
   rows that bobs about under the mouse is fidgety, so the shadow deepens
   and the row stays put. */
.font-button,
.theme-button {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 9px 13px;
  text-align: left;
  color: var(--ink-strong);
  background-color: var(--btn-face);
  border-color: var(--btn-edge);
  border-radius: var(--r-sm);
  box-shadow: 0 2px 0 var(--btn-lip), var(--shadow-1);
}
.font-button:hover,
.theme-button:hover {
  background-color: var(--btn-face-hover);
  border-color: var(--accent-line);
  box-shadow: 0 3px 0 var(--btn-lip), var(--shadow-2);
  transform: none;          /* a list of rows shouldn't bob about */
}
/* A row doesn't rise under the mouse, but it does go DOWN when you press it —
   the one bit of movement a list should have. */
.font-button:active,
.theme-button:active {
  transform: translateY(1px);
  box-shadow: 0 1px 0 var(--btn-lip), var(--shadow-1);
}
/* The currently selected font (or theme) in a group. It keeps its lip — the
   chosen row is still a raised thing, not a stamp pressed into the panel —
   and swaps it for the accent one, like every other filled button. */
.font-button.active,
.theme-button.active {
  background-color: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent);
  --btn-lip: var(--accent-lip);
  box-shadow: 0 2px 0 var(--btn-lip), var(--shadow-2);
}
.font-button.active:hover,
.theme-button.active:hover {
  background-color: var(--accent-hover);
  color: var(--on-accent);
}

/* The typeface's name — set in the typeface itself, which is the only honest
   preview there is. */
.font-button-name {
  font-size: var(--fs-sm);
  font-weight: 600;
  line-height: 1.3;
}

/* Why you would choose it. Always the interface font, so the five reasons
   line up as a column you can compare instead of five different shapes. */
.font-button-why {
  flex: none;
  font-family: var(--ui-font);
  font-size: 0.7rem;
  font-weight: 500;
  line-height: 1.3;
  color: var(--ink-mute);
}
.font-button.active .font-button-why {
  color: var(--on-accent);
  opacity: 0.8;             /* still quieter than the name, still readable */
}

/* Preview: each choice's NAME is set in the font it selects. These repeat the
   stacks in app.js's FONTS map — the one duplication in the file, and it is
   deliberate: CSS cannot read a JavaScript object, and a preview drawn in the
   wrong font would be worse than no preview at all. */
.font-button[data-font="clean"]   .font-button-name { font-family: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif; }
.font-button[data-font="book"]    .font-button-name { font-family: "Source Serif 4", Georgia, "Iowan Old Style", "Times New Roman", serif; }
.font-button[data-font="clear"]   .font-button-name { font-family: "Atkinson Hyperlegible", Verdana, Geneva, "DejaVu Sans", sans-serif; }
.font-button[data-font="classic"] .font-button-name { font-family: "Palatino Linotype", "Book Antiqua", Palatino, "Iowan Old Style", Georgia, serif; }
.font-button[data-font="mono"]    .font-button-name { font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, "Courier New", monospace; }

/* ---- The theme choice: Light / Dark / Auto ----
   Three short options rather than five long ones, so they sit side by side
   instead of stacking, each with a small drawing of what it means above its
   name. Everything about the way they LOOK is shared with .font-button
   above; only the shape is different, which is why there are so few lines
   here. */
.theme-buttons {
  display: grid;
  grid-template-columns: repeat(3, 1fr);   /* three across, equal widths */
  gap: 5px;
}
.theme-button {
  flex-direction: column;    /* icon on top, name underneath */
  align-items: center;
  justify-content: center;
  gap: 5px;
  padding: 9px 4px;
  text-align: center;
  font-size: var(--fs-sm);
  font-weight: 600;
}
/* The icon sits ABOVE the name here rather than beside it, so the spacing
   .btn-icon builds in for a label to its right has to come back off. */
.theme-button .btn-icon {
  width: 20px;
  height: 20px;
  margin-right: 0;
  vertical-align: 0;
}

/* The size sliders. accent-color themes the track + handle in one line, and
   follows the theme because it is a variable. */
/* 26px tall, not the browser's default ~16: a slider you have to hit exactly
   is the most annoying control there is, and the extra height costs nothing
   because the track is drawn in the middle of whatever room it is given. */
.size-slider {
  width: 100%;
  height: 26px;
  cursor: pointer;
  accent-color: var(--accent);
}


/* ------------------------------------------------------------
   SECTION 8 — THE SAVED-VOCABULARY PANEL (top-left)
   ------------------------------------------------------------
   Mirrors the settings menu, but on the LEFT. The button opens a panel that
   lists the words you've saved. The list itself lives in memory (see app.js).
   ------------------------------------------------------------ */

/* Keep the vocab UI in the stable interface font, like the settings menu. */
.vocab,
.vocab button {
  font-family: var(--ui-font);
}

/* The button, pinned top-left (a mirror of the settings burger top-right).
   Shares the ".corner-button" base look (SECTION 2) — only the side and
   colour differ here. */
.vocab-button {
  left: var(--corner-edge);
  color: var(--accent-text);        /* a soft colour, so the book reads as a mark */
}
/* The little dictionary (a rounded book with an A on its cover) is drawn in
   outline like every other corner icon — it used to be a FILLED ★, which needed
   its own fill/stroke override here. It doesn't any more: the shared
   .corner-icon rules (SECTION 2) are the whole of it, one less exception. */

/* A quick "pop" when a word is saved, so the save feels acknowledged. */
.vocab-button.pop { animation: vocab-pop 0.35s var(--ease); }
@keyframes vocab-pop {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.18); }
  100% { transform: scale(1); }
}

/* The panel that drops down under the button (shares ".corner-panel", SECTION 2). */
.vocab-panel {
  left: var(--corner-edge);
}

.vocab-heading {
  margin: 0 0 var(--sp-3) 0;
  font-size: var(--fs-lg);
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--ink-strong);
}

/* The list of saved words. */
.vocab-list {
  list-style: none;      /* no bullet points */
  margin: 0;
  padding: 0;
}
.vocab-item {
  display: flex;
  justify-content: space-between;  /* French on the left, English on the right */
  gap: var(--sp-3);
  padding: var(--sp-2) 0;
  border-bottom: 1px solid var(--line-soft);
  font-size: var(--fs-sm);
}
.vocab-item:last-child { border-bottom: none; }
.vocab-fr { font-weight: 700; color: var(--ink); }
.vocab-en { color: var(--ink-soft); font-style: italic; text-align: right; }

/* Shown when nothing has been saved yet. */
.vocab-empty {
  color: var(--ink-mute);
  font-size: var(--fs-sm);
  font-style: italic;
}


/* ------------------------------------------------------------
   SECTION 9 — THE STORY PICKER (the screen you land on)
   ------------------------------------------------------------
   A small "home" screen: a heading and a shelf of story cards. The same
   centered column is reused by every other non-reading screen (flashcards,
   study, options, both account screens, Add content, Analysis), which is why
   ".picker" turns up in so many places — it is the app's page layout, not
   just this page's.
   ------------------------------------------------------------ */

.picker {
  max-width: 640px;
  margin: 0 auto;
  padding: 96px 24px 72px;   /* the corner buttons live in the first 60px, so
                                a page that starts at 72 feels crowded by them */
  text-align: center;
}

/* The one piece of display type in the app. Big, tightly set, and in the
   READING font rather than the interface font — the front page should look
   like the cover of the book it is about to open, not like a settings screen. */
.picker-title {
  font-family: var(--display-font);
  font-size: var(--fs-display);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.05;
  color: var(--ink-strong);
  margin: 0 0 var(--sp-3) 0;
}

.picker-hint {
  color: var(--ink-soft);
  font-family: var(--ui-font);
  font-size: var(--fs-lg);
  font-weight: 400;
  line-height: 1.5;
  max-width: 30rem;
  margin: 0 auto;
  text-wrap: balance;      /* two even lines rather than a long one and a runt */
}

/* The shelf. "auto-fill" with a minimum rather than a fixed two columns: on a
   phone that is one card, on this column it is three, and it stays right at
   every width in between without a single media query. */
.story-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(224px, 1fr));
  gap: var(--sp-5);
}

/* ONE STORY CARD.
   It is still a .btn underneath, but grown into a real tile: the padding is
   removed so the cover can run edge to edge, and the text below it is ranged
   LEFT. Centred titles look tidy on two cards and become a wobbling ladder on
   six — a left edge gives the eye one line to come back to. */
.story-card {
  position: relative;           /* the "Continue" pill is pinned to the cover */
  display: flex;
  flex-direction: column;
  padding: 0 0 var(--sp-4) 0;
  overflow: hidden;             /* so the cover's corners follow the card's */
  text-align: left;
  border-color: var(--btn-edge);  /* a rim as well as the shadow: in the dark
                                     theme the shadow has nothing to land on,
                                     and a tile with no edge dissolves into
                                     the page */
  border-radius: var(--r-lg);
  box-shadow: 0 4px 0 var(--btn-lip), var(--shadow-2);   /* a bigger tile wants
                                                            a deeper lip */
}
.story-card:hover {
  border-color: var(--accent-line);
  box-shadow: 0 6px 0 var(--btn-lip), var(--shadow-3);
  transform: translateY(-4px);   /* a card lifts further than a button does */
}
.story-card:active {
  transform: translateY(-1px);
  box-shadow: 0 2px 0 var(--btn-lip), var(--shadow-2);
}


/* ------------------------------------------------------------
   SECTION 9A — THE LIBRARY (the front page's own, WIDER layout)
   ------------------------------------------------------------
   ".picker" above is a 640px column, and 640px is the READING measure — the
   width at which a line of prose is comfortable (SECTION 3). A shelf of
   stories is not prose: you scan it, you compare covers, you look for the
   one at your level. So the front page — and only the front page — opts into
   a landscape layout with ".library", and every other screen that uses
   ".picker" keeps the narrow column, because a 1240px-wide settings form
   would be worse rather than better.

   NARROW SCREENS GET EXACTLY WHAT THEY GOT BEFORE. Everything two-column
   here lives inside the min-width media query at the end of this section, so
   a phone runs the plain single column that has always been there and the
   wide layout is purely additive.
   ------------------------------------------------------------ */

.library {
  max-width: 1240px;
  text-align: left;      /* a browse page is ranged left; a centred title over
                            an asymmetric two-column body reads as a mistake */
}

.library-header {
  margin-bottom: var(--sp-6);
}

/* .picker-hint centres itself with "margin: 0 auto" for the narrow screens;
   here it starts at the left edge under the title, like everything else. */
.library .picker-hint {
  margin-left: 0;
  margin-right: 0;
}

/* The shelf and the rail. One column by default — see the note above. */
.library-body {
  display: grid;
  grid-template-columns: minmax(0, 1fr);   /* minmax(0,…), NOT 1fr: a grid
                                              item's default min-width is
                                              "as wide as my contents", which
                                              is how a single nowrap line
                                              once made a whole column 481px
                                              wide on a 375px phone */
  gap: var(--sp-6);
}

.library-shelf { min-width: 0; }
.library-rail  { min-width: 0; width: 100%; max-width: 384px; margin: 0 auto; }


/* ---- Narrowing the shelf down: the level buttons and the sort box ---- */

.shelf-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3) var(--sp-5);
  padding-bottom: var(--sp-4);
  border-bottom: 1px solid var(--line-soft);
}

.shelf-filter,
.shelf-sort {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-width: 0;
}

/* The sort box goes to the far right of the row when there is room, and falls
   in underneath the level buttons when there isn't. */
.shelf-sort { margin-left: auto; }

.shelf-toolbar-label {
  font-family: var(--ui-font);
  font-size: var(--fs-sm);
  font-weight: 500;
  color: var(--ink-soft);
  white-space: nowrap;
}

.shelf-levels {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

/* ONE LEVEL BUTTON. A .btn underneath, shrunk to a pill — the same
   "shared class plus a small one for the differences" arrangement as
   .chapter-card. */
.level-chip {
  min-width: 42px;
  padding: 7px 14px;
  font-size: var(--fs-sm);
  font-weight: 600;
  border-radius: var(--r-pill);
  justify-content: center;
}
/* The one you are on. Filled, with a lip in its own colour — the same one
   line every other "this is the chosen row" state in the app is (SECTION 7's
   active font row, SECTION 17's active language). */
.level-chip.active {
  color: var(--on-accent);
  background-color: var(--accent);
  border-color: var(--accent);
  --btn-lip: var(--accent-lip);
}
.level-chip.active:hover {
  background-color: var(--accent-hover);
  border-color: var(--accent-hover);
}

/* "3 stories" — what the two controls above left you. */
.shelf-count {
  margin: var(--sp-4) 0 var(--sp-4) 0;
  font-family: var(--ui-font);
  font-size: var(--fs-sm);
  color: var(--ink-mute);
  font-variant-numeric: tabular-nums;
}


/* ---- Landscape: the shelf beside the rail ----
   1000px is where a 320px rail plus a shelf wide enough for two cards fits
   without either being cramped. Below it the rail simply drops underneath,
   which is the single column above. */
@media (min-width: 1000px) {

  .library-body {
    grid-template-columns: minmax(0, 1fr) 320px;
    gap: var(--sp-6);
    align-items: start;          /* so the rail can be sticky, below */
  }

  .library-rail {
    max-width: none;
    margin: 0;
    position: sticky;
    top: 96px;                   /* clears the corner tiles, which are fixed
                                    and end at 64px down the window */
  }

  /* Two wide buttons stacked, rather than side by side: "Flashcards" and
     "Create a story" together are a few pixels wider than the rail, so
     side-by-side would wrap into a ragged pair. */
  .library-rail .picker-actions {
    flex-direction: column;
    margin-top: 0;
  }

  .library-rail .progress-card {
    max-width: none;
    margin: var(--sp-5) 0 0 0;   /* the card centres itself on a phone; in the
                                    rail it just fills the column */
  }
}


/* ------------------------------------------------------------
   SECTION 9B — THE CHAPTER LIST (a story made of chapters)
   ------------------------------------------------------------
   Reached by picking a book on the front page. One button per chapter,
   stacked one per row (unlike the story grid's columns) because a chapter
   title is a whole sentence, not two words.
   ------------------------------------------------------------ */

.chapter-list {
  display: grid;
  grid-template-columns: 1fr;   /* one chapter per row */
  gap: var(--sp-3);
  margin-top: var(--sp-6);
}

/* One chapter button — a .btn with its text ranged left, since the titles
   are long enough to wrap. The accent bar down the left edge appears on
   hover: it points at the row you are about to open, which a colour change
   alone does not do as clearly in a stack of near-identical rows. */
.chapter-card {
  position: relative;
  display: block;
  padding: var(--sp-4) var(--sp-4) var(--sp-4) 22px;
  text-align: left;
  border-radius: var(--r-lg);
  box-shadow: 0 3px 0 var(--btn-lip), var(--shadow-1);
}
.chapter-card::before {
  content: "";
  position: absolute;
  left: 0;
  top: 10px;
  bottom: 10px;
  width: 3px;
  border-radius: 0 3px 3px 0;
  background-color: var(--accent);
  opacity: 0;
  transition: opacity 0.16s var(--ease);
}
.chapter-card:hover {
  box-shadow: 0 4px 0 var(--btn-lip), var(--shadow-2);
}
.chapter-card:hover::before {
  opacity: 1;
}

/* "Chapter I" — a small label above the title. */
.chapter-number {
  display: block;
  font-family: var(--ui-font);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--accent-text);   /* the one place this look is accent-coloured:
                                  it is a NUMBER in a list of numbers, and the
                                  colour is what lets you count down them */
  margin-bottom: 5px;
}

/* The chapter's own title, in the reading font like the story titles. */
.chapter-title {
  display: block;
  font-family: var(--text-font);
  font-size: var(--fs-lg);
  line-height: 1.4;
  color: var(--ink);
}

/* "No recording yet, so no read-along." It used to be a bare blue star, which
   said nothing to anyone who hadn't read the legend; it is now a small warm
   pill, and warm is this app's colour for "not yet" wherever it appears. */
.chapter-star {
  display: inline-block;
  margin-left: 8px;
  padding: 2px 8px;
  vertical-align: 2px;

  font-family: var(--ui-font);
  font-size: 0.72rem;
  font-weight: 600;

  color: var(--warm-text);
  background-color: var(--warm-soft);
  border-radius: var(--r-pill);
  cursor: help;
}

/* The line under the list explaining what the pill means. */
.chapter-legend {
  margin-top: var(--sp-5);
  font-family: var(--ui-font);
  font-size: var(--fs-sm);
  color: var(--ink-mute);
}


/* ------------------------------------------------------------
   SECTION 10 — THE BACK BUTTON (top-left, on every screen but the picker)
   ------------------------------------------------------------
   Shares the ".corner-button" base look (SECTION 2) with the settings burger
   and the vocab star — only the side and the colour differ.
   ------------------------------------------------------------ */

.back-button {
  left: var(--corner-edge);
  color: var(--ink-strong);
}


/* ------------------------------------------------------------
   SECTION 11 — THE FLASHCARDS BUTTON (on the picker)
   ------------------------------------------------------------ */

/* The way OUT of the picker, so it is the one filled button on the page.
   It used to be a plain white button that turned dark blue on hover, which
   meant the front page's main secondary action was invisible until you found
   it with the mouse. */
/* Since milestone 76 it is FILLED at rest (.btn-primary in the markup gives
   it the accent face, its lip and its ink) rather than a pale tint that only
   lit up under the mouse — the one thing on the front page you come back to
   every day should look it.

   It is laid out in three columns so the word stays CENTRED while the badge
   ("12 due") sits on the right: the two outside columns are always the same
   width, so the empty left one balances the badge. With no badge, both are
   empty and the word is simply centred, as before. */
.flashcards-button {
  margin-top: var(--sp-6);
  padding: 13px 16px;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  column-gap: var(--sp-3);
}
.flashcards-label {
  grid-column: 2;
}
/* The badge: the button's colours turned inside out, so it reads as a little
   tag stuck on the button rather than as more of the button's words. */
.flashcards-due {
  grid-column: 3;
  justify-self: end;
  padding: 3px 10px;
  border-radius: var(--r-pill);
  background-color: var(--on-accent);
  color: var(--accent-text);
  font-size: var(--fs-sm);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}


/* ------------------------------------------------------------
   SECTION 12 — THE FLASHCARDS PAGE (flip cards)
   ------------------------------------------------------------
   Each saved word is a 3D FLIP CARD: French on the front, its in-story meaning
   and the sentence it came from (with its English) on the back. A little
   wider than the reading column so more cards fit per row.
   ------------------------------------------------------------ */

.flashcards-page {
  max-width: 820px;
}

/* The "Clear saved words" button. It is destructive, so it is the QUIETEST
   thing on the page and it moved to the bottom, under the cards — it used to
   sit above "Study now", which put the one irreversible action on the page
   directly in the path of the one you actually came for. */
.clear-vocab-button {
  margin-top: var(--sp-5);
  color: var(--ink-soft);      /* still the quietest button on the page (it is
                                  the one that can't be undone), but readable */
}
.clear-vocab-button:hover {
  color: var(--danger);
  background-color: var(--danger-soft);
  border-color: var(--danger-line);
}

/* The small status line (since milestone 74, only #sync-status: whether your
   words have reached your account). */
.flashcards-status {
  min-height: 1.2em;
  margin: var(--sp-4) 0 0 0;
  font-family: var(--ui-font);
  color: var(--ink-soft);
  font-size: var(--fs-sm);
  font-style: italic;
}

/* Shown when nothing has been saved yet. */
.flashcards-empty {
  color: var(--ink-mute);
  font-family: var(--ui-font);
  font-size: var(--fs-md);
  font-style: italic;
  padding: var(--sp-6) 0;
}

/* The grid of cards: as many per row as fit, at least ~210px each. */
.flashcards-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  gap: var(--sp-4);
  margin-top: var(--sp-4);
  text-align: left;
}

/* --- ONE FLIP CARD ---
   "perspective" gives the 3D flip its depth. TWO THINGS HERE ARE FIXES
   RATHER THAN PREFERENCES, and both arrived in milestone 44.

   1. THE SHADOW IS ON THE CARD, NOT ON ITS TWO FACES. Both faces are real
      elements in 3D space, and a face turned away from you still casts its
      shadow — so a shadow on .card-face put the BACK's shadow on screen as a
      grey slab sticking out above the front. It was invisible until the
      shadows got deep enough to notice, which is exactly what this milestone
      did. The card itself never rotates, so its shadow can't misbehave.

   2. THE HEIGHT COMES FROM THE CONTENT, not from a number picked in advance.
      The two faces used to be absolutely positioned inside a fixed-height
      box, so ONE number had to serve a front holding three short lines AND a
      back holding a whole dictionary entry — which meant the fronts looked
      like blank paper and the longer backs scrolled anyway. Measured: the
      backs of six ordinary saved words want between 270px and 392px, so no
      single number was ever going to be right.
      The faces are laid over each other with GRID instead — both in row 1,
      column 1 — so the stack is as tall as the taller of the two. Fronts stop
      being mostly empty, an ordinary back is whole, and a runaway entry still
      meets max-height and scrolls, which is what that was always for. Cards
      in a row match each other, because the grid stretches them. */
.card {
  display: grid;                  /* one child, stretched to the row height */
  perspective: 1200px;
  cursor: pointer;
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-2);
  transition: box-shadow 0.16s var(--ease), transform 0.16s var(--ease);
}
/* The whole card lifts a little when you point at it, so a grid of them
   answers the mouse the way the story cards do. */
.card:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-3);
}
.card-inner {
  display: grid;                  /* the two faces share one cell */
  min-height: 244px;              /* a short word is still a card, not a strip */
  transition: transform 0.5s var(--ease);
  transform-style: preserve-3d;   /* let the back sit behind in 3D */
}
.card.flipped .card-inner {
  transform: rotateY(180deg);     /* the flip */
}

/* The two faces sit on top of each other; backface-visibility hides whichever
   one is currently turned away from you. */
.card-face {
  grid-area: 1 / 1;               /* both in the same cell = stacked */
  position: relative;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background-color: var(--surface);
  padding: var(--sp-4);
  max-height: 440px;
  overflow: auto;                 /* a runaway entry still scrolls inside */
}

/* FRONT: the French word, its part-of-speech chip, then the example
   sentence it came from — in that order, top to bottom, as one centred block
   with the flip hint pinned to the bottom edge. */
.card-front {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  padding-bottom: var(--sp-6);    /* keeps the block clear of the flip hint */
  text-align: center;
}
/* A hairline of accent along the top of every front. It is the only mark on
   an otherwise blank face, and it is what makes a grid of fronts read as a
   deck of cards rather than a sheet of empty boxes. */
.card-front::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 3px;
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  background-color: var(--accent);
}
.card-word {
  font-family: var(--text-font);  /* the reading font, like the story */
  font-size: 1.7rem;
  line-height: 1.15;
  letter-spacing: -0.015em;
  color: var(--ink-strong);
  overflow-wrap: anywhere;        /* a very long word wraps, never overflows */
}
/* The story sentence the word came from — French text, so it uses the same
   reading font as the story, just smaller and muted. */
.card-example {
  font-family: var(--text-font);
  font-size: 0.85rem;
  font-style: italic;
  color: var(--ink-soft);
  line-height: 1.45;
  margin: var(--sp-1) 0 0 0;
  padding: 0 var(--sp-1);
}
.card-flip-hint {
  position: absolute;
  bottom: 12px;
  left: 0;
  right: 0;
  text-align: center;
  font-family: var(--ui-font);
  font-size: 0.74rem;
  letter-spacing: 0;
  color: var(--ink-mute);
}

/* BACK: starts flipped 180° so it reads correctly once the card turns. */
.card-back {
  transform: rotateY(180deg);
  font-family: var(--trans-font);
  background-color: var(--surface-2);   /* a shade different from the front,
                                           so a flipped card is obvious at a
                                           glance across a whole grid */
}

/* The in-story meaning — the trusted, context-correct translation. */
.card-context {
  font-size: var(--fs-md);
  color: var(--ink-strong);
  font-weight: 600;
  line-height: 1.4;
  margin-bottom: var(--sp-3);
}
.card-context-label {
  display: block;
  font-size: 0.72rem;
  color: var(--ink-mute);
  margin-bottom: 3px;
}
/* The French word or expression the answer belongs to, above its meaning —
   in the reading font, because it is story text. */
.card-back-word {
  display: block;
  font-family: var(--text-font);
  font-size: 1.3rem;
  line-height: 1.2;
  color: var(--ink-strong);
  overflow-wrap: anywhere;
  margin-bottom: 2px;
}
.card-meaning {
  display: block;
}
/* The saved word inside its example sentence — the one occurrence you picked
   (app.js PART 7B), underlined the way the reader underlines a piece. */
.card-mark {
  text-decoration-line: underline;
  text-decoration-color: var(--accent);
  text-decoration-thickness: 2px;
  text-underline-offset: 0.2em;
  text-decoration-skip-ink: none;
}

/* The example sentence on the BACK, with what it means underneath. The French
   line uses the reading font (it's story text); the English line uses the
   translation font, exactly like the box above the story does. */
.card-sentence {
  border-top: 1px solid var(--line-soft);
  padding-top: var(--sp-2);
  margin-bottom: var(--sp-3);
}
.card-sentence-label {
  display: block;
  font-size: 0.72rem;
  color: var(--ink-mute);
  margin-bottom: 3px;
}
.card-sentence-fr {
  font-family: var(--text-font);
  font-size: 0.88rem;
  font-style: italic;
  color: var(--ink);
  line-height: 1.4;
  margin: 0;
}
.card-sentence-en {
  font-size: 0.84rem;
  color: var(--accent-text);
  line-height: 1.4;
  margin: 3px 0 0 0;
}
/* When there is no translation to show (a story added on the DEV screen). */
.card-sentence-missing {
  font-style: italic;
  color: var(--ink-mute);
}

/* The two buttons that lead out of this page: "Study now" (a review session,
   SECTION 13) and the flashcard options (SECTION 14). */
.flashcards-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--sp-3);
  margin-top: var(--sp-5);
}
/* "Study now" is a .btn-primary (the main action here); the options button is
   a plain .btn. */
.study-button,
.srs-settings-button {
  font-size: var(--fs-md);
  padding: 13px 26px;
}
/* Nothing due right now — still clickable, just visibly quiet. It is a
   .btn-primary underneath, so it also has to give back the accent-coloured
   lip: a violet underside on a white face reads as a mistake. */
.study-button.is-empty {
  color: var(--ink);
  background-color: var(--btn-face);
  border-color: var(--btn-edge);
  --btn-lip: var(--btn-lip-neutral);
}
.study-button.is-empty:hover {
  color: var(--ink-strong);
  background-color: var(--btn-face-hover);
  border-color: var(--accent-line);
}


/* ------------------------------------------------------------
   SECTION 13 — THE STUDY SCREEN (one card, four grade buttons)
   ------------------------------------------------------------
   A review session: the queue counts at the top, one flip card in the middle
   (the same .card built by app.js PART 7B, just bigger), and either "Show
   answer" or the four grade buttons underneath.
   ------------------------------------------------------------ */

.study-page {
  max-width: 540px;
}

/* The row along the top: the counts, then Undo beside them. */
.study-top {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-3);
  margin: 0 0 var(--sp-5) 0;
}
.study-undo:disabled {
  opacity: 0.6;
  cursor: default;
  transform: none;
  box-shadow: none;
}

/* The three counts: still learning / due for review / new. Each is now a
   little pill rather than a run-on sentence, because this line is a status
   readout you glance at, not prose you read. */
.study-counts {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2);

  margin: 0;
  font-family: var(--ui-font);
  font-size: var(--fs-sm);
  color: var(--ink-soft);
}
.count-learning,
.count-review,
.count-new {
  display: inline-block;
  padding: 5px 12px;
  border-radius: var(--r-pill);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.count-learning { color: var(--caution); background-color: var(--caution-soft); }
.count-review   { color: var(--good);    background-color: var(--good-soft); }
.count-new      { color: var(--accent-text); background-color: var(--accent-soft); }

/* The card sits here. It's the same flip card as the flashcards grid, but a
   single, taller one — this is the thing you're actually looking at.
   The study column is centred (it borrows .picker), but the card's BACK is a
   meaning and a sentence, and reads much better ranged left — the front sets its own
   centring, so this only affects the back. */
.study-area {
  text-align: left;
}
.study-area .card {
  cursor: default;              /* you flip it with the button below */
}
.study-area .card-inner {
  min-height: 300px;            /* one card on its own wants more presence
                                   than one of a dozen in a grid */
}
.study-area .card-face {
  max-height: 60vh;             /* it is the only thing on the screen, so it
                                   may use most of it before scrolling */
}
.study-area .card:hover {
  transform: none;              /* no hover lift: this card isn't clickable */
  box-shadow: var(--shadow-2);
}
.study-area .card-face {
  padding: var(--sp-5);
}
.study-area .card-word {
  font-size: 2.4rem;            /* the word is the point: make it big */
}
.study-area .card-example {
  font-size: var(--fs-md);
}
.study-area .card-flip-hint {
  display: none;                /* the "Show answer" button says this instead */
}
.study-area .card-back-word {
  font-size: 1.7rem;
}

/* THE FLASH when you grade a card: a ring in that grade's colour, a gradient
   with a soft glow, that fades in and straight back out around the card
   (app.js PART 11's flashGrade). It is drawn BEHIND the card and a little
   bigger than it, so only the edge shows. The colours are the grade
   buttons' own, swapped in per grade through two variables. */
.study-area {
  position: relative;
  isolation: isolate;           /* keeps the ring behind the card, not the page */
}
/* Two layers, both a little bigger than the card: ::before is the soft glow
   (an outer shadow only — nothing is drawn inside it), ::after the crisp
   gradient ring. The ring is MASKED down to its 3px edge rather than being a
   filled panel behind the card, so if the card turns while it is showing,
   there is nothing behind the card to see. (A mask only reads how opaque a
   colour is, so --page here means "solid", not "the page colour".) */
.study-area::before,
.study-area::after {
  content: "";
  position: absolute;
  inset: -5px;
  z-index: -1;
  border-radius: calc(var(--r-lg) + 5px);
  opacity: 0;
  pointer-events: none;
}
.study-area::before {
  box-shadow: 0 0 22px 4px var(--flash-line);
}
.study-area::after {
  padding: 3px;
  background: linear-gradient(135deg, var(--flash-strong), var(--flash-line) 50%, var(--flash-strong));
  -webkit-mask: linear-gradient(var(--page) 0 0) content-box, linear-gradient(var(--page) 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(var(--page) 0 0) content-box exclude, linear-gradient(var(--page) 0 0);
}
.study-area:empty::before,
.study-area:empty::after {
  display: none;                /* no card left, nothing to ring */
}
.study-area.flash-1 { --flash-strong: var(--danger);  --flash-line: var(--danger-line); }
.study-area.flash-2 { --flash-strong: var(--caution); --flash-line: var(--caution-line); }
.study-area.flash-3 { --flash-strong: var(--good);    --flash-line: var(--good-line); }
.study-area.flash-4 { --flash-strong: var(--accent);  --flash-line: var(--accent-line); }
.study-area.is-flashing::before,
.study-area.is-flashing::after {
  animation: grade-flash 0.55s var(--ease) both;
}
@keyframes grade-flash {
  0%   { opacity: 0; }
  25%  { opacity: 1; }
  100% { opacity: 0; }
}
/* Reduced motion: no fade — the ring is simply there for the moment the
   class is on, then gone. (SECTION 2 would otherwise shorten the fade to
   nothing at all, and you would see no sign of the press.) */
@media (prefers-reduced-motion: reduce) {
  .study-area.is-flashing::before,
  .study-area.is-flashing::after {
    animation: none;
    opacity: 1;
  }
}

/* The key that does the same thing as a button: Space, Z, 1–4. Small and in
   the button's own colour, so it is there for whoever looks and quiet for
   whoever doesn't. A touch screen has no keyboard, so it hides them. */
.key-hint {
  display: inline-block;
  margin-left: 8px;
  padding: 0 5px;
  min-width: 1.35em;
  border: 1px solid currentColor;
  border-radius: var(--r-sm);
  font-family: var(--ui-font);
  font-size: 0.7rem;
  font-weight: 600;
  line-height: 1.5;
  text-align: center;
  vertical-align: 0.1em;
}
@media (hover: none) and (pointer: coarse) {
  .key-hint { display: none; }
}

/* Before the flip: one wide, obvious button (.btn .btn-primary .btn-wide). */
.study-show {
  margin-top: var(--sp-4);
  padding: 15px 0;
  font-size: var(--fs-md);
}

/* After the flip: the four grades, side by side (they stack on a narrow
   phone, since each one keeps a sensible minimum width). */
.study-grades {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(112px, 1fr));
  gap: var(--sp-2);
  margin-top: var(--sp-4);
}
/* A .btn stacked into two lines: the grade, then when it would come back. */
.grade-button {
  position: relative;           /* for the key hint in its corner */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 13px 6px;
  font-size: var(--fs-sm);
  font-weight: 700;
}

/* "When would this bring the word back" — printed under each grade. */
.grade-when {
  font-size: 0.76rem;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  color: var(--ink-soft);
}

/* One colour per grade, kept soft so the page stays calm. Each keeps its
   coloured border at rest, so the four are distinguishable before you point
   at any of them. */
.grade-again { color: var(--danger);  border-color: var(--danger-line); }
.grade-again:hover, .grade-again.is-lit { background-color: var(--danger-soft); border-color: var(--danger); }
.grade-hard  { color: var(--caution); border-color: var(--caution-line); }
.grade-hard:hover, .grade-hard.is-lit   { background-color: var(--caution-soft); border-color: var(--caution); }
.grade-good  { color: var(--good);    border-color: var(--good-line); }
.grade-good:hover, .grade-good.is-lit   { background-color: var(--good-soft); border-color: var(--good); }
.grade-easy  { color: var(--accent-text); border-color: var(--accent-line); }
.grade-easy:hover, .grade-easy.is-lit   { background-color: var(--accent-soft); border-color: var(--accent); }

/* A grade chosen from the keyboard looks exactly like one pressed with the
   mouse: lit in its colour and driven down onto its lip, for a moment. */
.grade-button.is-lit {
  transform: translateY(3px);
  box-shadow: 0 1px 0 var(--btn-lip), var(--shadow-1);
}
/* On a grade button the key sits in the top-left corner, out of the way of
   the grade and its interval. */
.grade-button .key-hint {
  position: absolute;
  top: 6px;
  left: 7px;
  margin-left: 0;
}

/* The "nothing left to study" message. */
.study-done {
  margin: var(--sp-7) auto 0;
  max-width: 26rem;
  font-family: var(--ui-font);
  font-size: var(--fs-md);
  line-height: 1.65;
  color: var(--ink-soft);
}


/* ------------------------------------------------------------
   SECTION 14 — THE SETTINGS SECTIONS (used by four screens)
   ------------------------------------------------------------
   Our version of Anki's deck options — and, because they turned out to be
   exactly the right shape for a form, the same classes build the log-in
   screen, the account settings page and the Add content screen. Grouped into
   cards, one per topic, each a stack of "label on the left, control on the
   right" rows.

   Grouping is not decoration: a page of eighteen unlabelled fields is
   unreadable, and five cards of three or four are not. That is the whole
   reason the sections exist.
   ------------------------------------------------------------ */

.options-page {
  max-width: 580px;
}

/* One group of settings. */
.options-section {
  margin-top: var(--sp-5);
  padding: var(--sp-5) var(--sp-5) var(--sp-5);
  text-align: left;               /* the picker column centres; rows read left */
  background-color: var(--surface);
  border: 1px solid transparent;  /* a card is separated from the page by its
                                     shadow; a hairline as well is what makes a
                                     stack of them read as table rows */
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-2);
}
/* The section's title. It used to be tiny grey capitals with a 3px accent
   bar stuck to its left — a scannable arrangement, and unmistakably the
   header of a settings panel. A heading that is simply BIGGER than the rows
   under it scans just as well and looks like a page rather than a form, so
   the bar is gone and the size does the work. */
.options-title {
  margin: 0 0 var(--sp-4) 0;
  font-family: var(--ui-font);
  font-size: var(--fs-lg);
  line-height: 1.3;
}

/* A label on the left, its control on the right. */
.options-row {
  display: flex;
  flex-wrap: wrap;                /* the retention slider drops to its own line */
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  margin-bottom: var(--sp-3);
}
.options-row:last-child { margin-bottom: 0; }

.options-label {
  font-family: var(--ui-font);
  font-size: var(--fs-md);
  font-weight: 500;
  color: var(--ink);
}
.options-label-block {
  display: block;
  margin: var(--sp-4) 0 var(--sp-2) 0;
}
/* The live "0.90" next to the retention label. */
.options-value {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--accent-text);
}

.options-input {
  width: 104px;
  padding: 11px 13px;
  font-family: var(--ui-font);
  font-size: var(--fs-md);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  background-color: var(--surface-sunk);
  border: 1px solid transparent;
  border-radius: var(--r-md);
  text-align: right;
  transition: border-color 0.16s var(--ease),
              background-color 0.16s var(--ease);
}
.options-input:hover {
  border-color: var(--accent-line);
}
/* A row whose control is a TEXT box rather than a number stacks: the label on
   its own line, the box full width under it. An ".options-row" normally puts
   the label left and the control right, which is right for a column of numbers
   you are scanning down and wrong for a form you are typing into — a login
   screen with a 176px box floating on the right of its label looks like a
   database admin page, which is exactly the impression this milestone set out
   to lose.

   ":has()" asks "does this row CONTAIN a wide input?", which is what lets one
   rule cover the login form, the account settings, the two learning-step boxes
   and the Add content screen without any of them being told about it. A
   browser too old to understand :has() simply ignores the whole rule and gets
   the side-by-side layout that was there before — worse-looking, not broken. */
.options-input-wide {
  width: 176px;
  text-align: left;
  font-weight: 400;
}
/* ...and a WIDE PICKLIST (SECTION 2) is a form field of exactly the same
   kind, so it joins every one of these selectors rather than getting a set of
   its own. Written as a list rather than a comma inside :has(), because
   :has(a, b) is "contains either" and some browsers took longer to support
   that form than the plain one. */
.options-row:has(.options-input-wide),
.options-row:has(.picklist-wide) {
  display: block;
  margin-bottom: var(--sp-4);
}
.options-row:has(.options-input-wide) .options-label,
.options-row:has(.picklist-wide) .options-label {
  display: block;
  margin-bottom: 7px;
}
.options-row:has(.options-input-wide) .options-input-wide {
  width: 100%;
}
/* Focus is drawn by SECTION 2's shared ring rather than by an outline of its
   own, so a text box and a button look the same when tabbed to. */
.options-input:focus,
.options-textarea:focus {
  outline: 2px solid var(--ring);
  outline-offset: 1px;
  border-color: var(--accent);
  background-color: var(--surface);
}

/* The retention slider takes a full line of its own under its label. */
.options-row .size-slider {
  flex: 1 0 100%;
  margin-top: 2px;
}

/* A TOGGLE SWITCH, drawn out of an ordinary checkbox.
   "appearance: none" throws away the box the browser would have drawn and
   leaves us an empty element that still behaves exactly like a checkbox —
   it is still focusable, still tabbable, still says "checkbox" to a screen
   reader, still toggles on space. So this is a change of clothes, not of
   machinery: no markup and no JavaScript moved for it.
   A tick-box is the right control for "include this in a list"; a switch is
   the right one for "turn this on", which is what this setting actually is. */
.options-check {
  appearance: none;
  -webkit-appearance: none;
  flex: none;
  position: relative;
  width: 46px;
  height: 27px;
  margin: 0;
  background-color: var(--surface-sunk);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  cursor: pointer;
  transition: background-color 0.18s var(--ease),
              border-color 0.18s var(--ease);
}
/* The knob. It is --surface when off and --on-accent when on, which is the
   one pair of names guaranteed to stay visible in BOTH themes: --on-accent
   is near-black in the dark theme precisely because the accent is light
   there, so the knob never disappears into the track it is sitting on. */
.options-check::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 21px;
  height: 21px;
  border-radius: 50%;
  background-color: var(--surface);
  border: 1px solid var(--line-soft);
  box-shadow: var(--shadow-1);
  transition: transform 0.18s var(--ease),
              background-color 0.18s var(--ease);
}
.options-check:checked {
  background-color: var(--accent);
  border-color: var(--accent);
}
.options-check:checked::after {
  background-color: var(--on-accent);
  border-color: transparent;
  transform: translateX(19px);
}
.options-check:hover {
  border-color: var(--accent-line);
}

/* The 21 FSRS parameters. Monospace, because it's a list of numbers. */
.options-textarea {
  width: 100%;
  padding: var(--sp-4);
  font-family: var(--mono-font);
  font-size: 0.8rem;
  line-height: 1.7;
  color: var(--ink);
  background-color: var(--surface-sunk);
  border: 1px solid transparent;
  border-radius: var(--r-md);
  resize: vertical;
}

/* The small explanation under a group of settings. */
.options-help {
  margin: var(--sp-3) 0 0 0;
  font-family: var(--ui-font);
  font-size: var(--fs-sm);
  line-height: 1.65;
  color: var(--ink-soft);
}
.options-help code {
  font-family: var(--mono-font);
  font-size: 0.78rem;
  color: var(--ink);
  background-color: var(--surface-sunk);
  border: 1px solid var(--line-soft);
  padding: 1px 5px;
  border-radius: var(--r-xs);
}

/* Save / restore, under the last group. */
.options-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: var(--sp-3);
  margin-top: var(--sp-5);
}
/* Save is the .btn-primary here; the two reset buttons are .btn-small. */
.options-save {
  padding: 13px 28px;
}
.options-small-button:hover { color: var(--ink); }

/* "Saved." / "That didn't look like 21 numbers." */
.options-status {
  min-height: 1.3em;
  margin: var(--sp-4) 0 0 0;
  font-family: var(--ui-font);
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--good);             /* green: the usual case is "Saved." */
}
.options-status.is-error {
  color: var(--danger);           /* red: something needs fixing */
}


/* ------------------------------------------------------------
   SECTION 15 — ACCOUNTS (the top-right button, and its two screens)
   ------------------------------------------------------------
   The account button is the mirror image of the saved-words ★: the same
   .corner-button square and .corner-panel drop-down from SECTION 2, pinned
   to the RIGHT instead of the left. The two screens behind it are built
   almost entirely from things that already existed — .picker for the column,
   .options-section / .options-row / .options-input for the forms, .btn for
   every button — so there is very little here that is genuinely new.
   ------------------------------------------------------------ */

/* Keep the account UI in the stable interface font, like the ☰ and ★ corners. */
.account,
.account button {
  font-family: var(--ui-font);
}

/* The button itself, pinned top-right on the picker screen. Signed out it
   shows a small person icon; signed in, JavaScript puts your initial here —
   hence the slightly larger, bolder type than the other corner buttons. */
.account-button {
  right: var(--corner-edge);
  font-size: var(--fs-lg);
  font-weight: 700;
  color: var(--accent-text);
}

/* The panel that drops down under it (shares .corner-panel, SECTION 2). */
.account-panel {
  right: var(--corner-edge);
}

.account-heading {
  margin: 0 0 var(--sp-3) 0;
  font-size: var(--fs-sm);
}

/* The "why bother signing in" line, shown while signed out. */
.account-blurb {
  margin: 0 0 var(--sp-4) 0;
  font-size: var(--fs-sm);
  line-height: 1.6;
  color: var(--ink-soft);
}

/* Who you are, shown while signed in. */
.account-name {
  margin: 0;
  font-size: var(--fs-md);
  font-weight: 700;
  color: var(--ink-strong);
  overflow-wrap: anywhere;         /* a username with no spaces still wraps */
}
.account-email {
  margin: 2px 0 var(--sp-4) 0;
  font-size: var(--fs-xs);
  color: var(--ink-soft);
  word-break: break-all;           /* a long email wraps instead of overflowing */
}

/* The panel's buttons, stacked one per line with a little air between. */
.account-actions {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.account-actions .btn {
  padding: 11px 16px;
  font-size: var(--fs-sm);
}


/* ---- The log in / create account screen ---- */

.account-page {
  max-width: 470px;
}

/* Google sign-in. Deliberately a plain button in our own style — it is a
   normal .btn, just a touch quieter, and says what it does. */
.auth-google {
  margin-top: var(--sp-4);
  color: var(--ink);
}

/* The little "or" between Google and the email form. */
.auth-or {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin: var(--sp-4) 0 0 0;
  font-family: var(--ui-font);
  font-size: var(--fs-sm);
  letter-spacing: 0;
  color: var(--ink-mute);
}
/* A rule either side of the word, which is what an "or" divider is actually
   for: separating two ways of doing the same thing. */
.auth-or::before,
.auth-or::after {
  content: "";
  flex: 1;
  height: 1px;
  background-color: var(--line);
}

/* ---- The account settings screen ---- */

/* The one destructive button on the page, so it turns red on hover — the
   same signal the flashcards page's "Clear saved words" uses. */
.account-delete:hover {
  color: var(--danger);
  border-color: var(--danger-line);
  background-color: var(--danger-soft);
}

/* On the preferences rows the font buttons and sliders each want a full
   line of their own, under their label. */
.options-section .font-buttons,
.options-section .theme-buttons {
  margin-bottom: var(--sp-1);
}
.options-section .size-slider {
  width: 100%;
}


/* ---- YOUR PLAN (the billing section, app.js PART 21) ----
   The name and price on the left, and — only when there is something worth
   saying — a small badge on the right. Everything in it is written by
   JavaScript from billing/plans.js. */
.plan-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-4);
  margin-bottom: var(--sp-4);
}

.plan-name {
  margin: 0;
  font-size: var(--fs-xl);
  font-weight: 600;
  color: var(--ink-strong);
}

.plan-detail {
  margin: 2px 0 0 0;
  font-size: var(--fs-sm);
  color: var(--ink-soft);
}

/* Warm rather than red: "your card was declined" and "this ends on the 4th"
   are both things to notice, and neither is a disaster. The same colour the
   chapter list uses for "no recording yet" (SECTION 9B) — this app's one
   word for "attention, not alarm". */
.plan-badge {
  flex: none;
  padding: 3px 10px;
  border-radius: var(--r-pill);
  background-color: var(--warm-soft);
  border: 1px solid var(--warm-line);
  color: var(--warm-text);
  font-size: var(--fs-xs);
  font-weight: 600;
}


/* ------------------------------------------------------------
   SECTION 16 — THE "CREATE A STORY" SCREEN
   ------------------------------------------------------------
   Reached from the front page, and from Account settings -> Developer.
   Almost all of it is built from SECTION 14's ".options-*" classes and
   SECTION 2's ".btn", so there is very little here: the file pickers, the
   roomier text box, the cover preview, and the list of stories you've already
   made. The pieces that arrived with the translator and the recorder are in
   SECTION 20, at the bottom of the file, where new sections go.

   Also here: the two extra pieces a story CARD carries — the cover and the
   meta line (SECTION 9 has the card itself).
   ------------------------------------------------------------ */

/* A story title is longer than a username, so the text boxes on this screen
   are roomier than the ones the account page uses them at. */
#screen-create .options-input-wide {
  width: 230px;
  max-width: 100%;
}

/* "Choose file" buttons. The browser draws the button itself; all we do is
   make the surrounding text match the rest of the interface. */
.create-file {
  font-family: var(--ui-font);
  font-size: var(--fs-sm);
  color: var(--ink-soft);
  max-width: 100%;
}

/* The story box is prose, not numbers, so it overrides the monospace of the
   FSRS-parameters box it inherits from and is much taller. */
.create-textarea {
  font-family: var(--ui-font);
  font-size: var(--fs-sm);
  line-height: 1.65;
  min-height: 240px;
}

/* The cover you just picked, shown at the size it will actually be saved. */
.create-thumb-preview {
  display: block;
  width: 104px;
  height: 104px;
  margin-top: var(--sp-3);
  object-fit: cover;
  border-radius: var(--r-md);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-1);
}

/* The list of stories you've added, each with a way to remove it. */
.create-list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.create-list li {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-2) 0;
  border-bottom: 1px solid var(--line-soft);
  font-family: var(--ui-font);
  font-size: var(--fs-sm);
  color: var(--ink);
}
.create-list li:last-child {
  border-bottom: none;
}

/* The title takes whatever room is left, so the button stays on the right. */
.create-item-name {
  flex: 1;
  font-weight: 600;
  overflow-wrap: anywhere;
}

/* What the story is made of — a quiet second line under its title. */
.create-item-meta {
  font-size: var(--fs-xs);
  color: var(--ink-mute);
  white-space: nowrap;
}

/* Removing a story can't be undone, so its button turns red on hover — the
   same signal "Clear saved words" and "Delete account" use. */
.create-item-delete:hover {
  color: var(--danger);
  border-color: var(--danger-line);
  background-color: var(--danger-soft);
}

/* The "nothing here yet" line. */
.create-empty {
  margin: 0;
  font-family: var(--ui-font);
  font-size: var(--fs-sm);
  color: var(--ink-mute);
  font-style: italic;
}

/* ---- What a story CARD is made of ----
   The cover runs edge to edge along the top of the card (SECTION 9 removes
   the card's padding so it can), and the title and meta sit under it with
   their own. */

.story-card-title {
  display: block;
  /* The title takes whatever slack the row has, which puts every card's level
     and length on the SAME LINE across a row. Grid stretches the cards to a
     common height already; without this the slack collects at the bottom
     instead, so a card whose title wraps to two lines sits its facts a line
     lower than its neighbours' and the shelf reads as a ragged ladder. */
  flex: 1 1 auto;
  padding: var(--sp-4) var(--sp-4) 0;
  font-family: var(--display-font);
  font-size: var(--fs-lg);
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: var(--ink-strong);
}

.story-card-thumb {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;      /* a shelf of covers all the same shape, whatever
                               shape the pictures themselves are */
  object-fit: cover;
  border-bottom: 1px solid var(--line-soft);
}

/* The stand-in for a story with no picture of its own: its first letter on a
   coloured tile. It shares the box above, so a shelf of placeholders and a
   shelf of photographs line up exactly. app.js (PART 3) sets only the HUE,
   worked out from the title, so a story's cover is stable — and the two
   lightness values come from the theme, so the same tile is pale on cream
   and deep in the dark. */
.story-card-cover {
  display: flex;
  align-items: center;
  justify-content: center;

  font-family: var(--display-font);
  font-size: 3.4rem;
  font-weight: 600;
  line-height: 1;
  user-select: none;

  /* Two stops of the SAME hue rather than one flat fill. The difference is
     small on paper and large on screen: a flat swatch reads as a missing
     image, and a lit one reads as a cover somebody chose. */
  background-image: linear-gradient(
      148deg,
      hsl(var(--cover-hue, 210) var(--cover-bg-s) calc(var(--cover-bg-l) + 5%)),
      hsl(calc(var(--cover-hue, 210) + 18) var(--cover-bg-s) calc(var(--cover-bg-l) - 6%)));
  color: hsl(var(--cover-hue, 210) var(--cover-fg-s) var(--cover-fg-l));
}

/* Language and difficulty used to be one grey line here — "French · Level 1".
   The language half was noise (the front page shows exactly ONE language at a
   time, and the Language button says which), and "Level 1" in small grey type
   is the least readable way to say the thing a learner most wants to know. It
   is replaced by the three pieces below: how hard, how long, and whether it is
   read aloud. */

/* ---- How hard: five dots, filled to the story's level ---- */
.story-card-level {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4) 0;
}

.level-pips {
  display: inline-flex;
  gap: 4px;
}
/* Not text, so no contrast rule to clear — but an empty pip still has to be
   visible as an empty pip, which is why it is --line rather than a wash. */
.level-pip {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  /* --btn-edge, not --line: this dot sits ON a button face, and --line is a
     hairline between rows that measures 1.09:1 against that face in the dark
     theme — four invisible dots and one bright one, which reads as a single
     dot rather than as "one out of five". --btn-edge is the token whose whole
     job is being visible on a button. */
  background-color: var(--btn-edge);
}
.level-pip.on {
  background-color: var(--accent-text);
}

.level-name {
  font-family: var(--ui-font);
  font-size: var(--fs-sm);
  font-weight: 500;
  color: var(--ink-soft);
}

/* ---- How long, and whether it is read aloud ----
   One line, because they are the same kind of fact: what you are taking on. */
.story-card-facts {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2);

  /* The row reserves the CHIP's height whether or not there is a chip on it.
     Without that it is 4px shorter on a story with no recording — the chip is
     a line of text plus 2px of padding top and bottom — and since the cards in
     a grid row are all one height, those 4px come off the bottom of the card
     and push that card's level and length 4px down relative to its
     neighbours'. Small, and the kind of small that makes a shelf look
     approximate. (1.6em clears 1.25 of line plus the chip's 4px of padding,
     measured in the row's own font-size so it holds if that size changes.) */
  --facts-pad: 6px;
  min-height: calc(1.6em + var(--facts-pad));
  padding: var(--facts-pad) var(--sp-4) 0;
  font-family: var(--ui-font);
  font-size: 0.78rem;
  font-weight: 500;
  color: var(--ink-mute);
}

/* The "Read-along" chip. Until now the only way to find out whether a story
   had a recording was to open it and look for the ▶ button. */
.story-card-audio {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 9px 2px 7px;
  border-radius: var(--r-pill);
  background-color: var(--accent-wash);
  color: var(--accent-text);
  font-weight: 600;
}
.story-card-audio .btn-icon {
  width: 13px;
  height: 13px;
  margin: 0;
}

/* ---- "Continue" ----
   A story you have already got somewhere in, read from the reading positions
   the reader has always kept (PART 3C). It sits ON the cover rather than in
   the text below it, because it is the one thing on a card that is about YOU
   rather than about the story — and it carries its own solid background, so
   it is readable over a photograph as well as over a placeholder tile. */
.story-card-flag {
  position: absolute;
  top: var(--sp-3);
  left: var(--sp-3);

  display: inline-flex;
  align-items: center;
  gap: 5px;

  padding: 4px 10px;
  border-radius: var(--r-pill);

  font-family: var(--ui-font);
  font-size: var(--fs-xs);
  font-weight: 600;
  line-height: 1.3;

  color: var(--on-accent);
  background-color: var(--accent);
  box-shadow: var(--shadow-1);
}
.story-card-flag .btn-icon {
  width: 12px;
  height: 12px;
  margin: 0;
}



/* ------------------------------------------------------------
   SECTION 17 — THE LANGUAGE BUTTON (front page, top right)
   ------------------------------------------------------------
   Sits just left of the account button, so the two right-hand corners read as
   a pair: which shelf you're looking at, and who you are. The button and the
   panel get their whole look from SECTION 2's .corner-button / .corner-panel;
   only the position and the rows inside are new.
   ------------------------------------------------------------ */

/* One tile-width plus a gap further in than the account button, so the two
   right-hand corners sit side by side at every screen width (SECTION 2 keeps
   the three numbers). */
.language-button {
  right: calc(var(--corner-edge) + var(--corner-w) + var(--corner-gap));
  font-size: var(--fs-sm);
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--accent-text);
}

/* WHAT THE BUTTON SHOWS: the code and its flag, side by side, inside the
   .corner-glyph span (SECTION 2) that keeps them 21px tall so the "Language"
   word underneath never shifts. The flag is a size down from the panel's,
   because the tile is only 64px wide on a phone and the code has to fit
   beside it. */
#language-glyph {
  gap: 5px;
}
.language-code {
  line-height: 1;
}
#language-glyph .flag {
  width: 18px;
  height: 12px;
  border-radius: 3px;
}

/* The panel drops down under both buttons, like the account one. */
.language-panel {
  right: var(--corner-edge);
}

.language-heading {
  margin: 0 0 var(--sp-3) 0;
  font-family: var(--ui-font);
  font-size: var(--fs-sm);
}

.language-list {
  display: grid;
  gap: 6px;
}

/* One shelf. A .btn (SECTION 2) with its name on the left and how many
   stories are on it on the right. */
.language-option {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 14px;
  font-size: var(--fs-sm);
  text-align: left;
  box-shadow: 0 2px 0 var(--btn-lip), var(--shadow-1);
}
.language-option:hover {
  transform: none;        /* a list of rows shouldn't bounce under the mouse */
  box-shadow: 0 3px 0 var(--btn-lip), var(--shadow-2);
}
.language-option:active {
  transform: translateY(1px);
  box-shadow: 0 1px 0 var(--btn-lip), var(--shadow-1);
}

/* The one you're on, marked the same way the font buttons mark their choice. */
.language-option.active {
  color: var(--on-accent);
  background-color: var(--accent);
  border-color: var(--accent);
  font-weight: 600;
  --btn-lip: var(--accent-lip);
}
.language-option.active:hover {
  color: var(--on-accent);
  background-color: var(--accent-hover);
  border-color: var(--accent-hover);
}

/* A FLAG. The span is what makes it a flag rather than three coloured
   rectangles: it rounds the corners (4px on a 24x16 tile is a lot, and is
   most of why these read as friendly little stickers rather than as a
   diplomatic display), clips the bands to that rounding, and draws the
   hairline edge on the INSIDE with an inset shadow — a real border would
   have to be added to the width, and this way the tile is exactly the size
   it says it is. The drop shadow is the same one every small raised thing in
   the app uses, and lifts the flag off the row it sits on. */
.flag {
  display: block;
  width: 24px;
  height: 16px;
  flex: none;                  /* never squashed by the name beside it */
  overflow: hidden;
  border-radius: 4px;
  box-shadow: inset 0 0 0 1px var(--flag-edge), var(--shadow-1);
}
.flag svg {
  display: block;
  width: 100%;
  height: 100%;
}

/* The two little wrappers a flag sits in — one in a panel row, one on the
   corner button. Both exist only so the span around the flag is a flex box
   and adds no stray line-height under it. */
.language-option-flag,
.language-flag {
  display: flex;
  flex: none;
}

.language-option-name {
  flex: 1;
}

/* The count. Quiet, and it stays readable on the highlighted row too. */
.language-option-count {
  font-size: var(--fs-xs);
  opacity: 0.72;
  font-variant-numeric: tabular-nums;
}

/* "No stories in this language yet." — only reachable by filtering to a
   language and then deleting the last story in it. */
/* Its box, which is what spans the shelf — the message may be followed by a
   button that puts the level filter back to "every level". */
.shelf-empty {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-6) 0;
  text-align: center;
}

.picker-empty {
  margin: 0;
  font-family: var(--ui-font);
  font-size: var(--fs-md);
  font-style: italic;
  color: var(--ink-mute);
}


/* ------------------------------------------------------------
   SECTION 18 — READING PROGRESS (the card, the graph, the calendar,
                                   and the Analysis page)
   ------------------------------------------------------------
   Nearly all of it is built on classes that already exist: the 📊 button is a
   .corner-button (SECTION 2) pinned one button-width right of the ★, the
   Analysis page is the .picker column (SECTION 9) with SECTION 14's
   ".options-*" sections inside it, and "Mark as read" is a plain .btn. What's
   genuinely new is the graph, the calendar and the two lists.
   ------------------------------------------------------------ */

/* The 📊 tab on the front page — the mirror of the 🌐 button on the right,
   at the same distance from its own corner. */
.analysis-button {
  left: calc(var(--corner-edge) + var(--corner-w) + var(--corner-gap));
  color: var(--accent-text);
}

/* ---- The "Mark as read" row, at the end of a text ---- */
.mark-read-row {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--sp-3);
  max-width: 640px;                       /* the reading measure, as above */
  margin: var(--sp-4) auto var(--sp-7);
}
.mark-read-button:disabled {
  opacity: 0.7;
  cursor: default;
  transform: none;
  box-shadow: none;
}
/* What that press counted, next to the button. */
.mark-read-note {
  font-family: var(--ui-font);
  font-size: var(--fs-sm);
  color: var(--good);
  font-weight: 500;
}

/* ---- The small card on the front page ---- */
.progress-card {
  margin: var(--sp-7) auto 0 auto;
  max-width: 384px;
  padding: var(--sp-5);

  background-color: var(--surface);
  border: 1px solid transparent;
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-2);
}

.progress-figures {
  display: flex;
  justify-content: center;
  gap: var(--sp-7);
}
.progress-figure {
  margin: 0;
  text-align: center;
}
.progress-number {
  display: block;
  font-family: var(--ui-font);
  font-size: 2.1rem;
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
}
.progress-caption {
  display: block;
  margin-top: 5px;
  font-family: var(--ui-font);
  font-size: var(--fs-sm);
  color: var(--ink-soft);
}

/* The two colours used for the two numbers EVERYWHERE — on the card, on the
   Analysis page, and for the two lines on the graph — so a number and its
   line are always recognisably the same thing. */
.progress-known  { color: var(--accent-text); }
.progress-strong { color: var(--good); }

.progress-graph {
  margin-top: var(--sp-3);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--line-soft);
}

.progress-hint {
  margin: var(--sp-2) 0 0 0;
  font-family: var(--ui-font);
  font-size: var(--fs-xs);
  line-height: 1.5;
  color: var(--ink-mute);
  text-align: center;
}

/* ---- The graph itself (an SVG drawn by app.js PART 19) ----
   The drawing uses a fixed viewBox; these two lines are what let the same
   picture fill the small card and the wide page. The viewBox is WIDER for the
   page version (app.js chooses), because one shape stretched to two very
   different widths is how a graph ends up tall and empty. */
.graph {
  display: block;
  width: 100%;
  height: auto;
}
.graph-grid {
  stroke: var(--line);
  stroke-width: 1;
}
.graph-axis {
  font-family: var(--ui-font);
  font-size: 9px;
  fill: var(--ink-mute);
}
.graph-line {
  fill: none;
  stroke-width: 2.2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.graph-line-known  { stroke: var(--accent); }
.graph-line-strong { stroke: var(--good); }
.graph-dot-known   { fill: var(--accent); }
.graph-dot-strong  { fill: var(--good); }

/* ---- The calendar (app.js PART 19 builds it) ----
   One month of little squares, drawn twice: small on the front page's card,
   larger on the Analysis page. The grid itself is the same seven columns
   either way — only the size of a square changes, which is these two
   variables' whole job. */
.calendar {
  --cell: 26px;          /* how big one day is */
  --cell-gap: 4px;
  --calendar-bg: var(--surface);   /* what it's sitting ON — see .is-today */
  margin-top: var(--sp-3);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--line-soft);
}

.calendar-title {
  margin: 0 0 var(--sp-2) 0;
  font-family: var(--ui-font);
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: 0;
  color: var(--ink-soft);
  text-align: center;
}

/* Seven columns, centred: the day letters, then the squares. */
.calendar-grid {
  display: grid;
  grid-template-columns: repeat(7, var(--cell));
  gap: var(--cell-gap);
  justify-content: center;
}

/* The M T W T F S S row. These letters say which column is which day, so
   they are information, not decoration — hence --ink-mute rather than the
   fainter grey they started at (2.5:1, which almost nobody can read). */
.calendar-dow {
  font-family: var(--ui-font);
  font-size: 0.62rem;
  font-weight: 600;
  color: var(--ink-mute);
  text-align: center;
  line-height: 1.6;
}

/* One day. The default is a day that has been and gone with nothing on it. */
.calendar-day {
  display: flex;
  align-items: center;
  justify-content: center;

  height: var(--cell);
  border-radius: 7px;

  background-color: var(--surface-sunk);
  font-family: var(--ui-font);
  font-size: 0.68rem;
  font-variant-numeric: tabular-nums;
  color: var(--ink-mute);
}

/* The squares before the 1st: they hold the row open and nothing else. */
.calendar-blank { height: var(--cell); }

/* A day still to come is barely there — nothing has happened on it yet, and
   nothing can have. */
/* A day still to come is barely there — but its NUMBER still has to be
   readable, so what marks it out is the missing background, not unreadable
   text. */
.calendar-day.is-future {
  background-color: transparent;
  box-shadow: inset 0 0 0 1px var(--line-soft);
  color: var(--ink-mute);
}

/* A day you worked, in three strengths of the accent blue: very light, mid,
   full. The three are spaced far enough apart to tell apart at 22px across —
   an early version used the accent and its hover shade for the top two, and
   they were the same square as far as the eye could tell. Each has its own
   text colour so the number stays readable against it. */
.calendar-day.is-active  { font-weight: 700; }
.calendar-day.is-level-1 { background-color: var(--accent-soft); color: var(--accent-text); }
.calendar-day.is-level-2 { background-color: var(--accent-line); color: var(--accent-text); }
.calendar-day.is-level-3 { background-color: var(--accent);      color: var(--on-accent); }

/* Today, whether or not you have done anything yet.
   The ring is drawn OUTSIDE the square (box-shadow, not a border) so today's
   square stays exactly the size of every other one — and it is drawn in TWO
   rings: a couple of pixels of the background colour first, then the accent.
   Without that gap, a today that is also a busy day is an accent ring around
   an accent square, which is just a slightly bigger square. The gap is what
   makes it read as a ring. It fits inside --cell-gap, so it never touches
   the day beside it. */
.calendar-day.is-today {
  box-shadow: 0 0 0 2px var(--calendar-bg), 0 0 0 3.5px var(--accent);
}
/* A future "today" would otherwise lose its ring to the .is-future rule
   above, since both set box-shadow. */
.calendar-day.is-future.is-today {
  box-shadow: 0 0 0 2px var(--calendar-bg), 0 0 0 3.5px var(--accent);
}

/* "12 active days in August" */
.calendar-note {
  margin: var(--sp-2) 0 0 0;
  font-family: var(--ui-font);
  font-size: var(--fs-xs);
  color: var(--ink-mute);
  text-align: center;
}

/* The small copy on the front page's card, and the larger one on the
   Analysis page. Everything above is shared; these two only resize it. */
.progress-calendar .calendar {
  --cell: 22px;
}
.analysis-calendar .calendar {
  --cell: 36px;
  --cell-gap: 7px;
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}
.analysis-calendar .calendar-title { font-size: var(--fs-sm); }
.analysis-calendar .calendar-day   { font-size: var(--fs-sm); border-radius: 10px; }
.analysis-calendar .calendar-dow   { font-size: var(--fs-xs); }
.analysis-calendar .calendar-note  { font-size: var(--fs-sm); }

/* ---- The Analysis page ---- */
.analysis-page {
  max-width: 660px;
}

/* Said once, at the top: this page is a first version. */
.analysis-unfinished {
  display: flex;
  gap: 10px;
  margin: 0 auto var(--sp-5) auto;
  max-width: 30rem;
  padding: 11px var(--sp-4);
  text-align: left;

  background-color: var(--warm-wash);
  border: 1px solid var(--warm-line);
  border-radius: var(--r-md);

  font-family: var(--ui-font);
  font-size: var(--fs-sm);
  line-height: 1.55;
  color: var(--ink-soft);
}
/* A small warm dot, so the note reads as an aside rather than as a warning. */
.analysis-unfinished::before {
  content: "";
  flex: none;
  width: 6px;
  height: 6px;
  margin-top: 8px;
  border-radius: 50%;
  background-color: var(--warm);
}

/* The two headline numbers — the card's figures, larger. */
.analysis-figures {
  display: flex;
  justify-content: center;
  gap: var(--sp-7);
  margin-bottom: var(--sp-5);
}
.analysis-figure { margin: 0; text-align: center; }
.analysis-number {
  display: block;
  font-family: var(--ui-font);
  font-size: 2.6rem;
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -0.025em;
  font-variant-numeric: tabular-nums;
}
.analysis-caption {
  display: block;
  margin-top: var(--sp-1);
  font-family: var(--ui-font);
  font-size: var(--fs-sm);
  color: var(--ink-soft);
}

.analysis-graph { margin-bottom: 6px; }

/* The sort control borrows ".options-input" (SECTION 14), which is a narrow,
   right-aligned box built for numbers — a dropdown needs room for its longest
   word and its little arrow, so it says so here. */
/* The Analysis page's "Sort by" is a plain (narrow) .picklist — SECTION 2 —
   so there is nothing left to say about it here. */

/* The two lists, side by side on a wide screen and stacked on a phone. */
.analysis-lists {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--sp-4);
  margin-top: var(--sp-3);
}
.analysis-list-title {
  margin: 0 0 6px 0;
  font-family: var(--ui-font);
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--ink-soft);
}

/* Each list scrolls inside its own box, so a thousand words can't push the
   page down the screen. */
.analysis-list {
  list-style: none;
  margin: 0;
  padding: 0 var(--sp-3);

  max-height: 340px;
  overflow-y: auto;

  background-color: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
}
.analysis-item {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  padding: 8px 0;
  border-bottom: 1px solid var(--line-soft);
  font-family: var(--ui-font);
  font-size: var(--fs-sm);
}
.analysis-item:last-child { border-bottom: none; }

/* The "nothing read yet" row and the "…and 412 more" row both borrow
   .vocab-empty (SECTION 8), which was written for a panel with its own
   padding — inside these bordered boxes it needs its own. */
.analysis-list .vocab-empty {
  display: block;
  padding: var(--sp-4) 0;
  text-align: center;
}

.analysis-word  { flex: 1; font-weight: 600; color: var(--ink); overflow-wrap: anywhere; }
.analysis-times { color: var(--accent-text); font-weight: 600; font-variant-numeric: tabular-nums; }
.analysis-when  { color: var(--ink-mute); font-size: var(--fs-xs); min-width: 48px; text-align: right; }


/* ------------------------------------------------------------
   SECTION 19 — THE LANDING PAGE (landing.html)
   ------------------------------------------------------------
   The front door: the page a stranger sees before they have an account.
   Everything here is prefixed "lp-" (landing page) so you can tell at a
   glance whether a rule belongs to the app or to the shop window in front
   of it — and so that deleting the landing page one day is a matter of
   deleting one block.

   It shares this file with the app rather than having a stylesheet of its
   own, which is the single decision that keeps the two looking like one
   product: it inherits all forty-odd colour tokens, both themes, the .btn,
   the focus ring and the reduced-motion block, and it obeys the same rule
   as every section above it — NO COLOUR IS TYPED HERE, only named.

   Three shapes carry the whole page:
     .lp-section-head   a centred title + subtitle, used five times
     .lp-card           a white card with a picture in it
     .lp-email          the pill with a box and a round arrow button
   ------------------------------------------------------------ */

.lp-body {
  /* How wide the page's content is allowed to get, and how far it stands off
     the edge on a phone. Every section below is written in terms of these
     two, so the whole page re-flows from one place. */
  --lp-max: 1120px;
  --lp-edge: var(--sp-5);

  /* The hero's headline is bigger than anything in the app, so it gets its
     own step rather than borrowing --fs-display and looking undersized. */
  --lp-display: clamp(2.4rem, 1.5rem + 4.2vw, 4rem);

  font-family: var(--ui-font);
  line-height: 1.55;
}

@media (max-width: 560px) {
  .lp-body { --lp-edge: var(--sp-4); }
}

/* The top bar is sticky, so a link to #demo or #pricing would otherwise land
   with the section's heading tucked underneath it. This stops the jump that
   much short. */
.lp-body section[id] { scroll-margin-top: 72px; }

/* Something for a screen reader to read that nobody needs to look at — the
   labels on the three email boxes, whose placeholder says the same thing to
   everyone else. Hidden by clipping rather than by `display: none`, because
   a screen reader skips anything genuinely hidden. */
.lp-visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* A LINK DRESSED AS A BUTTON. Half the buttons on this page are really
   links — they go somewhere rather than doing something, and a link is what
   a browser lets you open in a new tab, copy the address of, and follow with
   scripts switched off. .btn was written for <button>, so these three lines
   are what make an <a> sit in it properly. */
.lp-body a.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  text-decoration: none;
}

/* A shorter .btn, for the top bar where a full-height one would make the bar
   too tall to keep on screen. */
.btn-compact {
  padding: 9px 18px;
  font-size: var(--fs-sm);
  border-radius: var(--r-sm);
  box-shadow: 0 3px 0 var(--btn-lip), var(--shadow-1);
}
.btn-compact:hover { box-shadow: 0 4px 0 var(--btn-lip), var(--shadow-2); }
.btn-compact:active { transform: translateY(2px); box-shadow: 0 1px 0 var(--btn-lip), var(--shadow-1); }


/* ============================================================
   THE TOP BAR
   ============================================================ */

.lp-nav {
  position: sticky;
  top: 0;
  z-index: 40;
  background-color: var(--page);
  border-bottom: 1px solid var(--line-soft);
}

.lp-nav-inner {
  max-width: var(--lp-max);
  margin: 0 auto;
  padding: var(--sp-3) var(--lp-edge);
  display: flex;
  align-items: center;
  gap: var(--sp-5);
}

.lp-brand {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  text-decoration: none;
  margin-right: auto;          /* pushes everything else to the right */
}

/* THE LECTICAL MARK — the same tile as the browser-tab icon, so the tab and
   the page agree about what this is. One look, used twice (the top bar and
   the final panel), and sized by one number: --mark-size. Its colours are
   four local variables so the final panel can draw it the other way round
   without a second set of rules.

   The keyline is two inset shadows: the first paints a thin band of the
   tile's own colour at the very edge, the second paints the keyline just
   inside it. Its colour is the ink mixed 38% into the tile — a solid
   colour rather than a see-through one, so it looks the same on any page. */
.lp-mark {
  --mark-size: 30px;
  --mark-tile: var(--brand-tile);
  --mark-ink: var(--brand-ink);
  --mark-underline: var(--brand-underline);
  display: grid;
  place-items: center;
  flex: none;
  width: var(--mark-size); height: var(--mark-size);
  border-radius: calc(var(--mark-size) * 0.22);
  background-color: var(--mark-tile);
  box-shadow:
    inset 0 0 0 calc(var(--mark-size) * 0.045) var(--mark-tile),
    inset 0 0 0 calc(var(--mark-size) * 0.075)
      color-mix(in srgb, var(--mark-ink) 38%, var(--mark-tile));
  color: var(--mark-ink);
  font-family: var(--display-font);
  font-size: calc(var(--mark-size) * 0.6);
  font-weight: 600;
  line-height: 1;
}

/* The L itself, lifted a touch so the L and its underline together sit in
   the middle of the tile rather than the L alone. */
.lp-mark-letter {
  position: relative;
  top: -0.06em;
}

/* The underline: a short terracotta bar under the L, a little wider than the
   letter on its right, where the L's foot reaches. */
.lp-mark-letter::after {
  content: "";
  position: absolute;
  left: -0.06em;
  right: -0.12em;
  bottom: -0.14em;
  height: max(1.5px, 0.09em);
  border-radius: 2px;
  background-color: var(--mark-underline);
}

.lp-brand-name {
  font-family: var(--display-font);
  font-size: var(--fs-lg);
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink-strong);
}

.lp-nav-links {
  display: flex;
  gap: var(--sp-5);
}

.lp-nav-links a,
.lp-nav-login {
  color: var(--ink-soft);
  text-decoration: none;
  font-size: var(--fs-sm);
  font-weight: 500;
  transition: color 0.16s var(--ease);
}
.lp-nav-links a:hover,
.lp-nav-login:hover { color: var(--accent-text); }

.lp-nav-actions {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
}

/* On a phone there is room for the wordmark and the two actions, and nothing
   else — so the section links go, rather than wrapping onto a second row and
   doubling the height of a bar that is stuck to the top of the screen. */
@media (max-width: 720px) {
  .lp-nav-links { display: none; }
  .lp-nav-inner { gap: var(--sp-3); }
}
@media (max-width: 400px) {
  .lp-nav-login { display: none; }   /* the footer and the final panel still have it */
}


/* ============================================================
   THE HERO
   ============================================================ */

.lp-hero {
  max-width: var(--lp-max);
  margin: 0 auto;
  padding: clamp(var(--sp-7), 7vw, var(--sp-8)) var(--lp-edge) var(--sp-7);
}

.lp-hero-inner {
  max-width: 46rem;           /* a reading measure, for the same reason the
                                 reader has one: a headline three feet wide
                                 is a headline nobody finishes */
  margin: 0 auto;
  /* CENTRED, and that is a decision rather than a default. Ranged left, the
     hero leaves the right-hand half of a wide screen completely empty —
     which works when there is a picture to put there, and reads as an
     unfinished page when there is not. Everything below it (the proof
     numbers, the section headings) is centred too, so this also stops the
     page starting off-axis and correcting itself two screens later. */
  text-align: center;
}

.lp-eyebrow {
  display: inline-block;
  margin: 0 0 var(--sp-4) 0;
  padding: 5px 12px;
  border-radius: var(--r-pill);
  background-color: var(--accent-soft);
  color: var(--accent-text);
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.01em;
}

.lp-hero-title {
  margin: 0 0 var(--sp-4) 0;
  font-family: var(--display-font);
  font-size: var(--lp-display);
  font-weight: 600;
  line-height: 1.03;
  letter-spacing: -0.025em;
  color: var(--ink-strong);
}

/* The italic half of the headline. It is the accent colour AND italic, never
   colour alone — the same rule the karaoke word follows (CLAUDE.md §6). */
.lp-hero-title em {
  color: var(--accent-text);
  font-style: italic;
}

.lp-hero-sub {
  margin: 0 auto var(--sp-6);
  max-width: 36rem;
  font-size: var(--fs-lg);
  line-height: 1.6;
  color: var(--ink-soft);
}
.lp-hero-sub em {
  color: var(--ink);
  font-style: italic;
}

/* ---- THE EMAIL PILL ----
   A rounded white box with a round dark button sitting inside its right-hand
   end. The button is INSIDE the pill rather than beside it so the two read as
   one control; the padding on the input is what keeps the typing clear of it. */
.lp-email {
  position: relative;
  display: flex;
  align-items: center;
  max-width: 30rem;
  margin: 0 auto;
  background-color: var(--surface);
  border: 1px solid var(--btn-edge);
  border-radius: var(--r-pill);
  box-shadow: var(--shadow-2);
  transition: border-color 0.16s var(--ease), box-shadow 0.16s var(--ease);
}
.lp-email:focus-within {
  border-color: var(--accent-line);
  box-shadow: var(--shadow-3);
}

.lp-email-input {
  flex: 1;
  min-width: 0;                /* lets it shrink inside the flex row instead
                                  of forcing the pill wider than the screen */
  padding: 15px 8px 15px 22px;
  border: 0;
  background: none;
  font-family: var(--ui-font);
  font-size: var(--fs-md);
  color: var(--ink);
  border-radius: var(--r-pill);
}
.lp-email-input::placeholder { color: var(--ink-mute); }
.lp-email-input:focus { outline: none; }   /* the pill shows the focus instead */

.lp-email-go {
  flex: none;
  display: grid;
  place-items: center;
  width: 44px; height: 44px;   /* the thumb-sized minimum, SECTION 2 */
  margin: 4px 4px 4px 0;
  border: 0;
  border-radius: 50%;
  background-color: var(--accent);
  color: var(--on-accent);
  cursor: pointer;
  transition: background-color 0.16s var(--ease), transform 0.12s var(--ease);
}
.lp-email-go:hover  { background-color: var(--accent-hover); }
.lp-email-go:active { transform: scale(0.94); }

.lp-email-go svg {
  width: 20px; height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.lp-hero-note,
.lp-final-note {
  margin: var(--sp-3) 0 0 0;
  font-size: var(--fs-sm);
  color: var(--ink-mute);
}

/* What a complaint about the typed email looks like. Red AND a changed
   sentence, never colour on its own. */
.lp-note-bad { color: var(--danger); font-weight: 600; }

.lp-final-note a { color: var(--panel-loud-ink); }

/* ---- The languages there are stories in ---- */
.lp-langs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--sp-3) var(--sp-4);
  margin-top: var(--sp-6);
  font-size: var(--fs-sm);
}

.lp-langs-label { color: var(--ink-mute); }

.lp-lang {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  color: var(--ink);
  font-weight: 500;
}

.lp-langs-more { color: var(--ink-mute); font-style: italic; }


/* ============================================================
   THE HEADLINE'S SWAPPING LANGUAGE
   ------------------------------------------------------------
   "French" takes turns with Spanish and German (landing.js part 6b). The
   word on show sizes the box like any word in a line; the leaving word is
   laid OVER the box (position: absolute) while it fades, so it never pushes
   anything. During a swap the script holds the box at the old width and
   slides it to the new one, so the line closes up around it.

   Each arriving letter springs up from below, tips, overshoots, and lands,
   flashing --swap-pop as it flies and settling to --swap-ink. The first
   word flashes violet and settles white; the italic one flashes terracotta
   and settles violet. Colour is only the flourish: the motion and the new
   word are what change.
   ============================================================ */

.lp-swap {
  position: relative;
  display: inline-block;         /* its baseline is the word's, so it sits in
                                    the line like the words around it */
  vertical-align: baseline;
  white-space: nowrap;
  color: var(--swap-ink);
  transition: width 0.55s cubic-bezier(.25, 1, .35, 1);   /* no overshoot: shrinking past the word would squeeze it into "by" */
}
.lp-swap[data-swap="first"]  { --swap-ink: var(--ink-strong);  --swap-pop: var(--accent-text); }
.lp-swap[data-swap="second"] { --swap-ink: var(--accent-text); --swap-pop: var(--warm); }

.lp-swap-word { display: none; }
.lp-swap-word.is-on { display: inline-block; }
.lp-swap-word.is-out {
  display: inline-block;
  position: absolute;
  left: 0;
  top: 0;
  animation: lp-swap-out 0.3s cubic-bezier(.2, .6, .4, 1) forwards;   /* starts leaving at once */
}

.lp-swap-letter {
  display: inline-block;
  animation: lp-swap-letter-in 0.75s cubic-bezier(.2, 1.3, .4, 1) both;
}

@keyframes lp-swap-out {
  to {
    opacity: 0;
    transform: translateY(-0.45em) scale(0.92);
    filter: blur(6px);
  }
}

@keyframes lp-swap-letter-in {
  0% {
    opacity: 0;
    transform: translateY(0.75em) rotate(12deg) scale(0.4);
    filter: blur(5px);
    color: var(--swap-pop);
  }
  55% {
    opacity: 1;
    transform: translateY(-0.12em) rotate(-4deg) scale(1.12);
    filter: blur(0);
    color: var(--swap-pop);
  }
  100% {
    transform: none;
    color: var(--swap-ink);
  }
}


/* ============================================================
   "SEE IT IN ACTION": THE FILM UNDER THE HERO
   ------------------------------------------------------------
   A loop that acts the reader out by itself (landing.js part 7): a pointer,
   a word lighting up, its meaning popping up, the English arriving, ＋ Save
   pressed, a card flying into the tray and turning over. It borrows the
   reader's LOOK rule for rule (SECTION 4's box, SECTION 5's highlighter and
   underline, SECTION 6's popup), so it shows what you get after signing up,
   in both themes, with no new colour. Nothing in it can be clicked
   (`pointer-events: none`), because it is a picture, not a control.

   Two layouts: the reader beside the captions and the tray on a wide
   screen, one above the other below 900px.
   ============================================================ */

.lp-try {
  max-width: var(--lp-max);
  margin: 0 auto;
  padding: 0 var(--lp-edge) clamp(var(--sp-7), 6vw, var(--sp-8));
}

.lp-try-head { margin-bottom: var(--sp-6); }

/* It sits straight under the hero with no padding above it, so it needs a
   longer stop than the sections below to clear the sticky bar comfortably. */
.lp-body section.lp-try { scroll-margin-top: 104px; }

.lp-try-layout {
  position: relative;          /* the pointer and the flying card move in here */
  display: grid;
  grid-template-columns: minmax(0, 1.7fr) minmax(0, 1fr);
  gap: var(--sp-5);
  align-items: start;
  pointer-events: none;
  user-select: none;
}

@media (max-width: 900px) {
  .lp-try-layout { grid-template-columns: minmax(0, 1fr); }
}

/* ---- The reader's frame ---- */
.lp-try-reader {
  position: relative;          /* the popup is placed against this box */
  min-width: 0;
  padding: var(--sp-5);
  background-color: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-3);
}

@media (max-width: 560px) {
  .lp-try-reader { padding: var(--sp-4); }
}

/* The translation box: the reader's own wash and edge (SECTION 4), and a
   FIXED height for the reader's reason (§6 B): growing would push the French
   down. Every English sentence here fits its two lines at 320px. */
.lp-try-trans {
  display: flex;
  align-items: center;
  height: calc(2.8em + 28px);
  padding: 14px var(--sp-4);
  margin-bottom: var(--sp-5);
  background-color: var(--accent-wash);
  border: 1px solid var(--accent-line);
  border-radius: var(--r-md);
  font-family: var(--trans-font);
  font-size: var(--fs-md);
  line-height: 1.4;
}

.lp-try-trans-text {
  flex: 1;
  min-width: 0;
  max-height: 2.8em;
  overflow: hidden;
  color: var(--ink);
}
.lp-try-trans-text.is-hint {
  color: var(--accent-text);
  font-style: italic;
}
/* A new sentence arrives with a short rise, so the eye notices it. */
.lp-try-trans-text.is-new { animation: lp-try-rise 0.4s var(--ease) both; }

@keyframes lp-try-rise {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

/* ---- The French ----
   The line height leaves room above each line for the popup, the reader's
   §6 A idea at this page's fixed sizes. */
.lp-try-text {
  margin: 0;
  font-family: var(--text-font);
  font-size: 1.3rem;
  line-height: calc(1.4em + 36px);
  color: var(--ink);
}

@media (max-width: 560px) {
  .lp-try-text { font-size: 1.15rem; }
}

.lp-try-word {
  border-radius: var(--r-xs);
  padding: 0 1px;
  white-space: nowrap;
  transition: background-color 0.2s var(--ease);
}

/* The word being looked at: the highlighter AND an underline, never colour
   alone (§6). */
.lp-try-word.is-lit {
  background-color: var(--mark);
  text-decoration-line: underline;
  text-decoration-color: var(--accent);
  text-decoration-thickness: 2px;
  text-underline-offset: 0.2em;
  text-decoration-skip-ink: none;
}

/* ---- The popup: SECTION 6's card, with its ＋ Save button ---- */
.lp-try-pop {
  position: absolute;
  z-index: 5;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 3px 4px 5px 10px;
  white-space: nowrap;
  font-family: var(--trans-font);
  font-size: var(--fs-md);
  line-height: 1.4;
  background-color: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  box-shadow: var(--shadow-2);
  transform-origin: 50% 100%;
}
.lp-try-pop[hidden] { display: none; }
.lp-try-pop.is-popping { animation: lp-try-pop-in 0.42s cubic-bezier(.2, 1.4, .4, 1) both; }

@keyframes lp-try-pop-in {
  from { opacity: 0; transform: translateY(6px) scale(0.6); }
  to   { opacity: 1; transform: none; }
}

.lp-try-pop-word {
  font-weight: 600;
  color: var(--ink);
}

.lp-try-save {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 3px;
  height: 24px;
  padding: 0 8px;
  font-family: var(--ui-font);
  font-size: 0.75rem;
  font-weight: 700;
  line-height: 1;
  color: var(--on-accent);
  background-color: var(--accent);
  border-radius: var(--r-xs);
  box-shadow: 0 2px 0 var(--accent-lip);
}
.lp-try-save.is-pressed { animation: lp-try-press 0.28s var(--ease); }
.lp-try-save.is-saved {
  color: var(--good);
  background-color: var(--surface-2);
  box-shadow: inset 0 0 0 1px var(--line);
}

@keyframes lp-try-press {
  40% { transform: translateY(2px) scale(0.92); box-shadow: 0 0 0 var(--accent-lip); }
}

/* ---- The pointer ----
   An arrow drawn like the rest of the icons (the ink colour, a rim in the
   surface colour so it shows on the highlighter too). Its tip is at the
   top-left of the drawing, which is the point the script moves. */
.lp-try-cursor {
  position: absolute;
  left: 0;
  top: 0;
  z-index: 6;
  width: 26px;
  height: 26px;
  margin: -3px 0 0 -5px;       /* puts the arrow's tip on the spot */
  fill: var(--ink-strong);
  stroke: var(--surface);
  stroke-width: 1.4;
  stroke-linejoin: round;
  opacity: 0;
  transition: transform 0.75s cubic-bezier(.45, .05, .25, 1), opacity 0.3s var(--ease);
}
.lp-try-cursor.is-shown  { opacity: 1; }
.lp-try-cursor.is-parked { transition: none; }

/* ---- The side: captions and the tray ---- */
.lp-try-side {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
}

.lp-try-steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

/* A caption. The one matching what the film is doing now is lifted onto a
   card and its number fills with violet; the others step back. */
.lp-try-step {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--r-md);
  border: 1px solid transparent;
  font-size: var(--fs-sm);
  line-height: 1.5;
  color: var(--ink-soft);
  transition: background-color 0.3s var(--ease), border-color 0.3s var(--ease),
              transform 0.3s var(--ease), box-shadow 0.3s var(--ease);
}
.lp-try-step strong { color: var(--ink-strong); font-weight: 600; }

.lp-try-step.is-active {
  background-color: var(--surface);
  border-color: var(--accent-line);
  box-shadow: var(--shadow-2);
  transform: translateX(4px);
}

.lp-try-step-mark {
  flex: none;
  display: grid;
  place-items: center;
  width: 26px; height: 26px;
  border-radius: 50%;
  background-color: var(--accent-soft);
  color: var(--accent-text);
  font-size: var(--fs-xs);
  font-weight: 700;
  transition: background-color 0.3s var(--ease), color 0.3s var(--ease), transform 0.3s var(--ease);
}
.lp-try-step.is-active .lp-try-step-mark {
  background-color: var(--accent);
  color: var(--on-accent);
  transform: scale(1.1);
}

.lp-try-tray {
  padding: var(--sp-4);
  min-height: 180px;           /* room for a row of cards, so the panel
                                  doesn't grow and shrink with the loop */
  background-color: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
}

.lp-try-tray-title {
  margin: 0 0 var(--sp-3) 0;
  font-weight: 600;
  color: var(--ink-strong);
}

.lp-try-count {
  display: inline-block;
  font-weight: 500;
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
}
.lp-try-count.is-bumped { animation: lp-try-bump 0.4s cubic-bezier(.2, 1.6, .4, 1); }

@keyframes lp-try-bump {
  40% { transform: scale(1.35); color: var(--accent-text); }
}

.lp-try-cards {
  position: relative;          /* a turned card lifts over the row */
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-2);
}

/* A saved word as a flashcard. It turns over by squeezing to nothing and
   opening out again with the other face showing. */
.lp-try-card {
  min-width: 0;
  display: flex;
  min-height: 106px;           /* tall enough for any card's back at 320px */
  padding: var(--sp-2);
  color: var(--ink);
  background-color: var(--btn-face);
  border: 1px solid var(--btn-edge);
  border-radius: var(--r-md);
  box-shadow: 0 3px 0 var(--btn-lip);
}
.lp-try-card.is-turning { animation: lp-try-turn 0.45s var(--ease); }
/* Turned over, a card lifts up and covers the whole row, so the back has
   room for its sentence WITHOUT the tray growing: on a phone the tray sits
   above the rest of the page, and a tray that grew and shrank with the loop
   would push the page down and back every few seconds. */
.lp-try-card.is-flipped {
  position: absolute;
  inset: 0;
  z-index: 1;
  overflow: hidden;
  box-shadow: 0 3px 0 var(--btn-lip), var(--shadow-3);
}

@keyframes lp-try-turn {
  0%   { transform: scaleX(0.05); }
  100% { transform: scaleX(1); }
}

.lp-try-card-face {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.lp-try-card-face[hidden] { display: none; }

.lp-try-card-front {
  align-items: center;
  justify-content: center;
  text-align: center;
}

.lp-try-card-word {
  font-family: var(--text-font);
  font-size: var(--fs-md);
  font-weight: 600;
  color: var(--ink-strong);
  overflow-wrap: anywhere;
}

@media (max-width: 420px) {
  .lp-try-card-word { font-size: var(--fs-sm); }
}

.lp-try-card-hint {
  font-size: var(--fs-xs);
  color: var(--ink-mute);
}

.lp-try-card-gloss {
  font-family: var(--trans-font);
  font-weight: 600;
  color: var(--accent-text);
}

.lp-try-card-sentence {
  font-family: var(--text-font);
  font-size: var(--fs-sm);
  line-height: 1.45;
  color: var(--ink);
}

/* The word that was saved, underlined in its sentence (app.js PART 7B). */
.lp-try-card-mark {
  text-decoration-line: underline;
  text-decoration-color: var(--accent);
  text-decoration-thickness: 2px;
  text-underline-offset: 0.18em;
}

.lp-try-card-english {
  font-family: var(--trans-font);
  font-size: var(--fs-xs);
  line-height: 1.45;
  color: var(--ink-soft);
}


/* ============================================================
   THE PROOF STRIP
   ============================================================ */

.lp-proof {
  border-top: 1px solid var(--line-soft);
  border-bottom: 1px solid var(--line-soft);
  background-color: var(--surface-2);
}

.lp-proof-inner {
  max-width: var(--lp-max);
  margin: 0 auto;
  padding: var(--sp-6) var(--lp-edge);
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--sp-5);
}

.lp-proof-item { min-width: 0; text-align: center; }

.lp-proof-number {
  display: block;
  font-family: var(--display-font);
  font-size: var(--fs-2xl);
  font-weight: 600;
  color: var(--accent-text);
  font-variant-numeric: tabular-nums;
}

.lp-proof-caption {
  display: block;
  margin-top: var(--sp-1);
  font-size: var(--fs-sm);
  color: var(--ink-soft);
  line-height: 1.4;
}

@media (max-width: 760px) {
  .lp-proof-inner { grid-template-columns: repeat(2, 1fr); }
}


/* ============================================================
   A SECTION'S HEADING — used five times
   ============================================================ */

.lp-section-head {
  max-width: 40rem;
  margin: 0 auto var(--sp-7);
  text-align: center;
}

.lp-section-title {
  margin: 0 0 var(--sp-3) 0;
  font-family: var(--display-font);
  font-size: clamp(1.75rem, 1.2rem + 2vw, 2.5rem);
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.02em;
  color: var(--ink-strong);
}

.lp-section-sub {
  margin: 0;
  font-size: var(--fs-lg);
  color: var(--ink-soft);
  line-height: 1.55;
}


/* ============================================================
   THE FOUR FEATURE CARDS
   ============================================================ */

.lp-features,
.lp-steps,
.lp-pricing,
.lp-faq {
  max-width: var(--lp-max);
  margin: 0 auto;
  padding: clamp(var(--sp-7), 6vw, var(--sp-8)) var(--lp-edge);
}

.lp-cards {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--sp-5);
}

@media (max-width: 800px) {
  .lp-cards { grid-template-columns: 1fr; }
}

.lp-card {
  /* A GRID ITEM WILL NOT SHRINK BELOW ITS CONTENTS UNLESS YOU SAY SO.
     `min-width: auto` is the default for a grid item, so the widest
     unbreakable thing inside a card — the demo sentence in the second one,
     which is deliberately `nowrap` — becomes a floor the whole column has to
     clear. On a 375px phone that made every card 481px wide and the page
     scroll sideways by 122. This one line, plus the same on the demo text
     below, is the whole fix. (The same trap is set for every grid and flex
     item on this page, which is why the ones that could hit it all say it.) */
  min-width: 0;

  background-color: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: var(--sp-5);
  box-shadow: var(--shadow-2);
  transition: transform 0.16s var(--ease), box-shadow 0.16s var(--ease);
}
.lp-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-3);
}

/* The little mock-up at the top of each card. A fixed height so the four
   cards' titles line up across the row, and `overflow: hidden` so a demo
   that wants to be wider than the card is cropped rather than escaping it. */
.lp-card-visual {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--sp-4);
  height: 168px;
  margin-bottom: var(--sp-5);
  padding: var(--sp-4);
  overflow: hidden;
  border-radius: var(--r-md);
  background-color: var(--surface-sunk);
  border: 1px solid var(--line-soft);
}

.lp-card-title {
  margin: 0 0 var(--sp-2) 0;
  font-size: var(--fs-xl);
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink-strong);
}

.lp-card-body {
  margin: 0;
  color: var(--ink-soft);
  line-height: 1.6;
}
.lp-card-body em { color: var(--ink); font-style: italic; }


/* ---- Demo 1: a hovered word with its popup ---- */

.lp-demo-text {
  font-family: var(--text-font);
  font-size: 1.15rem;
  line-height: 2.6;            /* room above the line for the popup, which is
                                  the same problem SECTION 5 solves properly */
  color: var(--ink);
  text-align: center;
}

.lp-demo-word {
  position: relative;
  background-color: var(--mark);
  border-radius: var(--r-xs);
  padding: 1px 3px;
}

.lp-demo-pop {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 6px);
  transform: translateX(-50%);
  white-space: nowrap;
  padding: 4px 10px;
  border-radius: var(--r-sm);
  background-color: var(--surface);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-2);
  font-family: var(--trans-font);
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--accent-text);
}


/* ---- Demo 2: the translation bar and a spoken word ---- */

.lp-demo-bar {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: 8px 10px;
  border-radius: var(--r-md);
  background-color: var(--surface);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-1);
}

.lp-demo-play {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border-radius: var(--r-pill);
  background-color: var(--accent);
  color: var(--on-accent);
  font-size: var(--fs-xs);
  font-weight: 600;
}
.lp-demo-play svg {
  width: 12px; height: 12px;
  fill: currentColor;
}

.lp-demo-bar-text {
  font-family: var(--trans-font);
  font-size: var(--fs-sm);
  color: var(--ink-soft);
  min-width: 0;                /* the "…" below only happens if it may shrink */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.lp-demo-text-spoken { line-height: 1.8; }

/* The word being spoken: blue AND bold AND dotted underneath — three signals,
   because colour alone is never enough (CLAUDE.md §6). */
.lp-demo-speaking {
  position: relative;
  color: var(--speak);
  font-weight: 600;
}
.lp-demo-speaking::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -6px;
  width: 5px; height: 5px;
  margin-left: -2.5px;
  border-radius: 50%;
  background-color: var(--speak-dot);
}


/* ---- Demo 3: a flashcard and its four grade buttons ---- */

.lp-demo-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: var(--sp-3);
  border-radius: var(--r-md);
  background-color: var(--surface);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-1);
}

.lp-demo-card-word {
  font-family: var(--text-font);
  font-size: var(--fs-lg);
  font-weight: 600;
  color: var(--ink-strong);
}

.lp-demo-card-gloss {
  font-family: var(--trans-font);
  font-size: var(--fs-sm);
  color: var(--ink-soft);
}

.lp-demo-grades {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 6px;
}

.lp-demo-grade {
  min-width: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 5px 2px;
  border-radius: var(--r-sm);
  border: 1px solid var(--line);
  background-color: var(--surface);
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--ink-soft);
}
.lp-demo-grade em {
  font-style: normal;
  font-weight: 500;
  color: var(--ink-mute);
  font-variant-numeric: tabular-nums;
}

/* The four keep their own colours at rest, exactly as the real grade buttons
   do on the study screen (SECTION 13). */
.lp-demo-grade-1 { border-color: var(--danger-line);  color: var(--danger); }
.lp-demo-grade-2 { border-color: var(--caution-line); color: var(--caution); }
.lp-demo-grade-3 { border-color: var(--good-line);    color: var(--good); }
.lp-demo-grade-4 { border-color: var(--accent-line);  color: var(--accent-text); }


/* ---- Demo 4: story tiles, plus one waiting to be added ---- */

.lp-demo-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--sp-3);
}

/* The same recipe as a story card's placeholder cover (SECTION 16): one hue
   is set in the markup and the stylesheet turns it into a background and a
   text colour, which is what lets the same tile be pale on cream and deep in
   the dark. */
.lp-demo-tile {
  min-width: 0;
  display: grid;
  place-items: center;
  aspect-ratio: 3 / 4;
  border-radius: var(--r-sm);
  font-family: var(--display-font);
  font-size: var(--fs-xl);
  font-weight: 600;
  background-image: linear-gradient(150deg,
      hsl(var(--cover-hue, 210) var(--cover-bg-s) calc(var(--cover-bg-l) + 5%)),
      hsl(calc(var(--cover-hue, 210) + 18) var(--cover-bg-s) calc(var(--cover-bg-l) - 6%)));
  color: hsl(var(--cover-hue, 210) var(--cover-fg-s) var(--cover-fg-l));
}

/* The empty slot at the end: a dashed outline says "this one is not a story
   yet, it is a space for one". */
.lp-demo-tile-new {
  background-image: none;
  background-color: var(--surface);
  border: 2px dashed var(--accent-line);
  color: var(--accent-text);
}


/* ============================================================
   THE THREE STEPS
   ============================================================ */

.lp-steps-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-5);
}

@media (max-width: 800px) {
  .lp-steps-list { grid-template-columns: 1fr; }
}

.lp-step {
  min-width: 0;
  padding: var(--sp-5);
  border-radius: var(--r-lg);
  background-color: var(--surface-2);
  border: 1px solid var(--line-soft);
}

.lp-step-number {
  display: grid;
  place-items: center;
  width: 34px; height: 34px;
  margin-bottom: var(--sp-3);
  border-radius: 50%;
  background-color: var(--accent);
  color: var(--on-accent);
  font-weight: 700;
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
}

.lp-step-title {
  margin: 0 0 var(--sp-2) 0;
  font-size: var(--fs-lg);
  font-weight: 600;
  color: var(--ink-strong);
}

.lp-step-body {
  margin: 0;
  color: var(--ink-soft);
  line-height: 1.6;
}


/* ============================================================
   PRICING
   ------------------------------------------------------------
   The cards are built by landing.js from billing/plans.js, so everything
   here has to work for any number of plans — which is why the grid is
   written with auto-fit rather than a fixed three columns.
   ============================================================ */

.lp-plans {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: var(--sp-5);
  align-items: start;
}

.lp-plan {
  min-width: 0;
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  padding: var(--sp-6) var(--sp-5) var(--sp-5);
  border-radius: var(--r-lg);
  background-color: var(--surface);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-2);
}

/* The one that is meant to be picked: the accent rim, a tinted ground, and a
   deeper shadow. Three signals again, not just a coloured border. */
.lp-plan-featured {
  border-color: var(--accent);
  background-color: var(--accent-wash);
  box-shadow: var(--shadow-3);
}

.lp-plan-badge {
  position: absolute;
  top: -11px;
  left: var(--sp-5);
  padding: 3px 12px;
  border-radius: var(--r-pill);
  background-color: var(--accent);
  color: var(--on-accent);
  font-size: var(--fs-xs);
  font-weight: 700;
}

.lp-plan-name {
  margin: 0;
  font-size: var(--fs-lg);
  font-weight: 600;
  color: var(--ink-strong);
}

.lp-plan-price {
  margin: 0;
  display: flex;
  align-items: baseline;
  gap: 3px;
}

.lp-plan-amount {
  font-family: var(--display-font);
  font-size: clamp(2.1rem, 1.6rem + 1.6vw, 2.6rem);
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--ink-strong);
  font-variant-numeric: tabular-nums;
}

.lp-plan-interval {
  font-size: var(--fs-md);
  color: var(--ink-mute);
}

.lp-plan-tagline {
  margin: 0;
  min-height: 2.9em;           /* keeps the buttons on one line across the row
                                  whether a tagline runs to one line or two */
  color: var(--ink-soft);
  font-size: var(--fs-sm);
  line-height: 1.45;
}

.lp-plan-features {
  list-style: none;
  margin: var(--sp-2) 0 0 0;
  padding: 0;
  display: grid;
  gap: var(--sp-2);
}

.lp-plan-feature {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  font-size: var(--fs-sm);
  color: var(--ink);
  line-height: 1.45;
}

.lp-tick {
  flex: none;
  width: 16px; height: 16px;
  margin-top: 2px;
  fill: none;
  stroke: var(--good);
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.lp-plan-note {
  margin: auto 0 0 0;          /* pushed to the bottom of the card */
  padding-top: var(--sp-3);
  font-size: var(--fs-xs);
  color: var(--ink-mute);
}

.lp-plans-foot {
  margin: var(--sp-5) 0 0 0;
  text-align: center;
  font-size: var(--fs-sm);
  color: var(--ink-mute);
}


/* ============================================================
   QUESTIONS
   ============================================================ */

.lp-faq-list {
  max-width: 44rem;
  margin: 0 auto;
  display: grid;
  gap: var(--sp-3);
}

.lp-faq-item {
  background-color: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: var(--sp-4) var(--sp-5);
}

.lp-faq-item summary {
  cursor: pointer;
  font-weight: 600;
  color: var(--ink-strong);
  list-style: none;            /* the default triangle, replaced below */
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
}
.lp-faq-item summary::-webkit-details-marker { display: none; }

/* Our own marker: a + that becomes a − when the answer is open. Drawn with
   text rather than an icon because it is one character wide and rotating a
   drawn plus is the same thing with more parts. */
.lp-faq-item summary::after {
  content: "+";
  flex: none;
  font-size: var(--fs-xl);
  line-height: 1;
  color: var(--accent-text);
  transition: transform 0.16s var(--ease);
}
.lp-faq-item[open] summary::after {
  content: "−";
}

.lp-faq-item p {
  margin: var(--sp-3) 0 0 0;
  color: var(--ink-soft);
  line-height: 1.65;
}


/* ============================================================
   THE FINAL PANEL
   ============================================================ */

.lp-final {
  max-width: var(--lp-max);
  margin: 0 auto;
  padding: 0 var(--lp-edge) clamp(var(--sp-7), 6vw, var(--sp-8));
}

.lp-final-panel {
  text-align: center;
  padding: clamp(var(--sp-6), 5vw, var(--sp-8)) var(--sp-5);
  border-radius: var(--r-xl);
  background-color: var(--panel-loud);
  color: var(--panel-loud-ink);
}

/* The mark again, larger and the other way round: a white tile with a violet
   L, because a violet tile on the violet panel would disappear. */
.lp-final-mark {
  --mark-size: 52px;
  --mark-tile: var(--brand-ink);
  --mark-ink: var(--brand-tile);
  --mark-underline: var(--brand-underline-on-light);
  display: inline-grid;
  margin-bottom: var(--sp-4);
}

.lp-final-title {
  margin: 0 0 var(--sp-2) 0;
  font-family: var(--display-font);
  font-size: clamp(1.8rem, 1.2rem + 2.2vw, 2.6rem);
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--panel-loud-ink);
}

.lp-final-sub {
  margin: 0 0 var(--sp-5) 0;
  color: var(--panel-loud-mute);
}

.lp-final-note { color: var(--panel-loud-mute); }

/* The email pill, standing on a filled panel: it keeps its white face (so it
   still reads as a box you type in) and loses its rim, which would be a line
   of the wrong colour on this ground. */
.lp-email-onaccent {
  margin: 0 auto;
  border-color: transparent;
  box-shadow: var(--shadow-3);
}


/* ============================================================
   THE FOOTER
   ============================================================ */

.lp-footer {
  border-top: 1px solid var(--line-soft);
  background-color: var(--surface-2);
}

.lp-footer-inner {
  max-width: var(--lp-max);
  margin: 0 auto;
  padding: var(--sp-6) var(--lp-edge) calc(var(--sp-8) + var(--sp-6));
  text-align: center;
}

.lp-footer-brand {
  margin: 0 0 var(--sp-3) 0;
  font-family: var(--display-font);
  font-size: var(--fs-lg);
  font-weight: 600;
  color: var(--ink-strong);
}

.lp-footer-links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--sp-3) var(--sp-5);
  margin-bottom: var(--sp-4);
}

.lp-footer-links a {
  color: var(--ink-soft);
  text-decoration: none;
  font-size: var(--fs-sm);
}
.lp-footer-links a:hover { color: var(--accent-text); }

.lp-footer-fine {
  margin: 0;
  font-size: var(--fs-xs);
  color: var(--ink-mute);
  line-height: 1.6;
}


/* ============================================================
   THE BAR THAT FOLLOWS YOU UP THE PAGE
   ============================================================ */

.lp-sticky {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 50;
  background-color: var(--panel-loud);
  border-top: 1px solid var(--panel-loud);
  box-shadow: var(--shadow-3);
}

.lp-sticky-inner {
  max-width: var(--lp-max);
  margin: 0 auto;
  padding: var(--sp-3) var(--lp-edge);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-5);
}

.lp-sticky-title {
  margin: 0;
  font-weight: 600;
  color: var(--panel-loud-ink);
}

.lp-sticky-sub {
  margin: 0;
  font-size: var(--fs-xs);
  color: var(--panel-loud-mute);
}

.lp-sticky .lp-email { max-width: 24rem; flex: 1; margin: 0; }

/* On a phone the two lines of words are what has to go: the box is the point
   of the bar, and a bar tall enough for both eats a third of the screen. */
@media (max-width: 620px) {
  .lp-sticky-words { display: none; }
  .lp-sticky .lp-email { max-width: none; }
}


/* ------------------------------------------------------------
   SECTION 20 — WHAT "CREATE A STORY" ADDED
   ------------------------------------------------------------
   The Create screen itself is SECTION 16; this is only the handful of things
   that arrived with the translator and the recorder (app.js PARTs 22 and 23),
   kept together at the bottom of the file the way SECTION 19 keeps the
   landing page's rules together.

   Same rule as everywhere below SECTION 2: NO COLOUR LITERALS. Every colour
   here is a token from SECTION 1, so all of it has a dark version already.
   ------------------------------------------------------------ */

/* The two buttons under the story grid — Flashcards, and the way in here.
   They sit side by side on anything wider than a small phone and stack when
   there isn't room, which is the whole reason this is a flex row rather than
   two loose buttons with margins. */
.picker-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--sp-3);
  margin-top: var(--sp-6);
}

/* .flashcards-button carries the margin that used to space it from the grid;
   the row above owns that now, so both buttons start level. */
.picker-actions .flashcards-button {
  margin-top: 0;
}

/* Quieter than Flashcards on purpose: reading is what you came for, and
   making a story is the other thing. It keeps the standard face and lip, so
   it is plainly a button — just not the one asking to be pressed first. */
.create-button {
  padding: 13px 30px;
  font-weight: 600;
}

/* ---- The progress line ----
   What the app is doing right now: splitting, translating sentence 34 of 141,
   recording piece 2 of 5, saving. It is deliberately NOT the status line
   underneath (.options-status), which says how it ENDED — a line that means
   "still going" and a line that means "done" should not be the same line, or
   a finished message reads as a stuck one. */
.create-progress {
  display: flex;
  flex-wrap: wrap;      /* the Create screen's bar and hint take a row each */
  align-items: center;
  gap: 10px;
  margin: var(--sp-4) 0 0;
  font-family: var(--ui-font);
  font-size: var(--fs-sm);
  color: var(--ink-soft);
}

/* A small turning ring, so a long translation looks alive rather than hung.
   prefers-reduced-motion switches it off in SECTION 2 along with everything
   else that moves; a still ring is still a "working" mark. */
.create-spinner {
  flex: none;
  width: 14px;
  height: 14px;
  border: 2px solid var(--accent-line);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: create-spin 0.8s linear infinite;
}

@keyframes create-spin {
  to { transform: rotate(360deg); }
}

/* ---- The progress rows and bar (milestones 72 and 73) ----
   On the Create screen the progress is a list of ROWS — one per step
   (English, Recording, Saving), each with a mark, its name, what it is doing
   and its own clock — then a bar for the whole job, then the "keep this page
   open · 0:42 so far" hint. The rows stay after the job ends, so they say how
   each step went. (The backend's story editor uses the same .create-progress
   with just a ring and a line of words, which is why the rows are opt-in
   classes rather than a change to .create-progress itself.)
   No new colours. A row's state is a mark AND words (§6: never colour alone);
   the colours on the marks are the signal family's own. */
.create-steps {
  flex: 1 1 100%;
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--sp-2);
  min-width: 0;
  text-align: start;
}

.create-step {
  display: grid;
  grid-template-columns: 18px 84px minmax(0, 1fr) auto;
  gap: 10px;
  align-items: baseline;
  min-width: 0;
}

.create-step-mark {
  display: flex;
  justify-content: center;
  font-weight: 700;
  color: var(--ink-mute);
}
.create-step[data-state="done"] .create-step-mark    { color: var(--good); }
.create-step[data-state="partial"] .create-step-mark { color: var(--caution); }
.create-step[data-state="failed"] .create-step-mark  { color: var(--danger); }

.create-step-name {
  font-weight: 600;
  color: var(--ink);
}

.create-step-text {
  min-width: 0;
  overflow-wrap: anywhere;
}

.create-step-time {
  color: var(--ink-mute);
  font-variant-numeric: tabular-nums;
}

.progress-bar {
  flex: 1 1 100%;
  display: block;
  height: 10px;
  border-radius: var(--r-pill);
  background: var(--surface-sunk);
  border: 1px solid var(--line);
  overflow: hidden;
}

.progress-fill {
  display: block;
  height: 100%;
  width: 0;
  background-color: var(--accent);
  background-image: linear-gradient(135deg,
    var(--accent-hover) 25%, transparent 25%,
    transparent 50%, var(--accent-hover) 50%,
    var(--accent-hover) 75%, transparent 75%);
  background-size: 16px 16px;
  transition: width 0.4s var(--ease);
  animation: progress-stripes 1s linear infinite;
}

@keyframes progress-stripes {
  to { background-position: 16px 0; }
}

/* A finished job keeps its rows and its bar, but nothing should look busy. */
.create-progress.is-finished .progress-fill {
  animation: none;
}

.create-progress-hint {
  flex: 1 1 100%;
  min-width: 0;
  text-align: start;    /* .picker centres its text; this belongs with the line above */
  font-size: var(--fs-xs);
  color: var(--ink-mute);
}

@media (prefers-reduced-motion: reduce) {
  .create-spinner { animation: none; }
  .progress-fill { animation: none; transition: none; }
}

/* ---- Your own recording (milestone 73) ----
   A small, folded-away option at the bottom of the Create form: one line of
   accent text that opens to a sentence and a file box. */
.create-own-audio {
  margin: var(--sp-4) 0 0;
  text-align: start;
  font-family: var(--ui-font);
  font-size: var(--fs-sm);
  color: var(--ink-soft);
}

.create-own-audio summary {
  width: fit-content;
  cursor: pointer;
  color: var(--accent-text);
}

.create-own-audio[open] summary {
  margin-bottom: var(--sp-2);
}


/* ------------------------------------------------------------
   SECTION 21 — THE BACKEND SCREEN
   ------------------------------------------------------------
   Almost all of it is SECTION 14's ".options-*" classes and SECTION 16's story
   list, so there is very little here: the warning at the top, the editor's
   sub-heading, and slightly roomier boxes for an API address.

   Same rule as everywhere below SECTION 2: NO COLOUR LITERALS.
   ------------------------------------------------------------ */

/* "Everyone can see this screen." Warm rather than red: it is a fact about how
   the app is configured, not a failure — and the --warm family is exactly the
   "this is worth knowing" voice (SECTION 1). */
.admin-warning {
  margin: 0 0 var(--sp-5);
  padding: 12px 14px;
  font-family: var(--ui-font);
  font-size: var(--fs-sm);
  line-height: 1.55;
  color: var(--warm-text);
  background-color: var(--warm-wash);
  border: 1px solid var(--warm-line);
  border-radius: var(--r-md);
}

/* The story editor's heading — smaller than a section title, because it is
   inside one. */
.options-subtitle {
  margin: var(--sp-5) 0 var(--sp-3);
  font-family: var(--ui-font);
  font-size: var(--fs-md);
  font-weight: 600;
  color: var(--ink-strong);
}

/* An API address is longer than a story title, and a box that shows half of
   one is a box you cannot check. */
#screen-admin .options-input-wide {
  width: 260px;
  max-width: 100%;
}

/* The story rows carry three or four buttons rather than one, so they need to
   be allowed to wrap on a phone instead of pushing the row sideways. */
#admin-story-list li {
  flex-wrap: wrap;
}


/* ------------------------------------------------------------
   The story limit on the Create screen (milestone 71)
   ------------------------------------------------------------
   "You have 12 French stories of your own, out of the 3,000…" is ordinary
   help text. Once that shelf is FULL the same line says so, and it is set in
   the danger ink AND made bold — never colour alone (§6): somebody who can't
   tell the red from the grey still sees that this line has changed its tone.
   --danger is the ink the red status lines already use, so it has its
   measured contrast in both themes. */
.create-limit.is-full {
  color: var(--danger);
  font-weight: 600;
}
