/* ═══════════════════════════════════════════════════════════════════════
   INTUITY — SHELL  ·  v10  (one accent, two grounds)
   ═══════════════════════════════════════════════════════════════════════

   ONE file, ONE definition per class. The previous file carried the
   navigation vocabulary twice: §5–6 defined .mode-btn and .vtab, and a
   block appended at the end defined them again. Later rules of equal
   specificity won, which is why the active tab kept its beige fill while
   the appended comment insisted nothing but .seg may be filled, and why
   the phone-sized mode row stopped shrinking. Every such pair is merged
   below and the losing copy is gone.

   WHAT CHANGED FROM v9

   1  ONE ACCENT, AND IT IS A RAMP. #F26430 is gone from the file. It was
      never an app colour: it was born on 26 August in tenses-defuse.html,
      where the commit that introduced it called it "the game's signal
      colour", and it escaped into the shell from there. What it replaced
      — the caramel family — is back, as three steps of one pigment:

        --accent      #DD8E58  L* 66  the MARK  underline, fill, dot
        --clay        #B4653A  L* 51  the WEIGHT  3.83:1, large type, borders
        --accent-ink  #9A5A2C  L* 45  the WORD  4.81:1, body text

      5.4° of hue between the extremes, so it reads as one colour at three
      densities rather than three colours competing. THE RULE, and the
      thing that stops this drifting again: pick by WHAT THE ELEMENT IS,
      never by how it looks on the screen you happen to be on. A mark takes
      --accent whether or not it feels light there. #B4653A is your landing
      page's own --clay, so the front door and every interior page are now
      on the same signal colour.

      #DD8E58 measures 2.30:1 on --screen. That is correct for a rectangle
      and wrong for a word, which is the same argument the poster block
      makes for --caramel at 2.7:1. Marks have no contrast floor; type has
      one, and takes --accent-ink.

   2  TWO GROUNDS, ONE HUE LINE. About 75 live pages painted a dark ground
      in six near-identical values — #0d0d0d, #0c0c0c, #0a0a0a, #06060f and
      two gradients — all at h≈290°, cool, against a product whose every
      other surface sits at h 85–90°. That is why the games felt like a
      different app: not that they were dark, but that they were a
      different HUE FAMILY. §2 now carries a dark ladder anchored on
      #14110E, which is the black your landing page already uses, at h 72°.
      A game declares itself with <body data-ground="dark"> and inherits
      the whole stack. On that ground --accent measures 7.24:1, better than
      the orange it replaces managed at 5.95:1.

   3  THE SURFACE LADDER IS NAMED. Seven light steps, seven dark ones,
      each the same role at the same index, so a component can be written
      once and work on either ground.

   4  SPEAKING'S THEME IS RETIRED. It existed only to set the caramel
      accent, which is now the default. Its taller header and near-black
      Writing ink remain. The eight Speaking pages that declare --accent
      locally are harmless — they now name the value they inherit — but
      they are dead weight; delete on next visit.

   5  --screen IS UNCHANGED at #F3F1ED, and is not up for revision. It is
      the crown: ΔE 1.4 from Pantone's 2026 white, the ground 85 pages
      already inherit, and the surface every contrast figure in this file
      was measured against.

   WHAT BELONGS HERE
     tokens · fonts · header · mode row · tabs · segmented control · tools ·
     dots · submit · the tile family · the close control · sheets · the
     modal · the gate · the pendulum · the poster · view transitions
   WHAT DOES NOT
     anything one mode owns — the bomb dial, the forge dial, the tile tray,
     the Explore cards, .rd-* (Reading is namespaced and needs nothing from
     this file but tokens, which is the pattern the rest should copy).
   ═══════════════════════════════════════════════════════════════════════ */

/* ── FONTS ───────────────────────────────────────────────────────────────
   An @import is a round trip: the browser cannot start the font until this
   file has arrived. A <link rel="preconnect"> plus <link> in the page is
   faster and every page SHOULD carry them. This line is here because the
   shell is the one file all 83 pages already load, so it fixes the 28 that
   render in system-ui today with a single edit rather than 28. Delete it
   once the <link> tags are in place — including in gapfill.css, which
   duplicates it. Anton is loaded because .poster-head and .modal-title use
   it and nothing else does. */
@import url("https://fonts.googleapis.com/css2?family=Geist:wght@400..900&family=Geist+Mono:wght@400..600&family=Space+Grotesk:wght@700&family=Anton&family=Newsreader:ital,opsz,wght@0,6..72,400..600;1,6..72,400..500&display=swap");

/* ═══════════════════════════════════════════════════════════════════════
   1 · TOKENS
   ═══════════════════════════════════════════════════════════════════════ */
:root{

  /* ── GEOMETRY ──────────────────────────────────────────────────────────
     v11 · THE TAB IS A CAPSULE. iOS 26 made the capsule the default shape
     of a control again, and the students hold iPhones. So every tab group
     — the segmented control, and every page's home-made copy of it — is a
     capsule track holding a capsule knob, the same on all pages. Square
     stays for everything that holds content: cards, fields, tiles, chips.

     --r-tab:999px   every tab track and every tab in it.

     --r-sheet:0     sheets, modals, cards, gates. Edge to edge, like paper.
     --r-field:3px   the answer slot and the option chip: "container", not
                     "capsule".
     --r-chip:4px    tabs, buttons, the segmented control, submit.
     --r-card:0      anything holding two lines.

     KEPT ROUND, and the only exception: the pendulum dial and the progress
     dots. They are objects, not containers, and set their own 50%. */
  --r-tab:999px;
  /* THE LAYERS · screen → container → focus card → tooltip. Each is
     narrower than the one beneath it, on every page:
       container   the box under the header — the page's working width
       focus card  a modal brought on top (gallery, phrase bank, sheets)
       tooltip     a note on hover or tap */
  --w-focus:35rem;
  --w-tip:17.5rem;
  --r-sheet:0;
  --r-card:0;
  --r-field:3px;
  --r-chip:4px;

  /* ── TYPE ──────────────────────────────────────────────────────────────
     One superfamily across display, body and mono, so the numeral in a
     tally, the label above it and the sentence below it are cut to the
     same skeleton. Space Grotesk + DM Sans + DM Mono was three unrelated
     families doing three jobs. */
  --f-display:'Geist', system-ui, sans-serif;
  --f-body:'Geist', system-ui, sans-serif;
  --f-mono:'Geist Mono', ui-monospace, monospace;
  /* DEPRECATED alias, declared in vocab.css before the shell had a mono
     token. Retire once colloc.css is migrated. */
  --f-mono2:var(--f-mono);

  --track-display:-.022em;  /* 900 weight needs negative tracking */
  --track-label:.08em;      /* the uppercase micro-labels go the other way */

  /* ── WIDTH ─────────────────────────────────────────────────────────────
     Two widths, and every surface uses one. Nothing is min(94vw,…) any
     more: the viewport clamp belongs in the padding, or no two pages share
     a margin. */
  --w-read:45rem;                     /* 720px — roughly 70 characters */
  --w-sheet:60rem;                    /* 960px — the frame a column sits in */
  --gutter:1rem;
  --gutter-phone:.75rem;

  /* ── SURFACES: ONE HUE LINE, SEVEN DEPTHS ──────────────────────────────
     The bug was never the beige, it was --card:#FFFFFF — the one object on
     the page off the family's hue line. The eye reads that absence of
     warmth, against a warm ground, as a cool cast.

       --card    #FAF8F5   L* 97.65  C* 1.67
       --screen  #F3F1ED   L* 95.19  C* 2.15   Δ2.46
       --sunk    #EDE9E2   L* 92.47  C* 3.86   Δ2.72
       --tile-bg #E8E1D6   L* 89.79  C* 6.23   Δ2.68
       --slot-bg #DED7CA   L* 86.20  C* 7.25   Δ3.59
       --press   #DAD1C2   L* 84.17  C* 8.55   Δ2.03
       --rule    #BCB1A0   L* 72.68  C* 10.15  Δ11.50 — a deliberate cliff:
                 a rule is a line, not another depth. */
  --card:#FAF8F5;
  --screen:#F3F1ED;
  --scrim:rgba(243,241,237,.94);
  --sunk:#EDE9E2;
  /* NEW. Used by .seg and .go in v8 and never declared, so both silently
     took their hard-coded fallback. It is the page's ground, by name. */
  --page-bg:var(--screen);

  --tile-bg:#E8E1D6;
  --tile-bg-press:#DAD1C2;
  --tile-ink:#1C1A18;                 /* 13.4:1 on the tile */
  --slot-bg:#DED7CA;                  /* a hollow reads DEEPER than a tile */
  --tile-ok-bg:#D2E3D6;  --tile-ok-ink:#1F4230;
  --tile-no-bg:#F3D9D2;  --tile-no-ink:#6E2416;

  --ink:#232C31;
  --paper:#4A433D;
  --sentence:#4A433D;
  --dim:#5C5852;
  --faint:#756B6E;                    /* 4.56:1 on --screen */
  --rule:#BCB1A0;
  --edge:#786E5F;

  /* THE ACCENT RAMP — one pigment, three densities, three jobs.
     Never pick between these by eye. Pick by what the element is:
       a MARK  (underline, fill, dot, kicker block) → --accent
       a WEIGHT (large type, a border, a filled state) → --clay
       a WORD  (any accent-coloured sentence) → --accent-ink          */
  --accent:#DD8E58; --accent-rgb:221,142,88;   /* 2.30:1 — marks only */
  --clay:#B4653A; --clay-rgb:180,101,58;       /* 3.83:1 — the landing page's own */
  --accent-ink:#9A5A2C;                        /* 4.81:1 — type       */
  --target:#9A5A2C;
  --typed:#3A3E4A;
  --ok:#4A6B5C;
  --wrong:#A23B2C; --wrong-rgb:162,59,44;

  /* PROMOTED. Ten files declared --sage:#708A81 locally and uoe.css
     declares #527065 for a button that has to carry a white label at
     4.9:1. Both are real, so both are named: the lighter one is the mark,
     the darker one is the one you may set type on. Use-of-English's
     .modal-btn-primary and .bar .go can stop hard-coding it. */
  --sage:#708A81;
  --sage-ink:#527065;

  --header-h:150px;

  --mark-sweep:#708A81;
  --mark-rest:#4A433D;
  --mark-core:#201E1C;
  --mark-ink:#E5D1B8;
  --mark-pct:100%;
}

/* ═══════════════════════════════════════════════════════════════════════
   2 · SECTION THEMES
   Thirty pages opened a :root block to move two or three values, which is
   how Speaking ended up on a different orange from the rest of the product
   with no file saying so. A section states which section it is, once, on
   the body tag, and the shell holds what that means.

     <body data-section="speaking">   caramel accent, taller header
     <body data-section="writing">    near-black ink, hairline rule

   A page needing a value no theme has still overrides the token — that is
   what tokens are for. tenses-defuse and tenses-forge do exactly that with
   the --mark-* set and they are right to.
   ═══════════════════════════════════════════════════════════════════════ */
/* Speaking no longer needs a theme: its caramel IS the accent now, and was
   first — 24 August, two days before the game orange existed. The eight
   pages still declaring it locally are naming what they inherit. */
body[data-section="writing"]{
  /* Nineteen Writing pages set --ink:#111 and six set a near-transparent
     rule. Both read correctly on Writing's whiter working surfaces. */
  --ink:#111111;
  --rule:rgba(0,0,0,.07);
}
body[data-section="games"]{
  /* Defuse and Forge need the extra band for the dial. */
  --header-h:168px;
}

/* ── THE DARK GROUND ─────────────────────────────────────────────────────
   <body data-ground="dark">  — for the games, and ONLY the games: the
   timed, scored, single-object modes (defuse, forge, pulse, rush, mill,
   duel, the phrasal-verbs games). A writer or a builder is a work surface,
   not a game; a student moving from a cream essay-question into a
   near-black essay-writer is crossing between two products mid-task.

   Every step below is the same ROLE at the same INDEX as its light
   counterpart, so a component written against --card or --sunk works on
   either ground with no override. That is the whole point of the mirror:
   one .sheet-card rule, two grounds.

   Anchored on #14110E — the black already on your landing page, h 71.6°,
   within a degree of the surface family. The six darks this replaces sat
   at h≈290°: cool, and the real reason the games felt foreign.
   ───────────────────────────────────────────────────────────────────── */
body[data-ground="dark"]{
  --card:#26221E;        /* L* 13.5  — the step ABOVE the ground   */
  --screen:#1C1916;      /* L*  9.0  — the ground itself           */
  --page-bg:#1C1916;
  --scrim:rgba(28,25,22,.94);
  --sunk:#161310;        /* L*  6.0  — a box RECEDES, as on light  */
  --void:#14110E;        /* L*  5.3  — the landing page's black    */

  --tile-bg:#312C27;     /* L* 18.5 */
  --tile-bg-press:#484139;
  --tile-ink:#E8E1D6;    /* 10.6:1 on the tile */
  --slot-bg:#3D3731;     /* L* 23.5 — the hollow, deeper than the tile,
                            which on a dark ground means LIGHTER. The
                            relationship is "further from the ground",
                            not "darker", and it inverts here.        */
  --rule:#615850;        /* L* 38.0 */
  --edge:#8A7E72;

  --ink:#F3F1ED;         /* 15.5:1 — the screen colour, as type      */
  --paper:#E8E1D6;
  --sentence:#E8E1D6;
  --dim:#C4BBAE;
  --faint:#9C9186;       /* 5.7:1 — clears AA for small text         */

  /* The accent needs no restatement: #DD8E58 measures 6.74:1 here,
     against the 5.53:1 the game orange managed. The ramp inverts in
     USE, not in value — on dark, --accent is the legible one and
     --accent-ink is the quiet one, so type takes --accent here.      */
  --target:#DD8E58;

  --tile-ok-bg:#24382B;  --tile-ok-ink:#C3E0CD;
  --tile-no-bg:#42231C;  --tile-no-ink:#F0C9BF;
}

/* ═══════════════════════════════════════════════════════════════════════
   3 · SEAMLESS MODE SWITCHING
   Explore/Practice/Quiz switch inside one document in zero milliseconds,
   while Defuse and Forge are separate documents — a white flash, a header
   rebuild, a font re-fetch, a scroll reset. Cross-document view
   transitions close that gap.

   TWO RULES THIS BLOCK MUST OBEY
   1. A view-transition-name must be UNIQUE in its document. .set-tabs was
      named once and tenses-rules.html has three of them; a duplicate makes
      the browser abort the transition, which mid-navigation shows as
      garbage.
   2. A name gives the element TWO snapshots painted on top of each other.
      animation:none on both leaves both at full opacity — that is the
      doubling. Hide the outgoing one instead.
   ═══════════════════════════════════════════════════════════════════════ */
@view-transition{ navigation:auto }
/* The wordmark is the one thing byte-identical on every page, so it holds
   still. NOT the mode row: its active item differs per page, so morphing
   "Quiz" into "Defuse" slid two different words across each other. */
.app-title{ view-transition-name:wordmark }
::view-transition-group(root),
::view-transition-group(wordmark){
  animation-duration:.22s;
  animation-timing-function:cubic-bezier(.2,.8,.2,1)}
::view-transition-old(wordmark){ animation:none; opacity:0 }
::view-transition-new(wordmark){ animation:none; opacity:1 }
@media (prefers-reduced-motion:reduce){
  @view-transition{ navigation:none }
}

/* ═══════════════════════════════════════════════════════════════════════
   4 · BASE
   ═══════════════════════════════════════════════════════════════════════ */
*,*::before,*::after{margin:0;padding:0;box-sizing:border-box}
/* overscroll-behavior:none kills the rubber-band, and with it the sense
   that a list is being flung rather than dragged. The games must not
   bounce and set their own overflow:hidden; a reading surface should
   scroll like a page. `contain` stops a scroll chaining to the page behind
   a sheet without freezing the bounce inside it. */
html,body{background:var(--screen);overscroll-behavior:contain}
body{font-family:var(--f-body);color:var(--paper);
  -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}
button,a{font:inherit;color:inherit;background:none;border:none;
  cursor:pointer;text-decoration:none}
/* THE BLACK FLASH ON PHONES. Chrome and Safari paint their own translucent
   black wash over any tapped control, in a colour this product does not
   contain. On the mode row it landed as a dark slab behind a word that on
   desktop is only weight and a 2px underline — two platforms looking like
   two designs. It was never authored; it is a default nobody turned off. */
*{-webkit-tap-highlight-color:transparent}
::selection{background:rgba(var(--accent-rgb),.18)}
/* ONE focus ring, stated once, at the end of the file so nothing overrides
   it by accident. See §14. */

/* ═══════════════════════════════════════════════════════════════════════
   5 · HEADER
   ONE rule, not three. The old header stacked a rule under the mode row, a
   rule at the bottom of the header, and inherited a third from the framed
   page — hence the source comments about "Rule 1 of 3" and lines
   "doubling". When the code has to reason that hard about which line is
   which, the answer is fewer lines: space separates the mode row from the
   tabs, and a single hairline closes the header.
   ═══════════════════════════════════════════════════════════════════════ */
.header{position:fixed;top:0;left:0;right:0;z-index:30;
  display:flex;flex-direction:column;gap:.45rem;
  padding:calc(env(safe-area-inset-top,0px) + .4rem) 1.25rem .5rem;
  background:var(--page-bg)}
.header::after{content:'';position:absolute;left:50%;bottom:0;
  transform:translateX(-50%);width:min(var(--w-read),calc(100% - 2rem));
  height:1px;background:var(--rule)}

/* STICKY VARIANT. Eight Speaking pages overrode .header to position:sticky
   and faded the hairline in on scroll, and that is a better header for a
   page that is read top to bottom rather than worked inside a fixed frame.
   It is an opt-in class now instead of eight copies: <header class="header
   header--flow">, and the page sets body.scrolled from its own listener. */
.header--flow{position:sticky;padding-bottom:.35rem}
.header--flow::after{width:100%;opacity:0;transition:opacity .2s ease}
body.scrolled .header--flow::after{opacity:1}

.header-top{display:grid;grid-template-columns:1fr auto 1fr;align-items:center}
.back-link{justify-self:start;font-family:var(--f-display);font-size:0.6875rem;
  font-weight:700;letter-spacing:var(--track-label);text-transform:uppercase;
  color:var(--faint);transition:color .2s;white-space:nowrap}
@media(hover:hover){ .back-link:hover{color:var(--accent-ink)} }
.header-title{display:flex;flex-direction:column;align-items:center;line-height:1.1}
.app-title{font-family:var(--f-display);font-size:1.09rem;font-weight:900;
  letter-spacing:.2em;color:var(--ink)}
.app-subtitle{font-size:0.6875rem;font-weight:600;letter-spacing:.16em;
  text-transform:uppercase;color:var(--dim);margin-top:.1rem}
/* the level badge: a fill, no rim, like everything else */
.header-spacer{justify-self:end;font-family:var(--f-mono);font-size:0.6875rem;
  letter-spacing:.1em;text-transform:uppercase;color:var(--faint);
  background:var(--tile-bg);border:none;border-radius:var(--r-chip);
  padding:.3rem .65rem}
.header-spacer b{color:var(--paper);font-weight:600}
/* PROMOTED from uoe.css: when the wordmark would crowd the back-link and
   the badge, it drops to its own row rather than shrinking. */
.header--stacked .header-title{order:3;grid-column:1 / -1;text-align:center;
  align-items:center}
.header--stacked .header-spacer{font-size:0.6875rem;padding:.22em .5em}

/* ═══════════════════════════════════════════════════════════════════════
   6 · NAVIGATION VOCABULARY
   SIX PARTS, ALWAYS IN THIS ORDER, NOTHING ELSE ABOVE THE CONTENT:

     1  .mode-selector   which section      text + underline
     2  .set-tabs        which content      text + underline
     3  .seg             which view         THE ONLY FILLED CONTROL
     4  .tools           what to do         mono text, accent when on
     5  .dots            where you are      6px circles
     6  .go              submit             the one solid button

   THE RULE THAT KEEPS IT CLEAN: a fill means "exclusive choice between
   views of the same content". Nothing else on the page may be filled. The
   beige crept across four page types because every new control reached for
   a background.

   v8 stated that rule in a comment and then broke it in the cascade: the
   earlier .vtab.active{background:var(--tile-bg)} was never reset, so the
   active tab was a beige chip AND an underline. It is one rule now, and
   the fill is gone.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── 1 · SECTION ─────────────────────────────────────────────────────────
   No emoji. Five saturated multicolour glyphs sat across the top of a
   product whose palette is warm grey and one orange — the loudest thing on
   screen, carrying nothing the word beside it did not already say. The
   words are the navigation.
   The active mode is marked by weight AND an underline, never colour
   alone, so it reads for a colour-blind student. The underline is a
   ::after rather than a border so it can carry its own transition. */
.mode-selector{display:flex;flex-wrap:wrap;justify-content:center;
  align-self:center;gap:.15rem;margin:0 auto}
.mode-btn{position:relative;display:inline-flex;align-items:center;
  font-family:var(--f-display);font-size:.7rem;font-weight:600;
  letter-spacing:.07em;text-transform:uppercase;color:var(--faint);
  background:none;border:none;text-decoration:none;cursor:pointer;
  padding:.5rem .7rem;transition:color .15s}
/* HOVER IS A POINTER IDEA. A touch browser fires :hover on tap and leaves
   it on until something else is tapped, so a grey wash meant for a mouse
   became a permanent second "active" state on whichever mode was last
   touched. Colour only, and only where there is a pointer. */
@media(hover:hover){ .mode-btn:hover{color:var(--sentence)} }
.mode-btn:active{color:var(--ink)}
.mode-btn.active,.mode-btn[aria-current]{color:var(--ink);font-weight:700}
.mode-btn.active::after,.mode-btn[aria-current]::after{
  content:'';position:absolute;left:.7rem;right:.7rem;bottom:.28rem;
  height:2px;border-radius:1px;background:var(--accent)}
/* RESTORED. v8's appended block overrode this media query simply by coming
   after it, so phones got desktop padding and a seven-item row (phrasal
   verbs) wrapped to two lines. */
@media(max-width:760px){
  .mode-btn{padding:.42rem .52rem;font-size:0.6875rem;letter-spacing:.05em}
  .mode-btn.active::after,.mode-btn[aria-current]::after{left:.52rem;right:.52rem}
}

/* ── 2 · CONTENT SET ─────────────────────────────────────────────────────
   Eight topics, four gap counts, six photo sets: the same row everywhere.
   Text and a rule — NOT eight filled chips wrapping onto two lines, which
   is what Speaking shipped and what makes a page feel busy before a
   student has read a word of it.
   Held to the reading column so long titles wrap instead of stretching
   edge to edge, which is what makes the framed modes and Explore look
   alike. */
.set-tabs{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;
  gap:.1rem;padding:0 .5rem;max-width:var(--w-read);margin:.2rem auto 0}
.set-tabs.hidden{display:none}
.vtab{position:relative;font-family:var(--f-mono);font-size:0.6875rem;
  letter-spacing:.09em;text-transform:uppercase;color:var(--faint);
  background:none;border:none;cursor:pointer;padding:.45rem .6rem;
  white-space:nowrap;transition:color .15s}
@media(hover:hover){ .vtab:hover{color:var(--sentence)} }
.vtab:active{color:var(--ink)}
.vtab.active{color:var(--ink);background:none}
.vtab.active::after{content:'';position:absolute;left:.6rem;right:.6rem;
  bottom:.22rem;height:1.5px;background:var(--accent);border-radius:1px}
/* THE STUDENT'S OWN HISTORY, not a rank. A span, never ::after: the
   underline owns ::after, and phrasal-verbs-quiz.html was drawing its
   score dot there — on the active tab the two collided and the dot became
   the underline. That page should move its four .score-* rules onto
   .vtab .due and delete its ::after block. */
.vtab .due{display:inline-block;width:5px;height:5px;border-radius:50%;
  margin-left:.35rem;vertical-align:middle;background:var(--edge);opacity:0}
.vtab.has-due .due{opacity:1;background:var(--wrong)}
.vtab.cleared .due{opacity:1;background:var(--ok)}
.vtab.score-poor .due{opacity:1;background:var(--wrong)}
.vtab.score-ok .due{opacity:1;background:var(--edge)}
.vtab.score-great .due,.vtab.score-perfect .due{opacity:1;background:var(--ok)}
.vtab .best,.vtab .pct{font-family:var(--f-mono);font-size:.86em;
  margin-left:.3rem;color:var(--ok);font-variant-numeric:tabular-nums}
.vtab .pct{color:inherit;opacity:.7}

/* ── 3 · VIEW · THE SEGMENTED CONTROL ────────────────────────────────────
   The one filled control in the product. A track holding two to four
   mutually exclusive views of the same content: Full text / Paragraph,
   Full discussion / In sections, Watch / Notice / Drill / Do.
   Not for actions, not for topics, not for anything a student can have two
   of at once.

   The track and the knob take --r-chip. v8 set both to 999px, which is the
   geometry the token block spends a paragraph retiring — and it is the
   only control on the page still shaped like 2019. */
.seg{display:inline-flex;align-items:center;gap:0;padding:.14rem;
  background:var(--tile-bg);border-radius:var(--r-chip)}
.seg button{font-family:var(--f-display);font-size:0.6875rem;font-weight:600;
  letter-spacing:.04em;color:var(--dim);background:none;border:none;
  cursor:pointer;padding:.34rem .95rem;border-radius:var(--r-field);
  white-space:nowrap;min-height:26px;
  transition:background .18s cubic-bezier(.2,.8,.2,1),color .18s,box-shadow .18s}
@media(hover:hover){ .seg button:hover{color:var(--ink)} }
.seg button[aria-selected="true"],.seg button.on{
  background:var(--page-bg);color:var(--ink);font-weight:700;
  box-shadow:0 1px 2px rgba(0,0,0,.06)}
/* The small size, from Speaking, where a segmented control sits inside a
   card rather than under the header. */
.seg.sm{padding:.14rem}
.seg.sm button{font-size:0.6875rem;padding:.38rem .6rem;min-height:30px}

/* ── 4 · TOOLS ───────────────────────────────────────────────────────────
   Verbs. A verb does not need a box: play, x-ray, hint, key, see the card.
   An active tool takes the accent, not a background, so the segmented
   control stays the only filled thing on the page. */
.tools{display:flex;align-items:center;gap:.4rem;flex-wrap:wrap;
  min-height:2.4rem;margin-bottom:1rem}
.tool{font-family:var(--f-mono);font-size:0.6875rem;letter-spacing:.08em;
  text-transform:uppercase;color:var(--faint);background:none;border:none;
  cursor:pointer;padding:.5rem .55rem;min-height:36px;
  border-radius:var(--r-chip);transition:color .15s}
@media(hover:hover){ .tool:hover{color:var(--sentence)} }
.tool.on{color:var(--accent-ink)}
.tool[disabled]{color:var(--rule);cursor:default}
/* The quiet readout on the right: never a control, so never coloured. */
.tool-n{font-family:var(--f-mono);font-size:0.6875rem;color:var(--faint);
  margin-left:auto;font-variant-numeric:tabular-nums}
.tool-sep{width:1px;height:14px;background:var(--rule);margin:0 .3rem}

/* ── 5 · POSITION ────────────────────────────────────────────────────────
   Gap-fill got these right first. Same dots everywhere, and always
   tappable: the 6px circle sits inside a 22px target, because a dot a
   thumb cannot hit is decoration pretending to be navigation. */
.dots{display:flex;align-items:center;justify-content:center;gap:.2rem;
  flex-wrap:wrap}
.dots button{width:22px;height:22px;padding:0;border:none;background:none;
  cursor:pointer;display:flex;align-items:center;justify-content:center}
.dots button::after{content:'';width:6px;height:6px;border-radius:50%;
  background:var(--rule);transition:background .2s ease,transform .2s ease}
.dots button.on::after{background:var(--accent);transform:scale(1.25)}
.dots button.done::after{background:var(--faint)}
@media(hover:hover){ .dots button:hover::after{background:var(--faint)} }

/* ── 6 · THE ONE SOLID BUTTON ────────────────────────────────────────────
   Submit, and nothing else. Solid so that on a page of hairlines there is
   never a question about what the next action is. --r-chip, for the same
   reason as .seg: this is the product's shape now. */
.go{font-family:var(--f-display);font-size:.72rem;font-weight:700;
  letter-spacing:.06em;text-transform:uppercase;color:var(--page-bg);
  background:var(--ink);border:none;border-radius:var(--r-chip);
  cursor:pointer;padding:.7rem 1.4rem;min-height:44px;
  transition:opacity .15s,background .15s}
.go[disabled]{background:var(--rule);color:var(--faint);cursor:default}
@media(hover:hover){ .go:not([disabled]):hover{opacity:.88} }
/* Use-of-English's submit is sage rather than ink, because on that page the
   ink button sits next to ink type. One class, not a hard-coded hex in
   uoe.css. */
.go--sage{background:var(--sage-ink);color:var(--card)}
@media(hover:hover){ .go--sage:not([disabled]):hover{filter:brightness(.94);opacity:1} }

/* ── SHARED ──────────────────────────────────────────────────────────────
   Every interactive thing clears 44px on touch, per iOS. Desktop keeps the
   tighter rhythm because a mouse does not miss. */
@media(pointer:coarse){
  .mode-btn,.vtab,.tool{min-height:44px}
  .dots button{width:32px;height:32px}
}
@media(prefers-reduced-motion:reduce){
  .seg button,.dots button::after{transition:none}
  .dots button.on::after{transform:none}
}

/* ═══════════════════════════════════════════════════════════════════════
   7 · THE TILE FAMILY
   Answer cards, word tiles, blanks and quiz options are ONE family across
   every mode. No rims, no shadows, no gradients: a fill and a radius.
   ═══════════════════════════════════════════════════════════════════════ */
.tile,.chip{display:inline-flex;align-items:center;justify-content:center;
  min-height:2.2em;padding:0 1em;border:none;box-shadow:none;
  border-radius:var(--r-chip);background:var(--tile-bg);color:var(--tile-ink);
  font-family:var(--f-display);font-weight:600;letter-spacing:-.01em;
  transition:transform .1s cubic-bezier(.2,.8,.2,1),background .12s}
.tile:active,.chip:active{transform:scale(.95);background:var(--tile-bg-press)}
.tile-card{border-radius:var(--r-card)}
.tile.is-ok,.chip.is-ok{background:var(--tile-ok-bg);color:var(--tile-ok-ink)}
.tile.is-no,.chip.is-no{background:var(--tile-no-bg);color:var(--tile-no-ink)}
/* the empty blank: DEEPER than a tile, because that is what reads as a
   hole once the rim and the inset shadow are gone */
.slot{display:inline-flex;align-items:center;justify-content:center;
  min-width:4.2em;height:1.9em;padding:0 .8em;vertical-align:middle;
  border:none;box-shadow:none;border-radius:var(--r-field);
  background:var(--slot-bg);color:var(--dim);
  font-family:var(--f-display);font-weight:600}
.slot.filled{background:var(--tile-bg);color:var(--tile-ink);font-weight:700}

/* ═══════════════════════════════════════════════════════════════════════
   8 · THE CLOSE CONTROL
   One control, every mode. A filled disc with a STROKED mark: the ✕ text
   glyph renders hairline-thin at this size and disappears on a cream page,
   which is exactly what happened to the old one.
   ═══════════════════════════════════════════════════════════════════════ */
.close-btn{width:34px;height:34px;border-radius:50%;flex:none;
  display:flex;align-items:center;justify-content:center;
  background:var(--slot-bg);color:var(--tile-ink);border:none;cursor:pointer;
  transition:background .15s,transform .1s}
@media(hover:hover){ .close-btn:hover{background:var(--tile-bg-press)} }
.close-btn:active{transform:scale(.92)}
/* Fixed to the corner, shown only when there is something to leave. The
   games set `playing` while a round runs and `gated` while the start card
   is up; a page that sets neither never shows it, so this is safe to share
   with the reading modes. */
.close-fixed{position:fixed;top:calc(env(safe-area-inset-top,0px) + .55rem);
  right:.7rem;z-index:70;display:none}
body.playing .close-fixed,
body.gated   .close-fixed{display:flex}

/* ═══════════════════════════════════════════════════════════════════════
   9 · SHEETS
   The slides overlay used rgba(8,8,9,.86) with a blur — the only near-black
   surface in the product, and a hard cut against a cream page. The veil is
   the page's own colour now, so a sheet reads as the page stepping back
   rather than as a different app opening.
   ═══════════════════════════════════════════════════════════════════════ */
.sheet{position:fixed;inset:0;z-index:60;display:none;
  align-items:center;justify-content:center;padding:1.2rem;background:var(--scrim)}
.sheet.open{display:flex}
.sheet-card{position:relative;width:100%;max-width:23rem;
  max-height:min(82vh,34rem);overflow-y:auto;
  display:flex;flex-direction:column;align-items:center;gap:.35rem;
  text-align:center;padding:1.6rem 1.5rem 1.4rem;border-radius:var(--r-sheet);
  background:var(--card);border:none;
  box-shadow:0 20px 55px rgba(43,33,41,.14);
  animation:sheetIn .28s cubic-bezier(.2,1,.4,1) both}
/* The wide, left-aligned sheet — Speaking's transcript and phrase panels.
   Was #sheet .sheet-card in six pages, which meant the id had to be right
   as well as the class. */
.sheet-card--wide{width:min(40rem,calc(100vw - 1.5rem));max-width:none;
  align-items:stretch;text-align:left;gap:0}
.sheet-card h2{font-family:var(--f-display);font-size:1.1rem;font-weight:900;
  letter-spacing:var(--track-display);color:var(--ink);margin-bottom:.5rem}
.sheet-card .close-btn{position:absolute;top:.7rem;right:.7rem}
@keyframes sheetIn{from{opacity:0;transform:translateY(10px) scale(.97)}
  to{opacity:1;transform:none}}

/* ═══════════════════════════════════════════════════════════════════════
   10 · THE MODAL
   PROMOTED FROM uoe.css, unchanged in substance.

   There were three modal systems in the repo: .sheet-card here, .poster in
   §13, and a .modal-* written per page. uoe.css's is the one already on
   the token set — square corners, the page's own scrim, Anton title, sage
   primary — so it is the one promoted. Writing's .modal-card (1.3rem
   radius, rgba(15,13,10,.5) backdrop, Barlow Condensed) is the copy to
   retire against it: it is a different product's modal.

   WHEN TO USE WHICH
     .sheet      a mode taking the screen — Practice, Quiz, the gate
     .modal      an interruption with a decision in it — confirm, results,
                 "you have unsaved work"
     .poster     a card lying ON the page, not over it
   ═══════════════════════════════════════════════════════════════════════ */
.modal-overlay,.modal{position:fixed;inset:0;z-index:80;display:none;
  align-items:center;justify-content:center;padding:1.1rem;
  background:var(--scrim)}
.modal-overlay.show,.modal.show,
.modal-overlay.active,.modal.active,
.modal-overlay.open,.modal.open{display:flex}
.modal-overlay > *,.modal > *{width:min(520px,100%);max-height:84vh;
  overflow-y:auto;background:var(--card);border-radius:var(--r-sheet);
  padding:1.7rem 1.5rem 1.4rem;box-shadow:0 20px 55px rgba(43,33,41,.14);
  animation:sheetIn .28s cubic-bezier(.2,1,.4,1) both}
.modal-overlay > *::-webkit-scrollbar,.modal > *::-webkit-scrollbar{width:3px}
.modal-overlay > *::-webkit-scrollbar-thumb,
.modal > *::-webkit-scrollbar-thumb{background:var(--poster-rule);border-radius:2px}
.modal-header,.modal-footer{display:block}
.modal-title{font-family:'Anton', Impact, var(--f-display), sans-serif;
  font-weight:400;text-transform:uppercase;letter-spacing:.01em;
  font-size:clamp(1.6rem,5.2vw,2rem);line-height:1.02;
  color:var(--poster-ink);margin-bottom:.5rem}
.modal-subtitle{font-size:.86rem;line-height:1.55;color:var(--poster-body);
  margin-bottom:1.2rem}
.modal-btn{font-family:var(--f-display);font-size:.72rem;font-weight:700;
  letter-spacing:.06em;text-transform:uppercase;border:none;cursor:pointer;
  padding:.7rem 1.2rem;min-height:44px;border-radius:var(--r-chip);
  transition:filter .15s}
.modal-btn-primary{background:var(--sage-ink);color:var(--card)}
.modal-btn-secondary{background:var(--poster-fill);color:var(--poster-ink)}
@media(hover:hover){ .modal-btn:hover{filter:brightness(.96)} }
.modal-close{position:absolute;top:.8rem;right:.8rem}

/* ═══════════════════════════════════════════════════════════════════════
   11 · FRAMED MODES, PRESENTED AS SHEETS
   Practice and Quiz used to be panels inside the page: the header stayed,
   the frame took whatever height was left, and the mode you were in was
   legible only from a highlighted word at the top. That made them the odd
   two out — Defuse and Forge drop the chrome and take the screen, and the
   slides overlay presents itself as a sheet.
   They are sheets too now. Same scrim, same radius, same close disc, and
   the same reason: a student working through ten sentences should see ten
   sentences, not ten sentences and a navigation bar they cannot use.
   Both frames stay mounted at boot and are merely revealed, so opening one
   costs nothing and the switch is a fade rather than a load.
   ═══════════════════════════════════════════════════════════════════════ */
.area{position:fixed;top:var(--header-h);left:0;right:0;bottom:0;
  overflow-y:auto;-webkit-overflow-scrolling:touch;overscroll-behavior:contain;
  opacity:0;visibility:hidden;pointer-events:none;
  transition:opacity .22s ease,visibility 0s linear .22s}
.area.active{opacity:1;visibility:visible;pointer-events:auto;
  transition:opacity .22s ease,visibility 0s}
/* THE SHEET SITS UNDER THE HEADER, NEVER OVER IT. An earlier version
   covered it. That was wrong: the header is what makes five separate modes
   read as one product — the wordmark holds still, the mode row stays
   reachable, and Quiz to Defuse is one tap rather than a close then a tap.
   Hiding it turned Practice and Quiz into places to escape from. */
.area.sheet-mode{top:var(--header-h);z-index:25;overflow:hidden;
  background:var(--scrim);display:flex;justify-content:center;
  padding:.9rem .75rem 0}
.sheet-full{position:relative;display:flex;flex-direction:column;
  width:min(760px,100%);height:100%;overflow:hidden;
  border-radius:var(--r-sheet) var(--r-sheet) 0 0;
  background:var(--page-bg);box-shadow:0 -6px 30px rgba(43,33,41,.14);
  transform:translateY(12px);transition:transform .26s cubic-bezier(.2,.9,.3,1)}
.area.active .sheet-full{transform:translateY(0)}
/* No title bar. The header directly above says which mode is active, and a
   second bar with its own hairline would put back the doubled rules this
   build removed. The close floats instead — one control, no band. */
.sheet-close{position:absolute;top:.55rem;right:.55rem;z-index:6}
.frame{position:relative;flex:1;min-height:0}
.frame iframe{width:100%;height:100%;border:none;display:block;
  background:var(--page-bg)}
@media(min-width:761px){
  .area.sheet-mode{padding:1.1rem 1.1rem 0}
  .sheet-full{border-radius:var(--r-sheet);
    box-shadow:0 10px 44px rgba(43,33,41,.14)}
}

/* ═══════════════════════════════════════════════════════════════════════
   12 · THE SHEET GATE
   Defuse and Forge never drop a student into a round: they show the
   object, name the set, and wait to be started. Practice and Quiz used to
   open straight into whatever test happened to be first, which is the one
   thing in the product that decided FOR the student.
   ═══════════════════════════════════════════════════════════════════════ */
.sheet-gate{position:absolute;inset:0;z-index:5;display:none;
  flex-direction:column;align-items:center;justify-content:flex-start;
  gap:.2rem;padding:2rem 1.25rem;overflow-y:auto;
  -webkit-overflow-scrolling:touch;overscroll-behavior:contain;
  background:var(--page-bg)}
.sheet-gate.open{display:flex}
/* Inside a sheet the gate fills its frame; opened directly there is no
   frame to fill, so it takes the viewport. */
.sheet-gate.gate-standalone{position:fixed;z-index:900}
.gate-mark{width:96px;height:96px;margin-bottom:1.1rem}
.gate-mark .pend-dial span{font-size:1.5rem}
.gate-head{font-family:var(--f-display);font-weight:900;
  font-size:clamp(1.15rem,4.2vw,1.5rem);letter-spacing:var(--track-display);
  color:var(--ink);text-align:center}
.gate-sub{margin-top:.4rem;font-size:.86rem;color:var(--faint);
  text-align:center;max-width:24rem;line-height:1.5}
.gate-list{display:flex;flex-wrap:wrap;justify-content:center;
  gap:.5rem;margin-top:1.5rem;max-width:34rem}
.gate-pick{font-family:var(--f-display);font-size:.78rem;font-weight:700;
  letter-spacing:.04em;padding:.7rem 1.15rem;border:none;
  border-radius:var(--r-chip);background:var(--tile-bg);color:var(--tile-ink);
  cursor:pointer;transition:transform .1s cubic-bezier(.2,.8,.2,1),background .15s}
@media(hover:hover){ .gate-pick:hover{background:var(--tile-bg-press)} }
.gate-pick:active{transform:scale(.95)}
.gate-wait{margin-top:1.5rem;font-family:var(--f-mono);font-size:.78rem;
  color:var(--faint)}

/* ═══════════════════════════════════════════════════════════════════════
   13 · THE PENDULUM
   Lifted out of Defuse and Forge, where it existed twice verbatim. Three
   concentric layers hung from a pivot: outer edge, coloured rim, dark
   core. Insets are percentages so it scales from a 96px gate mark to a
   292px dial without an override. Colour comes from --mark-*, so each
   surface sets its own object and the shell holds the shape.
   ═══════════════════════════════════════════════════════════════════════ */
.pend-dial{position:relative;border-radius:50%;
  background:linear-gradient(145deg,var(--card),#E4DDD1);
  box-shadow:-4px -4px 9px rgba(255,255,255,.9),5px 5px 13px rgba(43,33,41,.16);
  display:flex;align-items:center;justify-content:center}
.pend-dial::before{content:'';position:absolute;inset:5%;border-radius:50%;
  background:conic-gradient(var(--mark-sweep) var(--mark-pct,72%), var(--mark-rest) 0)}
.pend-dial::after{content:'';position:absolute;inset:13.5%;border-radius:50%;
  background:var(--mark-core)}
.pend-dial span{position:relative;z-index:2;font-family:var(--f-display);
  font-weight:900;color:var(--mark-ink);font-variant-numeric:tabular-nums}
/* the swing: loose and looping, because nothing is counting down yet */
.pend-hang{position:relative;margin:0 auto;transform-origin:50% -40px;
  animation:pendSwing 2.6s ease-in-out infinite}
.pend-hang::before{content:'';position:absolute;left:50%;top:-40px;width:1px;
  height:40px;background:linear-gradient(180deg,transparent,var(--edge));
  transform:translateX(-50%)}
.pend-hang::after{content:'';position:absolute;left:50%;top:-43px;width:7px;
  height:7px;border-radius:50%;background:var(--edge);transform:translateX(-50%)}
.pend-hang .pend-dial{position:absolute;inset:0}
@keyframes pendSwing{0%,100%{transform:rotate(-10deg)}50%{transform:rotate(10deg)}}
@media(prefers-reduced-motion:reduce){.pend-hang{animation:none;transform:rotate(0)}}

/* ═══════════════════════════════════════════════════════════════════════
   14 · THE POSTER
   PROMOTED, NOT COPIED. Authored twice — in gerunds-sort.html as `.card`
   with its own private tokens, and again in vocab.css as `.card`, where
   the tokens were re-declared, one value corrected and three added.
   Three drifts are reconciled here:
   1. THE PAPER. Sort hard-codes #ECE9E3, correct for its own #EBE8E1
      ground and wrong for --screen. So the token is not a number:
      --poster:var(--card). Sort keeps its darker paper by overriding one
      token rather than owning a second card.
   2. THE MONO. vocab.css declared --f-mono2, byte-identical to --f-mono.
      It survives above as a deprecated alias only.
   3. THE NAME. `.card` was wrong for a shared shell: eleven rules pages
      define a `.card` of their own. The thing is flat paper with a kicker,
      a caramel mark and a square corner, and it has been called the poster
      in every comment written about it.
   ═══════════════════════════════════════════════════════════════════════ */
:root{
  --poster:var(--card);
  /* Near-black, warm. Not --ink: that is tuned for an embossed board and
     reads cold on paper. The one place a second ink is justified, and it
     is justified by the material, not by taste. */
  --poster-ink:#14110E;
  /* Body copy on the card: 6.7:1 on the paper, against 15.5:1 for a placed
     word, so the hierarchy inside a sentence reads without either end
     shouting. */
  --poster-body:#5C5852;
  /* KEPT AND UNUSED HERE, deliberately: Sort still uses it for its own
     sentence, where context around one huge word SHOULD recede. */
  --poster-soft:rgba(20,17,14,.60);
  --poster-faint:rgba(20,17,14,.50);  /* kickers, folio numerals */
  --poster-rule:rgba(20,17,14,.20);   /* the hairline inside the card */
  --poster-fill:rgba(20,17,14,.09);   /* pills, rule chips, empty slots */
  --poster-fill-hi:rgba(20,17,14,.15);
  --caramel:#B0865C;                  /* a MARK, never type — see below */
}
/* THE PAPER. Square corners, because paper is cut straight. A 1px shadow,
   not 5px: Sort's card floats above a board and needs the lift; a poster
   on a lesson page is lying on it, and the heavier shadow read as a modal
   that had forgotten its scrim. */
.poster{position:relative;background:var(--poster);
  border-radius:var(--r-sheet);
  box-shadow:0 1px 3px rgba(0,0,0,.10);
  padding:clamp(1.3rem,4.4vw,1.9rem) clamp(1.1rem,4vw,1.6rem)}
/* THE KICKER. `i` is a 26×10 solid block. Caramel measures 2.7:1 on this
   paper, which is correct for a rectangle and fails for a word. Keeping it
   a block is what keeps it legible and what makes it read as expensive
   rather than decorated. */
.poster-kick{display:flex;align-items:center;gap:9px;
  font-family:var(--f-mono);font-size:0.6875rem;letter-spacing:.22em;
  text-transform:uppercase;color:var(--poster-faint);margin-bottom:.9rem}
.poster-kick i{display:block;width:26px;height:10px;flex:none;
  background:var(--caramel)}
/* THE HEADLINE. Anton, shared with .modal-title and nowhere else. A card
   with no headline is not a card missing one — Similar Words names none of
   its 115 sets and the pills carry the card. Opt-in, never a default. */
.poster-head{font-family:'Anton', Impact, var(--f-display), sans-serif;
  font-weight:400;letter-spacing:.01em;color:var(--poster-ink);
  font-size:clamp(1.4rem,5vw,1.85rem);line-height:1.05;
  margin:0 0 .9rem;text-transform:none}
.poster-sub{font-family:var(--f-mono);font-size:0.6875rem;letter-spacing:.16em;
  text-transform:uppercase;color:var(--poster-faint);margin-bottom:.85rem}
.poster-body{color:var(--poster-body);line-height:1.5;
  font-size:clamp(.85rem,3vw,1rem)}
/* The hairline INSIDE the card. Not --rule: that is the page's line, tuned
   against --screen, and it reads as a scratch on paper. */
.poster-rule{border:none;border-top:1px solid var(--poster-rule);margin:.95rem 0}
/* THE PILL — the card's own grey, so it belongs to the paper rather than
   importing the page's beige onto it. --r-field, not --r-chip: this is
   something a word goes INTO. */
.poster-pill{display:inline-flex;align-items:center;justify-content:center;
  font-family:var(--f-display);font-size:.88rem;font-weight:600;
  color:var(--poster-ink);background:var(--poster-fill);
  border:none;border-radius:var(--r-field);padding:.4rem .8rem;
  transition:background .15s,color .15s,opacity .15s,
             transform .1s cubic-bezier(.2,.8,.2,1)}
@media(hover:hover){ .poster-pill:hover:not(:disabled):not(.used){background:var(--poster-fill-hi)} }
.poster-pill:active:not(:disabled){transform:scale(.96)}
.poster-pill:focus-visible{outline:2px solid var(--caramel);outline-offset:2px}
/* Chosen and waiting: the pill inverts. Ink on paper becomes paper on ink —
   the strongest signal available without a colour the card does not own. */
.poster-pill.picked{background:var(--poster-ink);color:var(--poster)}
/* Spent. Struck, not removed: taking it out would reflow its neighbours and
   move the thing the student was about to tap. */
.poster-pill.used{opacity:.34;text-decoration:line-through;
  text-decoration-thickness:1px;cursor:default}
/* THE RULE CHIP — mono on the card's own fill. Here mono in a grey block
   means "this is the grammar itself", and that reading is shared with
   Explore's poster modal. */
.poster-chip{font-family:var(--f-mono);font-weight:500;font-size:.78rem;
  color:var(--poster-ink);background:var(--poster-fill);
  border-radius:var(--r-field);padding:.42em .7em;display:inline-block}
/* A wrong answer knocks the paper. Motion, not colour — the card does not
   own red. */
.poster.shake{animation:poster-shake .42s}
@keyframes poster-shake{
  0%,100%{transform:none} 20%{transform:translateX(-11px)}
  40%{transform:translateX(9px)} 60%{transform:translateX(-6px)}
  80%{transform:translateX(4px)}}
@media(prefers-reduced-motion:reduce){ .poster.shake{animation:none} }


/* ═══════════════════════════════════════════════════════════════════════
   14b · ONE TAB, EVERY PAGE (v11)

   Before this block the product had at least seven tab designs: black
   pills (.reg-btn, .ttab, .nb), green pills (.mode-btn inside writing
   headers), square boxes (.view-toggle), capsules (.seg on Speaking),
   rounded rectangles (.seg elsewhere). A student moving between pages met
   a new control each time.

   Now there are exactly three kinds of navigation, and they look the same
   wherever they appear — under the header, in a footer, inside a card:

     A  WHICH SECTION   .mode-selector .mode-btn      text + accent underline
     B  WHICH CONTENT   .vtab .ttab .ftab              text + accent underline
     C  WHICH VIEW      every tab group below          capsule track + knob

   The :root prefix is deliberate. Pages declare their own tab styles in a
   <style> AFTER this sheet loads, so an equal selector would lose; one
   extra pseudo-class wins without !important and without editing 80
   pages. When a page is next touched, delete its local tab CSS.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── C · the track ── */
:root .seg,:root .nav-pill,:root .mode-pill,:root .mode-toggle,:root .view-toggle,
:root .register-bar,:root .mode-toggle-container,:root .view-mode-toggle-mini,
:root .viewtog,:root .modes,:root .pills,:root .vmode{
  display:inline-flex;align-items:center;gap:0;width:auto;max-width:100%;
  padding:.14rem;margin-left:auto;margin-right:auto;
  background:var(--tile-bg);border:none;box-shadow:none;border-radius:var(--r-tab);
  overflow-x:auto;scrollbar-width:none}
:root .register-bar{display:flex;width:max-content;margin-top:.45rem;margin-bottom:.45rem}
:root .register-bar .reg-divider{display:none}
/* ── C · the tab ── */
:root .seg button,:root .nav-pill .nb,:root .mode-pill .mode-btn,:root .mode-toggle button,
:root .view-toggle button,:root .reg-btn,:root .mode-toggle-btn,:root .view-mode-btn,
:root .viewtog .viewbtn,:root .modes .mbtn,:root .pills .pill-b,:root .vmode .vm{
  font-family:var(--f-display);font-size:0.6875rem;font-weight:600;letter-spacing:.04em;
  text-transform:none;color:var(--dim);background:none;border:none;box-shadow:none;
  opacity:1;padding:.34rem .95rem;min-height:26px;border-radius:var(--r-tab);
  white-space:nowrap;cursor:pointer;text-decoration:none;
  display:inline-flex;align-items:center;justify-content:center;gap:.3rem;
  transition:background .18s cubic-bezier(.2,.8,.2,1),color .18s,box-shadow .18s}
:root .mode-pill .mode-btn::after{content:none}
@media(hover:hover){
  :root .seg button:hover,:root .nav-pill .nb:hover,:root .mode-pill .mode-btn:hover,
  :root .mode-toggle button:hover,:root .view-toggle button:hover,:root .reg-btn:hover,
  :root .mode-toggle-btn:hover,:root .view-mode-btn:hover,:root .viewtog .viewbtn:hover,
  :root .modes .mbtn:hover,:root .pills .pill-b:hover,:root .vmode .vm:hover{color:var(--ink);background:none} }
/* ── C · the knob: the chosen tab, whatever the page calls "chosen" ── */
:root .seg button[aria-selected="true"],:root .seg button.on,:root .seg button.active,
:root .nav-pill .nb.on,:root .nav-pill .nb.active,
:root .mode-pill .mode-btn.active,:root .mode-pill .mode-btn[aria-current],
:root .mode-toggle button.active,:root .mode-toggle button.on,:root .mode-toggle button[aria-pressed="true"],
:root .view-toggle button[aria-pressed="true"],:root .view-toggle button.active,:root .view-toggle button.on,
:root .reg-btn.active,:root .mode-toggle-btn.active,:root .view-mode-btn.active,
:root .viewtog .viewbtn.active,:root .modes .mbtn.on,:root .modes .mbtn.active,
:root .pills .pill-b.on,:root .pills .pill-b.active,:root .vmode .vm.on,:root .vmode .vm.active{
  background:var(--page-bg);color:var(--ink);font-weight:700;box-shadow:0 1px 2px rgba(0,0,0,.08)}
/* Pages that slide a separate thumb behind the tabs: same shape, same fill. */
:root .seg .thumb,:root .mode-toggle-slider,:root .toggle-thumb{
  border-radius:var(--r-tab);background:var(--page-bg);box-shadow:0 1px 2px rgba(0,0,0,.08)}
@media(max-width:380px){
  :root .seg button,:root .nav-pill .nb,:root .mode-pill .mode-btn,:root .view-toggle button,
  :root .reg-btn,:root .mode-toggle-btn{padding:.34rem .7rem} }

/* ── A · section: the writing pages had turned the header row into pills ── */
:root .mode-selector .mode-btn{background:none;box-shadow:none;border-radius:0}
:root .mode-selector .mode-btn.active,:root .mode-selector .mode-btn[aria-current]{
  background:none;box-shadow:none;border-radius:0;color:var(--ink);font-weight:700}
:root .mode-selector .mode-btn.active::after,:root .mode-selector .mode-btn[aria-current]::after{
  content:'';position:absolute;left:.7rem;right:.7rem;bottom:.28rem;height:2px;border-radius:1px;background:var(--accent)}
@media(max-width:760px){ :root .mode-selector .mode-btn.active::after,
  :root .mode-selector .mode-btn[aria-current]::after{left:.52rem;right:.52rem} }

/* ── The Speaking stage row: on a phone the phrase bank drops below the
   tabs instead of pushing them off the edge. ── */
@media(max-width:480px){
  :root .stage-row{grid-template-columns:1fr;gap:.5rem}
  :root .stage-row .seg,:root .stage-row .bank-btn{grid-column:1;justify-self:center} }

/* ── B · content: the old topic and filter bars become the shell's row ── */
:root .topic-tabs,:root .filter-bar{background:none;border-bottom:none;justify-content:center;
  gap:.1rem;padding:.2rem .5rem}
:root .ttab,:root .ftab{position:relative;font-family:var(--f-mono);font-size:0.6875rem;
  font-weight:400;letter-spacing:.09em;text-transform:uppercase;color:var(--faint);
  background:none;border:none;border-radius:0;padding:.45rem .6rem;white-space:nowrap;cursor:pointer}
@media(hover:hover){ :root .ttab:hover,:root .ftab:hover{color:var(--sentence);background:none} }
:root .ttab.active,:root .ftab.active{color:var(--ink);background:none}
:root .ttab.active::after,:root .ftab.active::after{content:'';position:absolute;left:.6rem;right:.6rem;
  bottom:.22rem;height:1.5px;background:var(--accent);border-radius:1px}

/* ── The way out: the phrase bank is a ring, not a tab ──
   Tabs are filled capsules now, so the one round exception has to differ
   by something other than shape. It is hollow: a door, not a setting. */
:root .bank-btn{background:none;color:var(--sentence);border:1px solid var(--rule);
  border-radius:var(--r-tab);box-shadow:none}
@media(hover:hover){ :root .bank-btn:hover{background:none;border-color:var(--accent);color:var(--accent-ink)} }
:root .bank-btn:active{background:var(--tile-bg)}

/* ═══════════════════════════════════════════════════════════════════════
   14c · THE LAYERS (v12)

   Screen → container → focus card → tooltip. A focus card is secondary:
   it is always narrower than the container it sits over, so it reads as
   one thing brought forward, not as a second page. The Writing gallery
   card (560 px over a 1180 px container) was the model; the Speaking
   phrase-bank sheet (640 px over a 640 px card) broke the rule. The
   :root prefix wins over each page's own rule, as in 14b.
   ═══════════════════════════════════════════════════════════════════════ */
:root .sheet-card,:root #sheet .sheet-card,:root .sheet-card--wide,:root .gal-card{
  width:min(var(--w-focus),calc(100vw - 1.5rem));max-width:none}
:root .tooltip,:root [role="tooltip"]{max-width:min(var(--w-tip),calc(100vw - 2rem))}
@media(min-width:760px){
  /* On a wide screen a focus card also stays well inside its container. */
  :root .sheet-card,:root #sheet .sheet-card,:root .sheet-card--wide{width:min(var(--w-focus),calc(100vw - 6rem))} }
/* ═══════════════════════════════════════════════════════════════════════
   14d · THE WORDMARK: Be2FIRST
   Replaces the INTUITY text in .header-title. Be2 is split at the centre of
   its capitals into a top half and a bottom half, with FIRST in the strip
   between them. Scoped to .be2 so it never touches .app-title.
     --mid    centre of the strip      --pivot  where the stretch is anchored
     --gap    strip height             --tall   vertical stretch of the word
     --e      extra stretch of the e
   Every em inside is relative to .be2's own font-size, so changing that one
   value rescales the whole mark.
   ═══════════════════════════════════════════════════════════════════════ */
.be2{--mid:.591em;--pivot:.486em;--gap:.36em;--tall:1.2;--e:1.3;
  view-transition-name:wordmark;
position:relative;display:block;width:max-content;margin:.1em auto .4em;
  font-family:'Geist', system-ui, sans-serif;font-weight:700;
  font-size:2.1rem;line-height:.98;letter-spacing:-.03em;
  text-transform:none;text-indent:0;padding-right:.02em;color:#000}

.be2 .big{display:block;margin:0;font:inherit;line-height:inherit;letter-spacing:inherit;
  color:#000;
  filter:drop-shadow(0 .04em .05em rgba(43,33,41,.24));
  transform-origin:50% var(--pivot);transform:scaleY(var(--tall))}
.be2 .e{display:inline-block;font-weight:700;
  transform-origin:50% .836em;transform:scaleY(var(--e))}

.be2 .big.top{clip-path:inset(0 0 calc(100% - (var(--pivot) + (var(--mid) - var(--gap) / 2 - var(--pivot)) / var(--tall))) 0)}
.be2 .big.bot{position:absolute;left:0;top:0;
  clip-path:inset(calc(var(--pivot) + (var(--mid) + var(--gap) / 2 - var(--pivot)) / var(--tall)) 0 0 0)}

.be2 .band{position:absolute;left:0;right:0;top:calc(var(--mid) - var(--gap) / 2);height:var(--gap);
  display:flex;align-items:center;justify-content:center}
.be2 .band i{font-style:normal;font-weight:700;font-size:.28em;letter-spacing:.28em;text-indent:.28em;
  line-height:1;color:#5E7A70;-webkit-text-stroke:.06em #5E7A70;paint-order:stroke fill;
  position:relative;top:0}

/* The mark is black. On the dark ground it would vanish, so it takes the
   page's ink instead. */
body[data-ground="dark"] .be2,
body[data-ground="dark"] .be2 .big{color:var(--ink)}

/* ═══════════════════════════════════════════════════════════════════════
   15 · FOCUS, AND THE LAST WORD ON MOTION
   The ring is stated once, last, so no component overrides it by accident.
   v8's :where() version also set border-radius, which made .go change
   shape when focused — a ring describes a control, it does not reshape it.
   ═══════════════════════════════════════════════════════════════════════ */
:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
:where(.mode-btn,.vtab,.ttab,.ftab,.seg button,.nb,.reg-btn,.view-toggle button,.tool,.dots button,.go,.gate-pick,
  .close-btn,.modal-btn):focus-visible{
  outline:2px solid var(--accent);outline-offset:2px}
@media(prefers-reduced-motion:reduce){
  *{animation-duration:.01ms!important;transition-duration:.01ms!important}
}

/* A pill a page has hidden stays hidden: the pill-group rule above sets
   display and would otherwise outrank a page's own .hidden. */
:root .seg button.hidden,:root .seg button[hidden]{display:none}
