:root{--bg:#f7f7f2;--surface:#fff;--ink:#263026;--muted:#5f685f;--accent:#1f7a4d;--accent-dark:#135434;
--accent-soft:#e2efe7;--line:#dddfd8;--line-strong:#848b81;--focus:#286da8;--flag:#a8541c;
--warn-soft:#fbeee2;--warn-ink:#8a3f12;--skip-bg:#135434;--on-accent:#fff;--r:12px;
/* ---- Scales --------------------------------------------------------------
   The palette was tokenised from the start; the rest of the system was not.
   Spacing came from about 60 hand-picked values (7px, 13px 22px, 8px 9px),
   and type from 22 font sizes, several of them a fraction of a pixel apart:
   .84rem, .85rem, .86rem and .88rem all existed, as did 1.02 and 1.05. Nobody
   can see those differences, and they guarantee the rhythm drifts.
   Type is an eight-step scale; spacing is a 4px scale.
   A note while you are here: --accent-dark is LIGHTER than --accent. It means
   "the accent for dark grounds", not "a darker accent". */
/* Spacing. Named by value, deliberately: this sheet was written on a 2px
   rhythm (10px appears 30 times, 6px 21, 14px 19), so a 4px scale would have
   moved 57% of the numbers in it while a 2px scale moves 6%. Value names also
   make a migration like this reviewable at a glance, which matters more here
   than the ability to retune the scale centrally. */
--sp-2:2px;--sp-4:4px;--sp-6:6px;--sp-8:8px;--sp-10:10px;--sp-12:12px;
--sp-14:14px;--sp-16:16px;--sp-18:18px;--sp-20:20px;--sp-22:22px;
--sp-24:24px;--sp-28:28px;--sp-32:32px;--sp-40:40px;--sp-48:48px;
--text-xs:.75rem;--text-s:.8125rem;--text-sm:.875rem;--text-base:1rem;
--text-lg:1.125rem;--text-xl:1.3125rem;--text-2xl:1.5rem;--text-3xl:1.875rem;
--r-sm:8px;--r-lg:16px;--r-pill:999px}
/* --line-strong draws the edge of buttons, chips, inputs and dialogs, so WCAG
   asks for 3:1 against the background. It was #b8bdb5 at 1.78:1 and had been
   since the start; adding dark mode is simply what caused it to be measured.
   --line stays softer because it only draws decorative dividers, which are
   exempt. */

/* Dark theme.
   Every colour is a variable, so this is a palette swap rather than a second
   stylesheet. Three points of care:
   - The brand green #1f7a4d is too dark to read on a dark background, so the
     accent is lightened. The mark in the header keeps the true brand colours,
     because that is a logo rather than text.
   - --accent-dark is used both as a dark green for text on pale chips and as a
     background for the skip link. Those pull in opposite directions once the
     page is dark, so the skip link gets its own variable.
   - Backgrounds are a warm near-black rather than pure black: #000 with light
     text produces halation, which is exactly what someone revising at midnight
     does not need. */
html[data-theme=dark]{
  --bg:#161a15;--surface:#1e231d;--ink:#e7ebe4;--muted:#a3aca1;
  --accent:#5cbf8c;--accent-dark:#9fdcbb;--accent-soft:#20342a;
  --line:#333a31;--line-strong:#6b7768;--focus:#7cb5e8;--flag:#d08b52;
  --warn-soft:#3a2c1e;--warn-ink:#f0c69a;--skip-bg:#1f7a4d;--on-accent:#12210c;
}
/* Dark mode already carries plenty of contrast, so "higher contrast" there
   means brighter edges rather than the darker greys it means on white. */
html[data-theme=dark] body.high-contrast{--line:#6a7567;--line-strong:#9aa697;--muted:#c8d0c5}

/* "Follow my device" only becomes dark when the device is. Both the palette and
   the high-contrast variant have to sit inside the media query -- outside it,
   data-theme=system would win on specificity and force dark styling on someone
   whose device is set to light. */
@media (prefers-color-scheme: dark){
  html[data-theme=system]{
    --bg:#161a15;--surface:#1e231d;--ink:#e7ebe4;--muted:#a3aca1;
    --accent:#5cbf8c;--accent-dark:#9fdcbb;--accent-soft:#20342a;
    --line:#333a31;--line-strong:#6b7768;--focus:#7cb5e8;--flag:#d08b52;
    --warn-soft:#3a2c1e;--warn-ink:#f0c69a;--skip-bg:#1f7a4d;--on-accent:#12210c;
  }
  html[data-theme=system] body.high-contrast{--line:#6a7567;--line-strong:#9aa697;--muted:#c8d0c5}
}

html[data-text-size=standard]{font-size:100%}html[data-text-size=large]{font-size:112%}html[data-text-size=larger]{font-size:126%}
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--ink);/* System fonts, deliberately, decided 3 September 2026. The brand typeface was
   considered and turned down: a web font costs bytes on a metered phone, needs
   the network on first paint, and would sit behind the offline promise this app
   makes on its own welcome screen. System fonts also honour whatever the reader
   has already set at the OS level, which matters for the same students the
   reading settings exist for. The brand mark carries the brand instead, and it
   is drawn from the same SVG paths as the website so the two cannot drift.
   Do not "fix" this without revisiting those three reasons. */
font-family:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;line-height:1.6}
body.wider-spacing{line-height:1.9;letter-spacing:.02em}
body.high-contrast{--line:#8d918a;--line-strong:#5f685f;--muted:#3a423a}
/* Links.
   There was no rule here at all until 26 August 2026, so every link that did
   not carry a class fell back to the browser default #0000EE, and visited ones
   to #551A8B. On white that looks unremarkable and passes; on the dark theme it
   is 1.87:1 against the page, 1.70:1 on a panel and 1.41:1 inside a soft green
   banner, against a 4.5:1 requirement. The colour was never chosen, which is
   why nothing flagged it: a default is invisible to a stylesheet.
   --accent-dark is the palette's readable green in both themes (#135434 on
   light, #9fdcbb on dark), and is what the policy pages already used.
   :visited is set explicitly because otherwise it keeps the browser purple,
   which is worse in dark mode than the blue it replaces. Every classed link
   (.row, .chip, .button--*, .brand, .tabbar a) sets its own colour and has a
   higher specificity, so none of them changes. */
a{color:var(--accent-dark)}
a:visited{color:var(--accent-dark)}
.visually-hidden{position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
.skip-link{position:absolute;left:-9999px;background:var(--skip-bg);color:#fff;padding:var(--sp-10) var(--sp-16);z-index:30}
.skip-link:focus{left:0}
:focus-visible{outline:3px solid var(--focus);outline-offset:2px}
/* Keep controls clear of the sticky header and fixed bottom navigation when
   they are brought into view by keyboard focus, validation or automation. */
:is(a,button,input,select,textarea){scroll-margin-block:96px calc(96px + env(safe-area-inset-bottom,0px))}
/* index.html sets viewport-fit=cover, so the page is drawn into the display
   cutouts. Without the inset the fixed tab bar falls under the iPhone home
   indicator: its labels are hidden and taps near it are taken by the system
   gesture area. Only visible in the installed PWA, never in the browser. */
.shell{max-width:44rem;margin:0 auto;padding-bottom:calc(76px + env(safe-area-inset-bottom,0px))}
.topbar{position:sticky;top:0;z-index:10;background:var(--bg);border-bottom:1px solid var(--line)}
.topbar__inner{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-12);padding:var(--sp-10) var(--sp-16)}
.sync-state{display:flex;align-items:center;justify-content:flex-end;gap:7px;min-height:22px;padding:0 var(--sp-16) var(--sp-6);color:var(--muted);font-size:var(--text-xs);font-weight:600}
.sync-state__dot{width:8px;height:8px;flex:0 0 auto;border-radius:50%;background:var(--line-strong)}
.sync-state[data-state=saved] .sync-state__dot{background:var(--accent)}
.sync-state[data-state=saving] .sync-state__dot{background:var(--focus)}
.sync-state[data-state=offline] .sync-state__dot,.sync-state[data-state=error] .sync-state__dot,.sync-state[data-state=blocked] .sync-state__dot{background:var(--flag)}
.update-banner{display:flex;align-items:center;justify-content:center;gap:var(--sp-14);padding:var(--sp-8) var(--sp-16);background:var(--accent-soft);border-bottom:1px solid var(--accent);font-size:var(--text-s)}
.update-banner[hidden]{display:none}
/* The brand mark is the same tricolour ring the website uses, drawn from the
   same SVG paths rather than copied as an image, so it stays sharp at any size
   and both sites can only drift apart deliberately. "IGCSE" is green here as it
   is there. */
.brand{display:flex;align-items:center;gap:var(--sp-10);text-decoration:none;color:var(--ink);font-weight:700;font-size:var(--text-base)}
.brand__mark{width:30px;height:30px;flex:0 0 auto;display:block}
.brand strong{color:var(--accent);font-weight:700}
/* color is set explicitly, and that is the whole point of this comment.
   `font:inherit` looks like it covers text appearance and does not: colour is
   not part of the font shorthand, and a <button> does not inherit colour from
   its parent — the browser applies its own default. So this button drew its
   "Aa" in near-black on var(--surface). In light mode that is black on white
   and looks deliberate. In dark mode it was #000 on #1e231d, about 1.3:1, and
   effectively invisible. Reported from a screenshot on 22 August 2026. */
.icon-button{min-width:44px;min-height:44px;border:1px solid var(--line-strong);background:var(--surface);color:var(--ink);border-radius:var(--r-pill);font:inherit;cursor:pointer}
main{padding:var(--sp-16)}main:focus{outline:none}
.loading{padding:var(--sp-40) var(--sp-16);text-align:center;color:var(--muted)}
.page h1{font-size:var(--text-2xl);margin:0 0 var(--sp-6);line-height:1.3}
.lede{margin:0 0 var(--sp-16);color:var(--muted)}
.section-head{font-size:var(--text-base);margin:var(--sp-24) 0 var(--sp-8)}
.panel{background:var(--surface);border:1px solid var(--line);border-radius:var(--r);padding:var(--sp-16)}
.caveat{margin-top:14px;font-size:var(--text-s);color:var(--muted);font-style:italic}
.notice{margin-top:14px;padding:var(--sp-12) var(--sp-14);background:var(--warn-soft);border-left:5px solid var(--flag);border-radius:var(--r-sm);font-size:var(--text-sm)}
.status-banner{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--sp-12);margin-bottom:16px;padding:var(--sp-12) var(--sp-14);background:var(--accent-soft);border-left:5px solid var(--accent);border-radius:var(--r-sm);font-size:var(--text-sm)}
.status-banner p{margin:0}.status-banner button{flex:0 0 auto}
.muted{color:var(--muted)}
.rgrid{display:grid;gap:var(--sp-12);margin-top:12px}@media(min-width:640px){.rgrid{grid-template-columns:repeat(3,1fr)}}
.rcard{--c:var(--accent);background:var(--surface);border:1px solid var(--line);border-top:4px solid var(--c);border-radius:var(--r);padding:var(--sp-14) var(--sp-16)}
.rcard__head{display:flex;justify-content:space-between;align-items:baseline;gap:var(--sp-8)}
.rcard h3{margin:0;font-size:var(--text-base)}
.pill{font-size:var(--text-xs);font-weight:600;color:var(--muted);border:1px solid var(--line-strong);border-radius:var(--r-pill);padding:var(--sp-2) var(--sp-10);white-space:nowrap}
.meter{height:10px;background:var(--line);border-radius:var(--r-pill);overflow:hidden;margin:var(--sp-12) 0 var(--sp-8)}
.meter__fill{height:100%;background:var(--c,var(--accent));border-radius:var(--r-pill)}
.rcard__figure{margin:0}.rcard__figure strong{font-size:var(--text-2xl);line-height:1}
.rcard__note{margin:var(--sp-6) 0 0;font-size:var(--text-s);color:var(--muted)}
.cta{margin-top:20px;background:var(--surface);border:1px solid var(--line);border-left:5px solid var(--accent);border-radius:var(--r);padding:var(--sp-16)}
.cta__label{margin:0 0 var(--sp-4);font-size:var(--text-xs);text-transform:uppercase;letter-spacing:.06em;color:var(--muted)}
.cta h2{margin:0 0 var(--sp-4);font-size:var(--text-lg)}
.chips{display:flex;flex-wrap:wrap;gap:var(--sp-8);margin:var(--sp-10) 0}
/* The free/paid split for the selected subject, said out loud rather than
   hidden in a title attribute no phone will ever show. */
.chips__note{margin:var(--sp-8) 0 0;font-size:var(--text-s);color:var(--muted)}
.chip{display:inline-flex;align-items:center;min-height:44px;padding:var(--sp-8) var(--sp-16);border:1px solid var(--line-strong);border-radius:var(--r-pill);background:var(--surface);color:var(--ink);text-decoration:none;font:inherit;font-size:var(--text-sm);cursor:pointer}
.chip.is-on{border-color:var(--accent);background:var(--accent-soft);color:var(--accent-dark);font-weight:600}
.list{list-style:none;margin:0;padding:0}.list li+li{margin-top:8px}
.row{display:flex;align-items:center;gap:var(--sp-12);justify-content:space-between;background:var(--surface);border:1px solid var(--line);border-left:5px solid var(--line-strong);border-radius:var(--r-sm);padding:var(--sp-12) var(--sp-14);text-decoration:none;color:var(--ink);min-height:44px}
.row--done{border-left-color:var(--accent)}
.row__body{display:block}.row__body small{display:block;color:var(--muted);font-size:var(--text-s);margin-top:2px}
.button{display:inline-block;border:1px solid transparent;border-radius:var(--r-sm);padding:13px var(--sp-22);font:inherit;font-weight:600;cursor:pointer;text-decoration:none;text-align:center;min-height:48px}
.button--primary{background:var(--accent);border-color:var(--accent);color:var(--on-accent)}
.button--secondary{background:var(--surface);border-color:var(--line-strong);color:var(--ink)}
.etable{width:100%;border-collapse:collapse;margin-top:8px;font-size:var(--text-sm)}
.etable th,.etable td{text-align:left;padding:var(--sp-8) var(--sp-10);border-bottom:1px solid var(--line)}
.etable thead th{font-size:var(--text-xs);text-transform:uppercase;letter-spacing:.04em;color:var(--muted)}
.explain{margin:var(--sp-10) 0 0;padding-left:18px;font-size:var(--text-sm);color:var(--muted)}
.tabbar{position:fixed;bottom:0;left:0;right:0;display:flex;background:var(--surface);border-top:1px solid var(--line);z-index:10;padding-bottom:env(safe-area-inset-bottom,0px)}
.tabbar a{flex:1;display:flex;flex-direction:column;align-items:center;gap:var(--sp-2);padding:var(--sp-8) var(--sp-4);font-size:var(--text-s);color:var(--muted);text-decoration:none;min-height:56px;justify-content:center}
/* One optical size for the whole set. The Unicode glyphs this replaced
   measured 13, 13, 13.78, 16 and 21.97px wide in the app's own font stack, so
   the tab row was visibly uneven and the emoji padlock was a second idiom. */
.icon{width:22px;height:22px;flex:0 0 auto;display:inline-block;vertical-align:-0.18em;stroke:currentColor;fill:none}
.tabbar__icon{width:23px;height:23px;display:block}
.tabbar a[aria-current]{color:var(--accent-dark);font-weight:700;box-shadow:inset 0 3px 0 var(--accent)}
.exam__bar{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-10);margin-bottom:10px}
.clock{font-variant-numeric:tabular-nums;font-size:var(--text-xl);font-weight:700;padding:var(--sp-4) var(--sp-12);border-radius:var(--r-sm);background:var(--accent-soft);color:var(--accent-dark)}
.clock--low{background:var(--warn-soft);color:var(--warn-ink)}
.qnav{list-style:none;display:flex;flex-wrap:wrap;gap:var(--sp-6);margin:0 0 var(--sp-14);padding:0}
.qnav__b{width:38px;height:38px;border:1px solid var(--line-strong);background:var(--surface);border-radius:var(--r-sm);font:inherit;font-size:var(--text-sm);cursor:pointer}
.qnav__b.is-done{background:var(--accent-soft);border-color:var(--accent)}
.qnav__b.is-flag{border-color:var(--flag);border-width:2px}
.qnav__b.is-current{outline:3px solid var(--focus);outline-offset:1px;font-weight:700}
.qprompt{font-size:var(--text-base);margin:var(--sp-6) 0 var(--sp-14)}
.choices{display:flex;flex-direction:column;gap:var(--sp-8)}
/* One definition only. There used to be a second .choice rule further down,
   added for the feedback form, which silently re-centred the control against
   multi-line options on every question screen in the app. Options can run to
   three lines, so the control aligns with the first line of text instead. */
.choice{display:flex;gap:var(--sp-12);align-items:flex-start;padding:var(--sp-12);border:1px solid var(--line);border-radius:var(--r-sm);background:var(--surface);cursor:pointer;min-height:44px}
.choice>span{flex:1}
.choice:has(input:checked){border-color:var(--accent);background:var(--accent-soft);color:var(--accent-dark)}
textarea,input[type=text]{width:100%;font:inherit;padding:var(--sp-12);border:1px solid var(--line-strong);border-radius:var(--r-sm);background:var(--surface);color:var(--ink)}
.written{padding:var(--sp-12);background:var(--bg);border-radius:var(--r-sm);white-space:pre-wrap}
.exam__actions{display:flex;gap:var(--sp-10);margin-top:16px}.exam__actions .button{flex:1}
.dialog{border:1px solid var(--line-strong);border-radius:var(--r);padding:var(--sp-22);max-width:32rem;width:calc(100% - 32px);background:var(--surface);color:var(--ink)}
.dialog::backdrop{background:rgba(0,0,0,.45)}
.dialog fieldset{border:1px solid var(--line);border-radius:var(--r-sm);padding:var(--sp-12) var(--sp-14);margin:0 0 var(--sp-14)}
.option-row{display:flex;flex-wrap:wrap;gap:var(--sp-12)}.option-row label{display:flex;align-items:center;gap:var(--sp-8);min-height:44px}
.switch-row{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-14);padding:var(--sp-10) 0;border-bottom:1px solid var(--line);min-height:44px}
.switch-row small{display:block;color:var(--muted);font-size:var(--text-s)}
.dialog__actions{margin-top:16px}.dialog__actions .button{width:100%}
code{background:var(--bg);border:1px solid var(--line);border-radius:6px;padding:var(--sp-2) var(--sp-6);font-size:.9em}

/* ---- study, practice and checkpoint screens ---- */
.study__top{margin-bottom:18px}
.study__crumb{margin:var(--sp-6) 0 var(--sp-10);font-size:var(--text-s);color:var(--muted)}
.study__navline{display:flex;align-items:center;gap:var(--sp-12);flex-wrap:wrap;margin-bottom:6px}
.study__navline .study__crumb{margin:0}
.study__progress{margin-bottom:12px}
.study__progress-text{margin:var(--sp-6) 0 0;font-size:var(--text-s)}
.text-command{display:inline-flex;align-items:center;min-height:44px;background:none;border:0;padding:var(--sp-8) 0;font:inherit;font-size:var(--text-sm);color:var(--accent-dark);text-decoration:underline;cursor:pointer}
/* The step rail holds eight stages. At the old size the last one fell off the
   end on a laptop, so the rail looked complete while quietly hiding a step.
   Trimmed horizontal padding, gap and text size until all eight fit; the height
   stays at 44px because that is the minimum comfortable touch target and the
   problem was only ever horizontal. On a phone it still scrolls, which is the
   right behaviour there. */
/* The rail scrolls 608px of chips through 343px of screen. Its scrollbar sat
   about 100px below the real progress bar and read as a second one, so it is
   hidden and the overflow is signalled by a fade at the right edge instead.
   The fade is drawn in the page background colour, so it is invisible when the
   chips happen to fit. "Step 3 of 8" above the rail carries the count. */
.step-rail-wrap{position:relative}
.step-rail-wrap::after{content:"";position:absolute;top:0;right:0;bottom:6px;width:32px;pointer-events:none;background:linear-gradient(to right,transparent,var(--bg))}
.step-rail{list-style:none;display:flex;gap:var(--sp-4);max-width:100%;overflow-x:auto;margin:0 0 var(--sp-4);padding:var(--sp-4) var(--sp-4) var(--sp-6);scroll-padding-inline:4px;scrollbar-width:none}
.step-rail::-webkit-scrollbar{display:none}
.step-rail>li{flex:0 0 auto}
.step-link{display:flex;align-items:center;gap:var(--sp-4);white-space:nowrap;border:1px solid var(--line-strong);background:var(--surface);color:var(--ink);border-radius:var(--r-pill);padding:var(--sp-8) 9px;font:inherit;font-size:var(--text-xs);cursor:pointer;min-height:44px}
.step-link.is-complete{border-color:var(--accent);background:var(--accent-soft);color:var(--accent-dark)}
.step-link[aria-current=step]{outline:3px solid var(--focus);outline-offset:1px;font-weight:700}
.step-link:disabled{opacity:.45;cursor:default}
.step-link__number{font-weight:700}
.screen-heading{margin-bottom:14px}
.screen-heading h1{font-size:var(--text-xl);margin:var(--sp-4) 0 var(--sp-6);line-height:1.3}
.eyebrow{margin:0;font-size:var(--text-xs);text-transform:uppercase;letter-spacing:.06em;color:var(--muted);font-weight:700}
.study__actions{display:flex;gap:var(--sp-10);margin-top:20px}.study__actions .button{flex:1}
/* Back is a way out, not a peer of the primary action, so it stops taking half
   the row and half the thumb path. */
.study__actions .button--secondary{flex:0 1 auto;min-width:104px}
/* On a phone a question screen used to end around 490px of an 812px viewport,
   leaving the easy-reach bottom third empty while the answer field and the
   primary button sat in the middle. The task pages now fill the viewport and
   push their action row to the foot of it, so the button lands under the thumb
   and the question keeps the top of the screen. Long questions scroll as
   before, because this is a minimum rather than a fixed height. */
/* Finding 20: the tab bar is a phone pattern. On a laptop it stretched the full
   window width — five items spread across 1265px at the very bottom, while the
   content sat in a 44rem column in the middle. Constrained to that same column,
   with its top corners rounded so it reads as part of the column rather than a
   strip stuck to the window. */
@media(min-width:641px){
  .tabbar{left:50%;right:auto;transform:translateX(-50%);width:min(44rem,100%);
    border:1px solid var(--line);border-bottom:0;
    border-radius:var(--r) var(--r) 0 0}
}
@media(max-width:640px){
  /* No magic constant: the shell already reserves the tab bar's height as its
     own bottom padding, so making it a full-height flex column and letting
     main take the slack puts the action row exactly above the bar. An earlier
     attempt subtracted a hand-measured 172px and overlapped it by 8px. */
  .shell{display:flex;flex-direction:column;min-height:100vh;min-height:100dvh}
  main{flex:1 1 auto;display:flex;flex-direction:column}
  .page--task{flex:1 1 auto;display:flex;flex-direction:column}
  .page--task .study__actions{margin-top:auto;padding-top:18px}
}
.study__hint{margin:var(--sp-10) 0 0;font-size:var(--text-s)}
.objective-list,.key-point-list,.reasoning-list{margin:0;padding-left:20px}
.objective-list li,.key-point-list li,.reasoning-list li{margin-bottom:8px}
.media-frame{margin:0 0 var(--sp-16);border:1px solid var(--line);border-radius:var(--r);overflow:hidden;background:var(--surface)}
.media-frame img{display:block;width:100%;height:auto}
.media-frame figcaption{padding:var(--sp-10) var(--sp-14);font-size:var(--text-s);color:var(--muted)}
.video-player{width:100%;aspect-ratio:16/9;border:0;border-radius:var(--r);display:block;margin-bottom:14px}
.video-placeholder{background:var(--surface);border:1px dashed var(--line-strong);border-radius:var(--r);padding:var(--sp-28) var(--sp-20);text-align:center;margin-bottom:14px}
.video-placeholder h2{margin:0 0 var(--sp-6);font-size:var(--text-base)}
.video-privacy{max-width:34rem;margin:var(--sp-14) auto 0;font-size:var(--text-s);line-height:1.55}
.concept-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(9rem,1fr));gap:var(--sp-8);margin-bottom:14px}
.concept-button{min-height:44px;padding:var(--sp-10) var(--sp-12);border:1px solid var(--line-strong);border-radius:var(--r-sm);background:var(--surface);color:var(--ink);font:inherit;font-size:var(--text-sm);cursor:pointer;text-align:left}
.concept-button.is-viewed{border-color:var(--accent);background:var(--accent-soft);color:var(--accent-dark)}
.concept-button[aria-pressed=true]{outline:3px solid var(--focus);outline-offset:1px;font-weight:700}
.concept-detail h2{font-size:var(--text-base);margin:var(--sp-4) 0 var(--sp-8);line-height:1.4}
/* The question used to render at 1.02rem while the topic name above it was a
   1.5rem bold h1 — the context you already have shouting over the thing you
   have to answer. The question now carries the weight. */
.question-panel legend{font-size:var(--text-lg);font-weight:600;padding:0;margin-bottom:12px;line-height:1.35}
.page h1.qtitle{font-size:var(--text-xl);font-weight:700;line-height:1.3;margin:var(--sp-2) 0 var(--sp-14);text-wrap:balance}
/* The context line above a question. Not .eyebrow: uppercase with letter
   spacing turned a topic title plus a section title plus a specification
   reference into three shouting lines, which is louder than the question. */
.qcontext{margin:0 0 var(--sp-8);font-size:var(--text-s);line-height:1.45;color:var(--muted)}
/* The checkpoint brief was a full panel above the first question, which put
   the question itself under the fold on a phone. Same three lines, about 60px
   less of them. */
.checkpoint-brief{margin:0 0 var(--sp-14);padding:var(--sp-10) var(--sp-12);border-left:3px solid var(--line-strong);background:var(--bg);border-radius:0 var(--r-sm) var(--r-sm) 0;font-size:var(--text-sm);color:var(--muted)}
.checkpoint-brief summary{cursor:pointer;font-weight:600;color:var(--ink);min-height:24px}
.checkpoint-brief summary:focus-visible{outline:3px solid var(--focus);outline-offset:2px}
.checkpoint-brief ul{margin:var(--sp-8) 0 0;padding-left:var(--sp-18)}
.checkpoint-brief li+li{margin-top:3px}
.qnav{margin:var(--sp-16) 0 0}
.question-panel fieldset{border:0;padding:0;margin:0}
/* Feedback never relies on colour alone: the word Correct or Review carries it. */
.feedback{margin-top:14px;padding:var(--sp-12) var(--sp-14);border-radius:var(--r-sm);background:var(--accent-soft);border-left:5px solid var(--accent);color:var(--accent-dark)}
.feedback--review{background:var(--warn-soft);border-left-color:var(--flag);color:var(--warn-ink)}
.feedback strong{display:block;margin-bottom:4px}
/* Two terms a student keeps confusing, side by side. Without a rule between
   them the two definitions read as one paragraph that happens to wrap oddly,
   which is the opposite of the point: the whole stage exists to hold the pair
   apart. The line only appears once the columns do; stacked on a phone, the gap
   already separates them and a horizontal rule would suggest a section break. */
.comparison__pair{display:grid;gap:var(--sp-12)}
@media(min-width:560px){
  .comparison__pair{grid-template-columns:1fr 1fr;gap:var(--sp-24)}
  .comparison__pair>section+section{border-left:1px solid var(--line);padding-left:24px}
}
.comparison__pair h3{margin:0 0 var(--sp-4);font-size:var(--text-base)}
.comparison__difference{margin:var(--sp-12) 0 0;padding-top:12px;border-top:1px solid var(--line)}
.scenario{font-size:var(--text-base)}
.reveal-panel{border-left:5px solid var(--accent)}
.result-score{text-align:center}
.result-score--review{border-left:5px solid var(--flag)}
.result-list{list-style:none;margin:0;padding:0}
.result-item{margin-bottom:10px}
.result-item h3{margin:var(--sp-6) 0 var(--sp-6);font-size:var(--text-base);line-height:1.4}
.result-item__status{display:inline-block;font-size:var(--text-xs);font-weight:700;text-transform:uppercase;letter-spacing:.05em;padding:3px var(--sp-10);border-radius:var(--r-pill);background:var(--accent-soft);color:var(--accent-dark)}
.result-item__status.is-review{background:var(--warn-soft);color:var(--warn-ink)}
.result-item__status.is-self-reviewed{background:var(--surface);border:1px solid var(--accent);color:var(--accent-dark)}
.result-answer{margin:var(--sp-12) 0 var(--sp-8);border-top:1px solid var(--line)}
.result-answer div{display:grid;grid-template-columns:minmax(7rem,9rem) 1fr;gap:var(--sp-12);padding:var(--sp-8) 0;border-bottom:1px solid var(--line)}
.result-answer dt{font-weight:700;font-size:var(--text-s);color:var(--muted)}
.result-answer dd{margin:0;white-space:pre-wrap;overflow-wrap:anywhere}
.result-review__action{display:inline-flex;align-items:center;margin-top:2px}
.result-review__note{margin:var(--sp-8) 0 0;font-size:var(--text-s);color:var(--muted)}
.result-next{margin-top:24px;padding-top:18px;border-top:1px solid var(--line)}
.result-next h2{font-size:var(--text-base);margin:0 0 var(--sp-6)}
.result-next p{margin:0}
.result-back{display:inline-block;margin-top:16px}
.course-progress{margin:var(--sp-14) 0 var(--sp-18)}
@media(max-width:520px){
  .result-answer div{display:block}.result-answer dd{margin-top:2px}
  .result-next .study__actions{flex-direction:column}
}
.mode-list{display:grid;gap:var(--sp-10);margin-bottom:6px}
.mode-card{display:flex;flex-direction:column;gap:var(--sp-4);text-align:left;padding:var(--sp-14) var(--sp-16);border:1px solid var(--line);border-left:5px solid var(--line-strong);border-radius:var(--r-sm);background:var(--surface);color:var(--ink);font:inherit;cursor:pointer;min-height:44px}
.mode-card.is-selected{border-left-color:var(--accent);background:var(--accent-soft)}
.mode-card:disabled{opacity:.5;cursor:default}
.mode-card span{font-size:var(--text-s);color:var(--muted)}
.mode-card.is-selected span{color:var(--accent-dark)}
.topic-select{width:100%;font:inherit;padding:var(--sp-12);border:1px solid var(--line-strong);border-radius:var(--r-sm);background:var(--surface);color:var(--ink);min-height:44px}
.matching-list{display:grid;gap:var(--sp-10)}
.matching-row{display:grid;gap:var(--sp-6)}
.matching-row select{font:inherit;padding:var(--sp-10);border:1px solid var(--line-strong);border-radius:var(--r-sm);background:var(--surface);color:var(--ink);min-height:44px}
.row--checkpoint{border-left-color:var(--accent-dark);border-left-style:dashed}
/* Exam paper stem: stays on screen for every part of its question. */
.exam__stem{border-left:5px solid var(--accent);margin-bottom:12px}
.exam__stem--continued{opacity:.85}
.exam__stem p:last-child{margin-bottom:0}
/* A thicker left edge marks where a new main question starts in the navigator. */
.qnav__b--group{border-left-width:3px;border-left-color:var(--accent-dark)}
.qnav__b{min-width:38px;width:auto;padding:0 var(--sp-6)}
details summary{cursor:pointer;min-height:44px;display:flex;align-items:center;font-weight:600}
.qnav__b.is-done{background:var(--accent-soft);border-color:var(--accent)}
span.qnav__b{display:inline-flex;align-items:center;justify-content:center;cursor:default}

/* Account control in the top bar */
.topbar__actions{display:flex;align-items:center;gap:var(--sp-8);flex:0 0 auto}
/* A pill in the header never wraps. "Create account" broke onto two lines at
   every width below 768px, which made the whole bar 63px tall instead of 40px
   and left a 92px button with a ragged label. Measured 18 September 2026. */
.button--sm{min-height:40px;padding:var(--sp-8) var(--sp-14);font-size:var(--text-sm);border-radius:var(--r-pill);white-space:nowrap}

/* THE HEADER ON A PHONE, measured rather than guessed.
   Three controls plus the word-mark do not fit. At 360px the bar overflowed to
   362px with "Create account" on two lines. The word-mark is what gives way:
   the tricolour ring is the brand and still links home, and every pixel it
   frees goes to the two buttons the person actually came to press. Below 400px
   the pills also tighten, which is what buys the last 24px at 320px. */
@media(max-width:599px){
  .brand__name{display:none}
}
@media(max-width:399px){
  .topbar__inner{gap:var(--sp-8);padding-left:var(--sp-12);padding-right:var(--sp-12)}
  .topbar__actions{gap:var(--sp-6)}
  .topbar__actions .button--sm{padding:var(--sp-8) var(--sp-10);font-size:var(--text-s)}
}
.button--ghost{background:var(--surface);border-color:var(--line-strong);color:var(--accent-dark)}
.button--link{background:none;border:none;color:var(--accent-dark);text-decoration:underline;min-height:44px;padding:var(--sp-8) 0;font-weight:600;cursor:pointer}
.support-list{display:grid;gap:var(--sp-12);margin-top:16px}.support-list .panel h2{margin:0 0 5px;font-size:var(--text-base)}.support-list .panel p{margin:0}

/* Locks on paid content */
/* Slightly smaller than a standalone icon: it sits inside a line of text
   rather than on its own, and it is decoration — the locked state is spoken by
   the .sr-only wording beside it. */
.lock{width:16px;height:16px;vertical-align:-0.15em;margin-left:2px;color:var(--flag)}
/* Visually hidden but read aloud. The lock glyph is aria-hidden decoration, so
   without this a screen reader user would hear the section title and nothing
   about it being locked. */
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}
/* Locked practice scopes. Colour alone must never carry this — the lock glyph,
   the "Subscription" tag and the sr-only text each say it independently, and
   the tag keeps working with colour filters or a monochrome display.
   Deliberately no background change here: setting a background without also
   setting a colour is exactly how the dark-mode icon-button fault happened. */
.chip--locked{border-style:dashed;color:var(--muted)}
.topic-option--locked{color:var(--muted)}
.topic-option--locked:hover{background:none}
.topic-option--locked input{cursor:not-allowed}
.topic-option__tag{display:inline-block;margin-left:6px;font-size:var(--text-xs);font-weight:700;
  text-transform:uppercase;letter-spacing:.05em;padding:var(--sp-2) var(--sp-8);border-radius:var(--r-pill);
  border:1px solid var(--line-strong);background:var(--bg);color:var(--muted);white-space:nowrap}
/* A locked row used to differ from a free one by a dashed left edge, slightly
   muted title text and a small padlock — while keeping the same chevron, which
   says "opens" rather than "costs money". A student could not scan the list for
   what they could actually do. Tinted ground, a warning-coloured edge, and a
   pill that names the action instead of the chevron. Full opacity: these rows
   are the offer, so dimming them was working against the paywall. */
.row--locked{border-left-style:solid;border-left-color:var(--flag);background:var(--warn-soft)}
.row--locked strong{color:var(--ink)}
.row__unlock{flex:0 0 auto;font-size:var(--text-xs);font-weight:700;letter-spacing:.04em;
  text-transform:uppercase;color:var(--warn-ink);border:1px solid var(--warn-ink);
  border-radius:var(--r-pill);padding:3px var(--sp-10);white-space:nowrap}

/* Paywall */
.eyebrow{margin:0 0 var(--sp-4);font-size:var(--text-xs);text-transform:uppercase;letter-spacing:.06em;color:var(--muted);font-weight:600}
.plans{display:grid;gap:var(--sp-12);margin:var(--sp-12) 0}@media(min-width:640px){.plans{grid-template-columns:1fr 1fr}}
.plan{position:relative}
.plan h2{margin:0 0 var(--sp-4);font-size:var(--text-lg)}
.plan__price{margin:0 0 var(--sp-2);font-size:var(--text-3xl);font-weight:700;line-height:1;color:var(--accent-dark)}
.plan__per{font-size:var(--text-sm);font-weight:400;color:var(--muted)}
.consent{display:flex;gap:var(--sp-10);align-items:flex-start;margin:var(--sp-18) 0;font-size:var(--text-sm);line-height:1.5}
.consent input{margin-top:3px;width:20px;height:20px;flex:0 0 auto}
.plan--feature{border-color:var(--accent);border-top:4px solid var(--accent)}
.plan__flag{position:absolute;top:-10px;right:14px;margin:0;background:var(--accent);color:var(--on-accent);font-size:var(--text-xs);font-weight:700;padding:3px var(--sp-10);border-radius:var(--r-pill)}
.plan .button{margin-top:12px;width:100%}
/* The only part of the paywall addressed to the person who pays. Its own
   block rather than another caveat, because a caveat is what it used to be. */
.handoff{margin-top:20px}
.handoff h2{margin:0 0 var(--sp-6);font-size:var(--text-base)}
.handoff p{margin:0 0 var(--sp-10)}
.handoff__actions{display:flex;flex-wrap:wrap;gap:var(--sp-10);margin:var(--sp-12) 0 0}
.handoff__actions .button{flex:1 1 auto}
.handoff .caveat{margin-top:12px}
.ticks{list-style:none;margin:var(--sp-16) 0 0;padding:0}
.ticks li{padding:var(--sp-6) 0 var(--sp-6) var(--sp-28);position:relative}
.ticks li::before{content:"✓";position:absolute;left:4px;color:var(--accent);font-weight:700}

/* Account forms */
.form{margin-top:16px}
.form+.form{margin-top:14px}
.form h2{margin:0 0 var(--sp-10);font-size:var(--text-lg)}
.form label{display:block;font-weight:600;margin:var(--sp-10) 0 var(--sp-4)}
.form input{width:100%;font:inherit;padding:var(--sp-12);min-height:48px;border:1px solid var(--line-strong);border-radius:9px;background:var(--surface);color:var(--ink)}
.form .button{margin-top:14px}
.form__hint{margin:var(--sp-6) 0 0;font-size:var(--text-s);color:var(--muted)}
.form__msg{margin:var(--sp-12) 0 0;padding:var(--sp-10) var(--sp-12);border-radius:var(--r-sm);background:var(--warn-soft);border-left:5px solid var(--flag);font-size:var(--text-sm)}
.form__msg--ok{background:var(--accent-soft);border-left-color:var(--accent)}
/* Spam trap. display:none rather than off-screen positioning, because a browser
   will happily autofill a field it cannot see but will not touch one that is
   not laid out at all — and a real person tripping this trap means a real
   complaint goes nowhere. The class this markup originally carried,
   .subscribe__trap, belongs to the marketing site and was never defined here,
   so the field rendered in plain sight until 21 August 2026. */
.form__trap{display:none}

/* Multi-select topic list for practice. Checkboxes rather than a <select
   multiple>, which on a phone needs a long press or a modifier key nobody has.
   Scrolls rather than pushing the start button off the bottom of a long
   subject, and every row is a full-width target. */
.topic-list{max-height:min(46vh,420px);overflow-y:auto;border:1px solid var(--line);border-radius:var(--r);padding:var(--sp-4);background:var(--surface)}
.topic-option{display:flex;align-items:flex-start;gap:var(--sp-12);padding:var(--sp-10) var(--sp-12);min-height:44px;cursor:pointer;border-radius:var(--r-sm);color:var(--ink)}
.topic-option:hover{background:var(--accent-soft)}
.topic-option:focus-within{outline:2px solid var(--focus);outline-offset:-2px}
.topic-option input{width:20px;height:20px;flex:0 0 auto;margin-top:2px;accent-color:var(--accent)}
.topic-option span{font-size:var(--text-sm);line-height:1.4}
.chip[disabled]{opacity:.45;cursor:not-allowed}

/* Exam: the paused clock and the two ways out.
   The paused state has to be obvious. A stopped clock that looks identical to a
   running one is worse than no clock, because the student cannot tell whether
   the time they are taking is being counted. */
.clock--paused{opacity:.7;font-style:italic}
.exam__leave{display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-12);margin-top:20px;padding-top:16px;border-top:1px solid var(--line)}
.exam__leave p{flex:1 1 100%;margin:0;font-size:var(--text-s)}
/* The default .form__msg already carries the warning colours. --error names
   that intent so the JS does not rely on a bare class meaning "problem". */
.form__msg--error{background:var(--warn-soft);border-left-color:var(--flag)}

/* Optional features: the consent screen and the account toggles.
   The checkbox is deliberately large and the whole title is a label, because
   these choices are made by 14- to 16-year-olds, often one-handed on a phone,
   and a mis-tap here turns something on that sends their data somewhere. */
.panel--choice{margin:var(--sp-14) 0}
.panel--choice p{margin:var(--sp-8) 0 0}
.panel--quiet{background:transparent;border-style:dashed}
/* An optional feature that is off, or a route to one, said once and quietly.
   Readable at body size rather than shrunk into a caveat: the offer has to
   stay genuinely available, it just should not outshout the screen. */
.feature-hint{margin:var(--sp-16) 0 0;font-size:var(--text-base);line-height:1.5}
.feature-hint a{font-weight:600}
/* Score rows that start a practice set on that topic. */
.etable__action{display:block;width:100%;text-align:left;background:none;border:0;padding:0;font:inherit;color:var(--accent-dark);font-weight:600;cursor:pointer;text-decoration:underline}
.etable__action:hover{text-decoration:none}
/* Radios were left at the browser default of 13px while the checkboxes beside
   them were sized to 22px, so the primary answer control on every multiple
   choice question was the one element that looked unstyled. */
.choice input[type=checkbox],.choice input[type=radio]{width:22px;height:22px;flex:0 0 auto;margin-top:1px;accent-color:var(--accent)}
.choice__title{font-weight:700;font-size:var(--text-base)}
.choice:focus-within .choice__title{outline:2px solid var(--focus);outline-offset:3px;border-radius:4px}

/* Terms acceptance on the sign-up form, and the first-run welcome.
   The tick box is a 24px target inside a 44px row, sits above the button
   rather than beside it, and never wraps under its own label.

   The class is doubled to raise its specificity, which is not decoration.
   `.form label` above sets display:block and scores (0,1,1); a single
   `.consent-row` scores (0,1,0) and loses, whatever the order in this file.
   The row was therefore rendering as a block with the tick box inline in the
   sentence: measured in Chromium at 390px, the first line of the label started
   at x=84 and the following two lines at x=47, wrapping underneath the box —
   exactly the thing this note says must not happen. Doubling the class scores
   (0,2,0), which wins, and keeps working if the row is ever used on something
   other than a label. */
.consent-row.consent-row{display:flex;align-items:flex-start;gap:var(--sp-12);min-height:44px;padding:var(--sp-12);margin:var(--sp-4) 0 var(--sp-8);border:2px solid var(--line-strong);border-radius:var(--r-sm);background:var(--surface);color:var(--ink);cursor:pointer}
.consent-row input{width:24px;height:24px;flex:0 0 auto;margin-top:1px;accent-color:var(--accent)}
.consent-row span{font-size:var(--text-base);line-height:1.45}
.consent-row:focus-within{outline:2px solid var(--focus);outline-offset:2px}

/* Sign in, step two.
   The address typed on step one, shown back with a way to correct it. A long
   address on a narrow phone wraps inside its own line rather than pushing the
   Change button off the edge, and the button keeps the 44px target every other
   link button here has.

   align-items is center rather than baseline because that is what the browser
   actually does with a 26px text span next to a 44px button: measured in
   Chromium at 360, 390, 768 and 1280, the two are centred to within 0.0px
   either way, while the text baselines sit 6.4px apart. Declaring center says
   what happens. */
.account-who{display:flex;flex-wrap:wrap;align-items:center;column-gap:var(--sp-12);margin:0 0 var(--sp-4)}
.account-who__value{font-weight:600;overflow-wrap:anywhere}

/* The switch between signing in and creating an account.
   The app deliberately does not look up whether an address is registered — see
   the note in screenAccount — so this is how a person says which they are. It
   sits below the form, separated by a rule, so it reads as the other way on
   rather than as part of the form above it. */
/* The primary action and its alternative, side by side and equal in size.
   They wrap to two full-width rows when the pair will not fit, which on a
   360px phone is where they end up. `.form .button` sets margin-top:14px and
   scores (0,1,1), so the override is written at (0,2,1) rather than left to
   source order - the same specificity trap that caught .consent-row and
   .qtitle earlier this month. */
.form .form__actions{display:flex;flex-wrap:wrap;gap:var(--sp-10);margin-top:var(--sp-14)}
.form .form__actions .button{flex:0 1 auto;margin-top:0}
/* On a 360px Android the untrimmed pair needs 301px against 294px of panel and
   drops to two rows. Trimming the side padding, rather than the type, buys the
   32px that keeps them side by side: the label stays at full size and the tap
   target stays 54px tall. Below about 340px they stack, which is the right
   answer there rather than two cramped buttons. */
@media(max-width:413px){
  .form .form__actions{gap:var(--sp-8)}
  .form .form__actions .button{padding-left:var(--sp-14);padding-right:var(--sp-14)}
}
/* Forgotten your password sits under the pair, on its own line, left aligned
   with them. Block rather than inline so it cannot end up beside a wrapped
   button and read as a third equal choice. */
.form .form__aside{display:block;width:100%;margin-top:var(--sp-10);text-align:left}
.welcome-step{counter-increment:welcome;margin-bottom:20px}
.welcome-step h2::before{content:counter(welcome) ". ";color:var(--muted)}
.welcome{counter-reset:welcome}
.howto{margin:0;padding:0;list-style:none}
.howto li{display:flex;gap:var(--sp-12);align-items:flex-start;padding:var(--sp-10) 0;border-bottom:1px solid var(--line)}
.howto li:last-child{border-bottom:0}
.howto__icon{flex:0 0 auto;width:22px;height:22px;margin-top:1px;color:var(--accent)}
.howto strong{display:block;color:var(--ink)}
.howto span{font-size:var(--text-sm);line-height:1.45}

/* Feedback form.
   .option-row is a horizontal row of short choices. The feedback categories
   carry a sentence of explanation each, so they need to stack and fill the
   width instead. A fieldset needs its default border and padding removing
   before it will sit like the rest of the form. */
.option-row--stack{flex-direction:column;gap:var(--sp-6)}
.option-row--stack .topic-option{width:100%;border:2px solid var(--line);align-items:flex-start}
.option-row--stack .topic-option:has(input:checked){border-color:var(--accent);background:var(--accent-soft);color:var(--ink)}
.form__group{border:0;margin:0 0 var(--sp-16);padding:0;min-width:0}
.form__group legend{padding:0;font-weight:700;color:var(--ink)}

@media(prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important}}

/* ---- First-run tour ------------------------------------------------------
   Six short steps for a student opening the app for the first time, ending at
   the first marked answer. See tour.js for why it exists and how it chooses.

   It is a card and a ring, not an overlay. Nothing is dimmed and nothing is
   blocked: a student who wants to get on with it can, and a student who never
   reads it loses nothing. Dimming the app would also mean dimming the very
   thing each step is pointing at.

   Every value here is a token from the sheet above, so the card is the app's
   own panel with a different job rather than a new visual idiom. */

.tour{
  position:fixed;left:50%;transform:translateX(-50%);
  z-index:60;                      /* above the tab bar, below the settings dialog */
  width:min(28rem,calc(100vw - var(--sp-32)));
  background:var(--surface);color:var(--ink);
  border:1px solid var(--line-strong);border-radius:var(--r-lg);
  padding:var(--sp-16) var(--sp-20) var(--sp-14);
  box-shadow:0 10px 30px rgb(0 0 0 / .18);
}
.tour__progress{
  margin:0 0 var(--sp-4);font-size:var(--text-xs);letter-spacing:.04em;
  text-transform:uppercase;color:var(--muted);
}
.tour__title{margin:0 0 var(--sp-6);font-size:var(--text-lg);line-height:1.25}
.tour__body{margin:0 0 var(--sp-14);font-size:var(--text-sm);line-height:1.5;color:var(--ink)}
.tour__actions{display:flex;gap:var(--sp-8);justify-content:flex-end;flex-wrap:wrap}

/* The ring. outline rather than border so nothing reflows when it appears:
   a border would move the very element the student is being asked to look at.
   outline-offset keeps it clear of the element's own focus ring. */
.tour-target{
  outline:3px solid var(--accent);outline-offset:3px;
  border-radius:var(--r-sm);
}

/* The card is drawn below its target where there is room and above it
   otherwise, which is decided in tour.js. These only supply the fallback so a
   card is never stuck to the wrong edge if the script sets neither. */
.tour[data-side="below"]{bottom:auto}
.tour[data-side="above"]{top:auto}

@media (prefers-reduced-motion:no-preference){
  .tour{animation:tour-in .18s ease-out both}
  @keyframes tour-in{from{opacity:0;transform:translateX(-50%) translateY(4px)}}
}

/* Narrow screens: the card spans the width it is given and the buttons stop
   competing for a single line. 320px is the narrowest phone still in use. */
@media (max-width:22.5rem){
  .tour{padding:var(--sp-14) var(--sp-16) var(--sp-12)}
  .tour__actions{justify-content:stretch}
  .tour__actions .button{flex:1}
}
