/* tokens.css */
/* Tinystudy design tokens: one place for colour, type, space, radius, elevation and motion. Loaded before app.css and every other stylesheet.
   Dark is the default and light only when the student picks it in Settings (owner 2026-10-04; theme.js sets data-theme): the device's
   own light or dark setting is never followed, so no rule here or in app.css reads prefers-color-scheme. */
:root {
  color-scheme: dark;
  /* colour (light, dark) */
  --bg: light-dark(#f7f8fb, #0f1420);       --surface: light-dark(#ffffff, #171e2e);   --line: light-dark(#e1e5ec, #34405a);
  --ink: light-dark(#172033, #e8ecf3);      --muted: light-dark(#566074, #a3aec2);
  --accent: light-dark(#1f5eff, #7aa2ff);   --on-accent: light-dark(#ffffff, #0b1020);
  --fill: light-dark(#3a62c7, #7aa2ff);      /* the filled button: a calmer blue than --accent in light (white text on it is still 5.6:1), the same soft blue as --accent in dark (loop L04-A5) */
  --accent-soft: color-mix(in srgb, var(--accent) 10%, var(--surface));
  --on-ok: light-dark(#ffffff, #0b1020);   --ok: light-dark(#157347, #4cc38a);       --warn: light-dark(#9a3d06, #f0a35e);      --bad: light-dark(#b42318, #ff8a80);
  --scrim: rgb(0 0 0 / .4);
  --off: light-dark(#7a8496, #6b7686);      /* switch off state and other UI parts, at least 3:1 */
  --field-line: var(--off);   /* the edge of a text box, a list and a text area: at least 3:1 against the card in both themes (WCAG 1.4.11; tests/test_tokens_contrast.py) */
  --ok-soft: color-mix(in srgb, var(--ok) 12%, var(--surface));   --warn-soft: color-mix(in srgb, var(--warn) 12%, var(--surface));
  --bad-soft: color-mix(in srgb, var(--bad) 12%, var(--surface));
  /* type: five sizes, two weights */
  --fs-xs: .75rem; --fs-sm: .875rem; --fs-md: 1rem; --fs-lg: 1.25rem; --fs-xl: 1.75rem;
  --fw: 400; --fw-b: 600; --lh: 1.5; --lh-t: 1.2;
  /* space (4 pt) */
  --s1: 4px; --s2: 8px; --s3: 12px; --s4: 16px; --s5: 24px; --s6: 32px; --s7: 48px;
  /* radius */
  --r-s: 8px; --r-m: 16px; --r-pill: 999px;
  /* elevation (dark: none, the 1 px line carries it) */
  --e0: none; --e1: 0 1px 2px rgb(16 24 40 / .06), 0 4px 12px rgb(16 24 40 / .06); --e2: 0 12px 32px rgb(16 24 40 / .16);
  /* motion */
  --d-fast: 120ms; --d-base: 200ms; --d-slow: 320ms; --d-cele: 700ms;
  --ease-out: cubic-bezier(.05, .7, .1, 1); --ease-in: cubic-bezier(.3, 0, .8, .15); --ease-std: cubic-bezier(.2, 0, 0, 1); --ease-io: cubic-bezier(.4, 0, .2, 1);
  /* layout */
  --tap: 44px; --content: 640px; --header-h: 56px; --bar-h: 56px; --safe-b: env(safe-area-inset-bottom, 0px);
  --focus: 2px solid var(--accent);
  /* old names, kept as aliases so welcome.css, guide.css, loading.css, lessons.css, reader.css keep working until their lanes migrate */
  --card: var(--surface); --accent-ink: var(--on-accent); --grey: var(--off); --radius: var(--r-m); --shadow: var(--e1);   /* aliases are removed in loop 15 */
}
:root:not([data-theme="light"]) { --e1: none; --e2: 0 12px 32px rgb(0 0 0 / .5); color-scheme: dark; }
:root[data-theme="light"] { color-scheme: light; }
@media (prefers-reduced-motion: reduce) {
  :root { --d-fast: 0ms; --d-base: 0ms; --d-slow: 0ms; --d-cele: 0ms; }
  *, *::before, *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; scroll-behavior: auto !important; }
}

/* one focus look everywhere */
:focus-visible { outline: var(--focus); outline-offset: 2px; }

@keyframes ts-fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes ts-rise-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@keyframes ts-pop { 0% { transform: scale(.8); } 60% { transform: scale(1.1); } 100% { transform: scale(1); } }
@keyframes ts-spin { to { transform: rotate(360deg); } }
.ts-fade-in { animation: ts-fade-in var(--d-base) var(--ease-out) both; animation-iteration-count: 1; }
.ts-rise-in { animation: ts-rise-in var(--d-base) var(--ease-out) both; animation-iteration-count: 1; }
.ts-pop { animation: ts-pop var(--d-base) var(--ease-out) both; animation-iteration-count: 1; }
.ts-sr { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }

/* app.css */
/* Colours, sizes, radii, shadows and durations come from tokens.css (loaded first). */
* { box-sizing: border-box; }
[hidden] { display: none !important; }   /* the hidden attribute must win over element rules such as label { display: block } */
body { margin: 0; background: var(--bg); color: var(--ink); font: var(--fw) var(--fs-md)/var(--lh) system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; padding-inline: var(--s4); padding-block: 0 var(--s5); }
a { color: var(--accent); }
h1 { font-size: var(--fs-xl); font-weight: var(--fw-b); line-height: var(--lh-t); margin: var(--s4) 0 var(--s2); }
h2 { font-size: var(--fs-md); font-weight: var(--fw-b); margin: var(--s4) 0 var(--s2); }
h3, h4 { font-size: var(--fs-md); font-weight: var(--fw-b); }
small, code, kbd, samp, pre { font-size: var(--fs-sm); }
b, strong { font-weight: var(--fw-b); }
.muted { color: var(--muted); }
.small { font-size: var(--fs-sm); }
.skip { position: absolute; left: -999px; }
.skip:focus { left: var(--s2); top: var(--s2); background: var(--surface); padding: var(--s2) var(--s3); z-index: 10; }

.top, main, .foot { max-width: var(--content); margin-inline: auto; }
.top { display: flex; align-items: center; gap: var(--s4); flex-wrap: wrap; padding: var(--s3) 0; border-bottom: 1px solid var(--line); position: sticky; top: 0; background: var(--bg); z-index: 5; }
.brand { display: flex; align-items: center; gap: var(--s2); font-weight: var(--fw-b); font-size: var(--fs-md); }
.wordmark { height: 28px; width: auto; display: block; color: var(--ink); }
.brand a.brandlink { display: inline-flex; color: var(--ink); }
.tabs { display: flex; gap: var(--s1); flex-wrap: wrap; }
.tab { border: 0; background: transparent; color: var(--muted); padding: var(--s2) var(--s3); border-radius: var(--r-pill); cursor: pointer; font: inherit; }
.tab[aria-selected="true"] { background: var(--surface); color: var(--ink); box-shadow: var(--e1); font-weight: var(--fw-b); }
.right { margin-left: auto; display: flex; gap: var(--s2); align-items: center; }
.icon { position: relative; border: 1px solid var(--line); background: var(--surface); border-radius: var(--r-pill); width: var(--tap); height: var(--tap); cursor: pointer; font-size: var(--fs-md); }
.badge { position: absolute; top: calc(var(--s1) * -1.5); right: calc(var(--s1) * -1.5); background: var(--bad); color: var(--on-accent); border-radius: var(--r-pill); font-size: var(--fs-xs); font-weight: var(--fw-b); min-width: 18px; height: 18px; line-height: 18px; padding: 0 var(--s1); }
select, input, textarea { font: inherit; color: var(--ink); background: var(--surface); border: 1px solid var(--field-line, var(--off)); border-radius: var(--r-s); padding: var(--s2); max-width: 100%; }
label { display: block; margin: var(--s2) 0; }
label > span { display: block; font-size: var(--fs-sm); color: var(--muted); margin-bottom: var(--s1); }
label.inline { display: flex; align-items: center; gap: var(--s2); }
label.inline > span { display: inline; margin: 0; color: var(--ink); font-size: var(--fs-md); }

.banner { margin: var(--s3) 0 0; padding: var(--s2) var(--s3); border-radius: var(--r-s); background: var(--accent-soft); font-size: var(--fs-sm); }
.banner:empty { display: none; }
.banner .btn.primary { background: var(--surface); color: var(--ink); border-color: var(--line); font-weight: var(--fw); }   /* one filled button per screen */
#mode { max-width: 960px; margin-inline: auto; }
.btn { display: inline-flex; align-items: center; gap: var(--s2); border: 1px solid var(--line); background: var(--surface); color: var(--ink); padding: var(--s2) var(--s4); border-radius: var(--r-s); cursor: pointer; font: inherit; text-decoration: none; transition: background-color var(--d-fast) var(--ease-std), transform var(--d-fast) var(--ease-std); }
.btn.primary { background: var(--fill); color: var(--on-accent); border-color: var(--fill); font-weight: var(--fw-b); }
.btn.danger { color: var(--bad); border-color: color-mix(in srgb, var(--bad) 40%, var(--line)); }
@media (hover: hover) {
  .btn:hover:not([disabled]) { background: color-mix(in srgb, var(--ink) 6%, var(--surface)); }
  .btn.primary:hover:not([disabled]) { background: color-mix(in srgb, var(--ink) 12%, var(--fill)); }
}
.btn:active:not([disabled]) { transform: scale(.98); }
/* disabled and busy buttons stay readable (no dimming) */
.btn[disabled], .btn[aria-busy="true"] { opacity: 1; color: var(--muted); background: var(--bg); cursor: default; }
.btn.primary[disabled], .btn.primary[aria-busy="true"] { background: var(--accent-soft); color: color-mix(in srgb, var(--accent) 75%, var(--ink)); border-color: var(--accent); }
.btn[aria-busy="true"] { cursor: progress; }
.row { display: flex; align-items: center; margin: var(--s3) 0; }
.gap { gap: var(--s3); }
.wrap { flex-wrap: wrap; }

.cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: var(--s3); }
.card { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-m); padding: var(--s3) var(--s4); box-shadow: var(--e1); }
.card .when { font-size: var(--fs-lg); font-weight: var(--fw-b); }
.card .sub { color: var(--muted); font-size: var(--fs-sm); }
.card .bar { height: 6px; border-radius: var(--r-pill); background: var(--line); margin-top: var(--s2); overflow: hidden; }
.card .bar > i { display: block; height: 100%; background: var(--accent); }
.chip { border: 1px solid var(--line); background: var(--surface); color: var(--ink); padding: var(--s2) var(--s3); border-radius: var(--r-pill); cursor: pointer; font: inherit; white-space: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis; min-height: var(--tap); }
/* a long test name must never make the page wider than a phone (round 3: the page was 494 px on a 390 px phone) */
.chip[aria-pressed="true"] { background: var(--accent-soft); color: var(--ink); border-color: var(--accent); font-weight: var(--fw-b); }
.focusCard { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-m); padding: var(--s4); margin: var(--s4) 0; box-shadow: var(--e1); }
.gradeRow { margin: var(--s3) 0; max-width: none; width: 100%; }
.gradeSlider { display: grid; gap: var(--s1); }
.gradeHead { display: flex; justify-content: space-between; align-items: baseline; color: var(--muted); font-weight: var(--fw-b); }
.gradeHead output { font-size: var(--fs-xl); font-weight: var(--fw-b); color: var(--accent); line-height: var(--lh-t); }
.gradeEnds { display: flex; justify-content: space-between; color: var(--muted); font-size: var(--fs-xs); }
.gradeEnds > span { color: var(--muted); }
#gradeSlider { -webkit-appearance: none; appearance: none; width: 100%; height: var(--tap); margin: 0; padding: 0; border: 0; box-shadow: none; background: transparent; cursor: pointer; }
#gradeSlider::-webkit-slider-runnable-track { height: 8px; border-radius: var(--r-pill); background: linear-gradient(to right, var(--accent) var(--fill, 50%), var(--line) var(--fill, 50%)); }
#gradeSlider::-moz-range-track { height: 8px; border-radius: var(--r-pill); background: var(--line); }
#gradeSlider::-moz-range-progress { height: 8px; border-radius: var(--r-pill); background: var(--accent); }
#gradeSlider::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 28px; height: 28px; margin-top: -10px; border-radius: var(--r-pill); background: var(--surface); border: 3px solid var(--accent); box-shadow: var(--e1); transition: none; }
#gradeSlider:not(:active)::-webkit-slider-thumb { transition: transform var(--d-fast) var(--ease-std); }   /* a short ease only for keyboard steps, never while dragging */
#gradeSlider::-moz-range-thumb { width: 18px; height: 18px; border-radius: var(--r-pill); background: var(--surface); border: 3px solid var(--accent); box-shadow: var(--e1); }
#gradeSlider:active::-webkit-slider-thumb { transform: scale(1.15); }
.progressLine { font-weight: var(--fw-b); margin: var(--s2) 0 2px; }
.bar { height: 8px; border-radius: var(--r-pill); background: var(--line); margin-top: var(--s1); overflow: hidden; }
.bar > i { position: relative; display: block; height: 100%; width: 0; overflow: hidden; border-radius: var(--r-pill); background: var(--accent); }
/* the new fill: full width scaled by --p (0..1), only transform moves */
.bar > i[style*="--p"] { width: 100%; transform: scaleX(var(--p, 0)); transform-origin: left; transition: transform var(--d-base) var(--ease-std), background-color var(--d-base) var(--ease-std); }
.bar[data-full] > i { background: var(--ok); animation: none; }
/* the sheen plays only while real work runs (.is-busy), twice at most, never on an empty or full bar */
.bar.is-busy > i::after { content: ""; position: absolute; inset: 0; width: 45%; background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--on-accent) 50%, transparent), transparent); animation: barSheen 1.3s var(--ease-std) 2; }
.bar.is-busy[data-full] > i::after, .bar.is-busy > i[style*="--p: 0;"]::after, .bar.is-busy > i[style*="--p:0;"]::after { display: none; }
@keyframes barSheen { from { transform: translateX(-120%); } to { transform: translateX(320%); } }
.taskDone { color: var(--muted); }
.taskDone .taskText { color: var(--muted); text-decoration: line-through; }
.taskDone:not(:has(.taskText)) { text-decoration: line-through; }   /* the old rows have no .taskText yet */
.taskSub { font-size: var(--fs-sm); color: var(--muted); }

.list { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--s2); }
.list li { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-s); padding: var(--s3); display: grid; grid-template-columns: 112px minmax(0, 1fr) auto; gap: var(--s3); align-items: center; }
.list .date { font-weight: var(--fw-b); }
.list .date small { display: block; font-weight: var(--fw); color: var(--muted); }
.list .title { font-weight: var(--fw-b); }
.list .meta { color: var(--muted); font-size: var(--fs-sm); }
.list .acts { display: flex; align-items: center; align-self: center; gap: var(--s2); flex-wrap: nowrap; justify-content: flex-end; }
.list .acts .btn { padding: var(--s2) var(--s3); font-size: var(--fs-sm); }
.list.grey .title, .list.grey .meta, .list.grey .date { color: var(--muted); }   /* finished rows: muted text, never dimmed (contrast stays >= 4.5:1) */
.completed { margin-top: var(--s4); }
.completed summary { cursor: pointer; font-weight: var(--fw-b); margin-bottom: var(--s2); }
.count { background: var(--line); color: var(--ink); border-radius: var(--r-pill); padding: 1px var(--s2); font-size: var(--fs-xs); }
.tag { display: inline-block; border-radius: var(--r-pill); padding: 1px var(--s2); font-size: var(--fs-xs); background: var(--line); color: var(--ink); }
.tag.estimated { background: var(--warn-soft); color: var(--warn); }
.form { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-m); padding: var(--s3) var(--s4); margin: var(--s3) 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: var(--s2) var(--s3); }
.form .row { grid-column: 1 / -1; }

.notice, .noticeCalm { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-s); padding: var(--s3) var(--s4); margin: var(--s3) 0; box-shadow: none; }
.notice.warn { background: var(--warn-soft); border-color: color-mix(in srgb, var(--warn) 40%, var(--line)); }
.qrBox { display: flex; justify-content: center; margin: var(--s2) 0; }
.qrBox img { background: #fff; padding: var(--s2); border-radius: var(--r-s); image-rendering: pixelated; max-width: 100%; height: auto; }   /* a QR code needs a white background, also in dark mode */
.calGroup { margin: var(--s4) 0; }
.calGroup h3 { margin: var(--s2) 0; font-size: var(--fs-md); }
.calGroup .list li { grid-template-columns: 112px minmax(0, 1fr) auto; }
/* By course rows: title, then the date line, then the Study button (no kind chip: By date already shows it) */
#calendarResults .list li { grid-template-columns: minmax(0, 1fr) auto; }
#calendarResults .list li > .tag { display: none; }
#calendarResults .list li b { display: block; }
#calendarResults .list li .meta { display: block; }
.tag.close { background: var(--bad-soft); color: var(--bad); }
.tag.due { background: var(--warn-soft); color: var(--warn); }
.tag.open { background: var(--ok-soft); color: var(--ok); }
.tableWrap { overflow-x: auto; background-color: var(--surface); border-radius: var(--r-s);
  /* a 12 px fade on the right edge, only while there is more to scroll (the local layer covers it at the end) */
  background-image: linear-gradient(to left, var(--surface) 30%, transparent), linear-gradient(to left, color-mix(in srgb, var(--ink) 14%, transparent), transparent);
  background-position: 100% 0, 100% 0; background-size: 24px 100%, 12px 100%; background-repeat: no-repeat; background-attachment: local, scroll; }
.table { width: 100%; border-collapse: collapse; background: transparent; border: 1px solid var(--line); border-radius: var(--r-s); }
.table th, .table td { text-align: left; padding: var(--s2) var(--s3); border-bottom: 1px solid var(--line); vertical-align: top; }
.table th { color: var(--muted); font-weight: var(--fw-b); font-size: var(--fs-xs); }
.v { display: inline-block; border-radius: var(--r-pill); padding: 1px var(--s2); font-size: var(--fs-sm); white-space: nowrap; }
.v-used, .v-checked, .v-cited { background: var(--ok-soft); color: var(--ok); }   /* one colour for every kind of used */
.v-skip, .v-seen { background: var(--line); color: var(--muted); }
.table td { transition: background-color var(--d-fast) var(--ease-std); }
.is-flash > td, .is-flash { background-color: var(--accent-soft); }
.v-unreachable { background: var(--bad-soft); color: var(--bad); }
.v-not-yet { background: var(--warn-soft); color: var(--warn); }

.grid2, .settingsCol { display: grid; gap: var(--s4); align-content: start; align-items: start; grid-template-columns: minmax(0, 1fr); }   /* one column inside main's 640 px; cards size to their content */
.setCard, fieldset, #dangerBox { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-m); padding: var(--s4); box-shadow: var(--e1); margin: 0; min-width: 0; }
.setCard h2, fieldset > legend, #dangerBox > summary { font-size: var(--fs-md); font-weight: var(--fw-b); margin: 0 0 var(--s2); padding: 0; }
#dangerBox > summary { cursor: pointer; display: flex; align-items: center; min-height: var(--tap); margin: 0; }
#dangerBox[open] > summary { margin-bottom: var(--s2); }
#dangerBox .btn.danger { border: 0; background: transparent; color: var(--bad); padding-inline: 0; box-shadow: none; }
.setRow { min-height: var(--tap); margin: 0 0 var(--s3); }   /* older markup: toggle line with help text below */
.setRow:not(:has(> p)) { display: flex; align-items: center; justify-content: space-between; gap: var(--s4); }
.setRow > .setText { flex: 1 1 auto; min-width: 0; }
.setRow > .switch { flex: none; }
.switch { display: inline-flex; align-items: center; cursor: pointer; }
.switch input { position: absolute; opacity: 0; }
.switch span { width: 44px; height: 26px; border-radius: var(--r-pill); background: var(--off); position: relative; transition: background-color var(--d-fast) var(--ease-std); }
.switch span::after { content: ""; position: absolute; top: 4px; left: 4px; width: 18px; height: 18px; border-radius: var(--r-pill); background: var(--on-accent); transform: translateX(0); transition: transform var(--d-fast) var(--ease-std); }
.switch input:checked + span { background: var(--ok); }
.switch input:checked + span::after { transform: translateX(18px); }
.switch input:focus-visible + span { outline: var(--focus); outline-offset: 2px; }

.drawer { position: fixed; top: 64px; right: var(--s4); width: min(380px, calc(100vw - 32px)); max-height: 70vh; overflow: auto; background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-m); padding: var(--s3) var(--s4); box-shadow: var(--e2); z-index: 6; }
.drawer > .row:first-child { justify-content: space-between; margin: 0 0 var(--s2); }
.drawer > .row:first-child h2 { margin: 0; }
.drawer > .row:first-child .btn { width: var(--tap); padding: 0; justify-content: center; }
.drawer .list li { grid-template-columns: 1fr; }
.drawer[data-open="0"] { opacity: 0; transform: translateY(-6px); visibility: hidden; pointer-events: none; transition: opacity var(--d-fast) var(--ease-in), transform var(--d-fast) var(--ease-in), visibility var(--d-fast); }
.drawer[data-open="1"] { opacity: 1; transform: none; visibility: visible; transition: opacity var(--d-base) var(--ease-out), transform var(--d-base) var(--ease-out), visibility var(--d-base); }
.fab { position: fixed; right: var(--s4); bottom: var(--s4); width: var(--tap); height: var(--tap); border-radius: var(--r-pill); border: 1px solid var(--line); background: var(--surface); color: var(--ink); font-size: var(--fs-lg); line-height: 1; padding: 0; cursor: pointer; box-shadow: var(--e1); z-index: 6; }   /* quiet: the one filled button of a screen is the primary action */
dialog { border: 1px solid var(--line); border-radius: var(--r-m); background: var(--surface); color: var(--ink); padding: var(--s4); width: min(480px, calc(100vw - var(--s6))); margin: 10vh auto auto; box-shadow: var(--e2); opacity: 1; transform: none;
  transition: opacity var(--d-base) var(--ease-out), transform var(--d-base) var(--ease-out), overlay var(--d-base) allow-discrete, display var(--d-base) allow-discrete; }
dialog:not([open]) { opacity: 0; transform: translateY(8px); transition-duration: var(--d-fast); transition-timing-function: var(--ease-in); }
@starting-style { dialog[open] { opacity: 0; transform: translateY(8px); } }
dialog::backdrop { background: var(--scrim); opacity: 1; transition: opacity var(--d-base) var(--ease-out), overlay var(--d-base) allow-discrete, display var(--d-base) allow-discrete; }
dialog:not([open])::backdrop { opacity: 0; transition-duration: var(--d-fast); transition-timing-function: var(--ease-in); }
@starting-style { dialog[open]::backdrop { opacity: 0; } }
dialog textarea { width: 100%; }
dialog textarea:focus-visible { outline: var(--focus); outline-offset: 2px; }
dialog form { display: grid; gap: var(--s3); }
dialog form > * { margin-top: 0; margin-bottom: 0; }
dialog .row { display: flex; flex-direction: row; flex-wrap: wrap; gap: var(--s2); }
#recoveryCode { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s3); font-family: ui-monospace, Menlo, Consolas, monospace; font-size: var(--fs-sm); word-break: normal; overflow-wrap: normal; }
#recoveryCode span { white-space: nowrap; }
body.dlg-open .tg-layer, body.dlg-open .tw-hint { display: none; }
.foot { margin-top: var(--s6); padding-top: var(--s3); border-top: 1px solid var(--line); color: var(--muted); font-size: var(--fs-sm); }

@media (max-width: 560px) {
  h1 { font-size: var(--fs-lg); }
  .tabs { order: 3; width: 100%; justify-content: space-between; }
  .tab { padding: var(--s2); font-size: var(--fs-sm); }
  .list li { grid-template-columns: minmax(0, 1fr); }
  .list li > .date { grid-column: 1 / -1; display: flex; gap: var(--s2); align-items: baseline; }
  .list .acts { justify-content: flex-end; flex-wrap: wrap; }
  #calendarResults .list li { grid-template-columns: minmax(0, 1fr) auto; }
  .table td:first-child .small { display: none; }
}

.upd { display: flex; gap: var(--s3); align-items: center; flex-wrap: wrap; }
.upd [hidden] { display: none; }
.upd.stale { background: var(--warn-soft); }

/* one message at a time: the welcome cards never sit on top of a guide label (the guide layer waits until they are closed) */
body.tw-open .tg-layer { display: none; }
/* the trial notice, the age read-back and the "next test" card use the shared notice look */
#ageConfirm .row { margin: var(--s2) 0 0; }
.nextCard .title { font-weight: var(--fw-b); margin: 0 0 var(--s2); }
/* a test is learnt: a check mark that draws itself (circle 400 ms, then the check 300 ms after 250 ms: 550 ms in all, once), the words and the button that finishes the test */
.learntBox { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; margin: var(--s3) 0; padding: var(--s3); border-radius: var(--r-m); background: var(--ok-soft); color: var(--ok); border: 1px solid color-mix(in srgb, var(--ok) 30%, transparent); }
.learntBox b { flex: 1 1 160px; color: var(--ink); }
.learntMark circle { stroke-dasharray: 150; }
.learntMark path { stroke-dasharray: 40; }
.learntBox.play .learntMark circle { animation: learntCircle 400ms var(--ease-out) both; animation-iteration-count: 1; }
.learntBox.play .learntMark path { animation: learntCheck 300ms 250ms var(--ease-out) both; animation-iteration-count: 1; }
@keyframes learntCircle { from { stroke-dashoffset: 150; } to { stroke-dashoffset: 0; } }
@keyframes learntCheck { from { stroke-dashoffset: 40; } to { stroke-dashoffset: 0; } }
/* the whole mark pops once; a box that already played (.seen) shows the finished mark, still */
.learntBox.play .learntMark { animation: ts-pop 180ms var(--ease-out) 1 both; }
.learntBox.seen { animation-name: none; }
.learntBox.seen .learntMark, .learntBox.seen .learntMark circle, .learntBox.seen .learntMark path { animation: none; stroke-dasharray: none; }
li.task input[type="checkbox"]:checked { animation: ts-pop 180ms var(--ease-out) 1; }
@media (prefers-reduced-motion: reduce) { .learntBox.play .learntMark, .learntBox.play .learntMark circle, .learntBox.play .learntMark path { animation: none; stroke-dasharray: none; } li.task input[type="checkbox"]:checked { animation: none; } }
/* notification rows are real buttons */
.notifRow { display: block; min-height: var(--tap); width: 100%; text-align: left; background: none; border: 0; padding: 0; font: inherit; color: inherit; cursor: pointer; }
.notifRow .meta { display: block; }
.notifRow:focus-visible { border-radius: var(--r-s); }

/* Get started steps (guide.css draws the rest): readable marks and titles, no dimming of a whole row */
li.step.is-todo, li.step.is-done { opacity: 1; }
li.step.is-todo .stepTitle { color: var(--muted); }
li.step.is-done .stepTitle { color: var(--muted); }
li.step.is-done .stepMark { background: var(--ok); border-color: var(--ok); color: var(--on-accent); }

/* ---- shell-css B2-01: tap targets, wrapping, phone layout (bottom bar) ---- */
.btn, .chip, select, input:not([type="checkbox"], [type="radio"], [type="range"]), summary, label.inline, .list .acts .btn { min-height: var(--tap); }
input[type="checkbox"], input[type="radio"] { width: 24px; height: 24px; margin: 0; flex: none; }
.switch { min-width: var(--tap); min-height: var(--tap); padding-block: 8px; }
.table td { overflow-wrap: anywhere; }
.taskText, .list .title, .list .meta { overflow-wrap: break-word; hyphens: manual; }   /* a word is cut only when it cannot fit a line alone */
.tab { min-height: var(--tap); transition: background-color var(--d-fast) var(--ease-std); }
.tab:active { background: var(--accent-soft); }
.tabs { flex-wrap: nowrap; }
.top { max-width: 960px; flex-wrap: nowrap; }
.syncSlot { position: fixed; left: var(--s3); bottom: var(--s4); z-index: 6; max-width: calc(100vw - 24px); display: flex; align-items: center; gap: var(--s2); padding: var(--s1) var(--s3); background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-pill); box-shadow: var(--e1); pointer-events: none; transition: opacity var(--d-base) var(--ease-std); }   /* a solid pill, never text drawn over the page */
.syncSlot span { font-size: var(--fs-sm); }
select { padding-right: var(--s5); }
#lang { min-width: 8.5em; }   /* the longest language name fits */
.syncSlot .btn { pointer-events: auto; }
.syncSlot:has(#syncMsg:empty):not(:has(button:not([hidden]))) { opacity: 0; }
@media (max-width: 640px), (pointer: coarse) {
  header.top { position: static; min-height: var(--header-h); padding: 0; gap: var(--s2); flex-wrap: nowrap; max-width: var(--content); }
  .top .brand { margin-right: auto; }
  .right { margin-left: 0; }
  .icon { width: var(--tap); height: var(--tap); }
  .right select { min-height: var(--tap); max-width: none; }
  nav.tabs { position: fixed; left: 0; right: 0; bottom: 0; z-index: 7; display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 0; order: 0; width: auto; height: calc(var(--bar-h) + var(--safe-b)); padding-bottom: var(--safe-b); background: var(--surface); border-top: 1px solid var(--line); }
  nav.tabs .tab { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; height: var(--bar-h); min-height: var(--bar-h); min-width: 0; padding: 0 var(--s1); border-radius: 0; box-shadow: none; font-size: var(--fs-xs); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  nav.tabs .tab[aria-selected="true"] { color: var(--accent); background: var(--accent-soft); }
  main { padding-bottom: var(--s4); }
  .foot { padding-bottom: calc(var(--bar-h) + 72px + var(--safe-b)); }   /* the last line scrolls clear of the bar and the chat button */
  .fab { width: var(--tap); height: var(--tap); bottom: calc(var(--bar-h) + var(--s3) + var(--safe-b)); }
  .syncSlot { left: 50%; translate: -50% 0; bottom: calc(var(--bar-h) + var(--s3) + var(--safe-b)); max-width: calc(100vw - 144px); }
  .syncSlot span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .toast { bottom: calc(var(--bar-h) + var(--s3) + var(--safe-b)); }
}

/* ---- shell-css B2-02: skeleton, onboarding focus, step list ---- */
html:not([data-strings="ready"]) #main, html:not([data-strings="ready"]) .foot, html:not([data-strings="ready"]) #aiPanel { visibility: hidden; }
html[data-strings="ready"] #skeleton { display: none; }
.skeleton { max-width: var(--content); margin-inline: auto; animation: ts-fade-in var(--d-base) var(--ease-out) both; animation-iteration-count: 1; }
.skel-line { width: 40%; height: 28px; margin: var(--s4) 0 var(--s3); background: var(--line); border-radius: var(--r-m); }
.skel-card { height: 96px; margin: var(--s3) 0; background: var(--line); border-radius: var(--r-m); }
main[data-onboard="account"] :is(#mode, #testPicker, #focusCard, #aiPanel, #mathPanel, #noTestMsg, #noTestActions, #nextCard),
main[data-onboard="import"] :is(#mode, #testPicker, #focusCard, #aiPanel, #mathPanel, #noTestMsg, #noTestActions, #nextCard) { display: none; }
.startSteps { list-style: none; margin: var(--s3) 0 0; padding: 0; display: grid; gap: var(--s2); }
li.step { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-m); padding: var(--s3) var(--s4); }
li.step.is-current { border-color: var(--accent); }
li.step.is-done .stepBody { display: none; }
.stepHead { display: flex; align-items: center; gap: var(--s3); }
.stepMark { flex: none; width: 28px; height: 28px; border-radius: var(--r-pill); border: 2px solid var(--line); display: inline-flex; align-items: center; justify-content: center; font-weight: var(--fw-b); font-size: var(--fs-sm); }
li.step.is-current .stepMark { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.stepTitle { font-weight: var(--fw-b); }
.stepNow { margin-inline-start: auto; font-size: var(--fs-xs); font-weight: var(--fw-b); color: var(--accent); }
.stepBody { margin: var(--s2) 0 0; margin-inline-start: 40px; }
#startFold, .stepFold { border: 0; background: none; padding: 0; font: inherit; color: var(--accent); text-decoration: underline; cursor: pointer; min-height: var(--tap); }   /* the real fold line is #startFold */
.signInSent { padding: var(--s3) var(--s4); border: 1px solid var(--line); border-radius: var(--r-s); background: var(--surface); }
.signInSent .signInSentText { margin: 0 0 var(--s2); overflow-wrap: anywhere; }
.signInSent .row { margin: 0; }

/* ---- shell-css B2-03: Today card, task rows, sticky progress, grade control ---- */
.list li.task, li.task { display: flex; align-items: stretch; gap: var(--s3); padding: 0; min-height: 56px; background: none; border: 0; border-bottom: 1px solid var(--line); border-radius: 0; }
li.task > label.inline { flex: 1; min-width: 0; min-height: 56px; align-items: center; margin: 0; padding-block: var(--s3); }
li.task input[type="checkbox"] { flex: none; width: 24px; }
.taskText { flex: 1; min-width: 0; }
.taskSub { display: block; font-size: var(--fs-xs); color: var(--muted); }
li.task .tag { align-self: center; }
#testMeta .title { font-size: var(--fs-lg); font-weight: var(--fw-b); line-height: var(--lh-t); }
#testMeta .meta { font-size: var(--fs-sm); color: var(--muted); margin-top: var(--s1); }
.progressSticky { position: sticky; top: 0; z-index: 4; background: var(--surface); padding: var(--s2) 0; }
.progressSticky .progressLine { font-size: var(--fs-sm); font-weight: var(--fw-b); margin: 0; }
.progressSticky .bar { height: 6px; }
@media (min-width: 641px) and (pointer: fine) { .progressSticky { top: var(--header-h); } }
.gradeHead { justify-content: space-between; align-items: center; gap: var(--s4); }   /* label left, [-] 8 [+] right */
.gradeBig { display: inline-flex; align-items: center; gap: var(--s3); }
.gradeHead output { font-size: var(--fs-xl); min-width: 2ch; text-align: center; }
.gradeStep { flex: none; display: inline-flex; align-items: center; justify-content: center; width: var(--tap); height: var(--tap); min-height: var(--tap); padding: 0; border: 1px solid var(--line); background: var(--surface); color: var(--ink); cursor: pointer; border-radius: var(--r-pill); font: inherit; font-size: var(--fs-lg); transition: background-color var(--d-fast) var(--ease-std), transform var(--d-fast) var(--ease-std); }
.gradeStep:active:not([aria-disabled="true"]) { transform: scale(.96); }
.gradeStep[aria-disabled="true"] { color: var(--muted); cursor: default; }
@media (hover: hover) { .gradeStep:hover:not([aria-disabled="true"]) { background: color-mix(in srgb, var(--ink) 6%, var(--surface)); } }
/* the grade number is the button that opens the slider (R3-cd-3): a full tap target, plain number, a quiet underline says it opens */
.gradeNum { flex: none; display: inline-flex; align-items: center; justify-content: center; min-width: var(--tap); min-height: var(--tap); padding: 0 var(--s2); border: 0; background: none; color: var(--ink); font: inherit; font-weight: 600; cursor: pointer; border-radius: var(--r-s); text-decoration: underline; text-decoration-color: var(--line); text-underline-offset: 4px; }
.gradeNum[aria-expanded="true"] { text-decoration-color: currentColor; }
#nextGradeHint { font-size: var(--fs-sm); color: var(--muted); }
#testPicker { display: flex; flex-wrap: wrap; gap: var(--s2); }

/* ---- the toast (shell-js) ---- */
/* loop L04-8: a quiet card of the page's own surface with a thin line (the inverted box was a white block over the dark page); the light theme keeps the inverse look. The text takes the room, Undo and Close stay at the end, two lines at most. */
.toast { position: fixed; left: 50%; bottom: var(--s5); transform: translateX(-50%); z-index: 8; display: flex; align-items: center; gap: var(--s2); max-width: min(520px, calc(100vw - 24px)); padding: var(--s2) var(--s3) var(--s2) var(--s4); background: var(--surface); color: var(--ink); border: 1px solid var(--line); border-radius: var(--r-s); box-shadow: var(--e2); font-size: var(--fs-sm); animation: toastIn var(--d-base) var(--ease-out) both; animation-iteration-count: 1; }
html[data-theme="light"] .toast { background: var(--ink); color: var(--bg); border-color: transparent; }
.toast #toastText { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; display: block; max-height: 40vh; overflow-y: auto; }   /* round 3 L12-A6: never cut (it was two lines; at 200% zoom text was lost) */
.toast #toastAction, .toast .toast-close { flex: none; }
@keyframes toastIn { from { opacity: 0; transform: translate(-50%, 8px); } to { opacity: 1; transform: translateX(-50%); } }   /* ts-rise-in, keeping the centring */
.toast .btn { min-height: var(--tap); font-size: var(--fs-sm); }
.toast .toast-close { width: 44px; height: 44px; flex: none; padding: 0; justify-content: center; background: transparent; color: inherit; border: 0; font-size: var(--fs-lg); }
.toast .toast-close:hover:not([disabled]) { background: color-mix(in srgb, var(--bg) 16%, transparent); }

/* ---- shell-css B3: tests screen, motion, material ---- */
.seg { display: inline-flex; gap: var(--s1); }
.seg > button, .seg > .chip { background: transparent; border: 0; border-bottom: 2px solid transparent; border-radius: 0; color: var(--muted); padding: var(--s2) var(--s3); cursor: pointer; font: inherit; min-height: var(--tap); }
.seg > button[aria-pressed="true"], .seg > .chip[aria-pressed="true"] { background: transparent; color: var(--ink); border-bottom-color: var(--accent); font-weight: var(--fw-b); }
.moreMenu { position: relative; }
.moreMenu > summary { list-style: none; display: inline-flex; align-items: center; justify-content: center; min-width: var(--tap); cursor: pointer; }
.moreMenu > summary::-webkit-details-marker { display: none; }
.popmenu { position: absolute; right: 0; top: calc(100% + 4px); min-width: 168px; background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-s); box-shadow: var(--e2); z-index: 8; display: flex; flex-direction: column; padding: var(--s1) 0; }
.popmenu button { min-height: 44px; border: 0; background: transparent; color: var(--ink); font: inherit; text-align: left; padding: 0 var(--s4); cursor: pointer; }
.popmenu button:hover { background: var(--accent-soft); }
.popmenu .danger, .popmenu button[data-act="delete"] { color: var(--bad); }
.view-enter { animation: ts-fade-in var(--d-base) var(--ease-out) both; animation-iteration-count: 1; }
.collapse { display: grid; grid-template-rows: 0fr; transition: grid-template-rows var(--d-base) var(--ease-std); }
.collapse[data-open="1"] { grid-template-rows: 1fr; }
.collapse-in { overflow: hidden; min-height: 0; }
@keyframes rowOut { to { opacity: 0; transform: translateX(-8px); } }
.is-leaving { animation: rowOut var(--d-base) var(--ease-in) both; animation-iteration-count: 1; }
.count.is-pulse { animation: ts-pop 180ms var(--ease-out) 1; }

/* Account card: sign-out panel, download button */
#accountBox .row, .setCard .row { flex-wrap: wrap; gap: var(--s2); }
#signOutPanel { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-s); padding: var(--s3) var(--s4); margin: var(--s3) 0; }
#signOutPanel .row { display: flex; flex-wrap: wrap; gap: var(--s2); }
#signOutPanel .btn, #exportBtn { min-height: var(--tap); }
#signOutPanel label { display: flex; align-items: center; gap: var(--s2); min-height: var(--tap); margin: 0; }
#exportMsg { font-size: var(--fs-sm); color: var(--muted); min-height: 20px; margin: var(--s1) 0 0; }
fieldset > legend { float: left; width: 100%; }   /* the title sits inside the card, not on its border */
fieldset > legend + * { clear: both; }
:is(.setCard, fieldset, #dangerBox) p:empty { display: none; }
.setRow > .row { margin: 0; }
.setRow > p { margin: var(--s1) 0 0; }

/* shell-css-B5-01: plan chooser, notice buttons */
.plans { display: flex; flex-direction: column; gap: var(--s2); margin: var(--s3) 0; }
label.planOpt { display: grid; grid-template-columns: 24px minmax(0, 1fr) auto; align-items: center; column-gap: var(--s3); min-height: var(--tap); box-sizing: border-box; background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-m); padding: var(--s3) var(--s4); margin: 0; cursor: pointer; }
label.planOpt input[type=radio] { width: 24px; height: 24px; margin: 0; grid-column: 1; grid-row: 1 / span 2; }
.planName { grid-column: 2; font-weight: 600; min-width: 0; overflow-wrap: anywhere; }
.planPrice { grid-column: 3; grid-row: 1; font-size: var(--fs-md); font-weight: 600; text-align: right; white-space: normal; overflow-wrap: anywhere; max-width: 45%; justify-self: end; }
.planOpt .planNote { grid-column: 2 / -1; font-size: var(--fs-sm); color: var(--muted); }
label.planOpt:has(input:checked), label.planOpt.is-selected { border-color: var(--accent); background: var(--accent-soft); }
label.planOpt:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
.planTerms { font-size: var(--fs-sm); color: var(--muted); }
.planTerms a { text-decoration: underline; }
.notice .row, .noticeCalm .row { display: flex; flex-wrap: wrap; gap: var(--s2); margin-top: var(--s2); }
.notice .row .btn, .noticeCalm .row .btn, .noticeCalm > .btn { min-height: var(--tap); }

/* shell-css-B5-02: invite card and plan note */
#inviteCard { max-width: 100%; box-sizing: border-box; }
#inviteLink { display: block; width: 100%; max-width: 100%; box-sizing: border-box; min-height: var(--tap); font-family: ui-monospace, Menlo, Consolas, monospace; font-size: var(--fs-sm); }
#inviteCard .row { display: flex; flex-wrap: wrap; gap: var(--s2); margin-top: var(--s2); }
#inviteCard .btn { min-height: var(--tap); }
#inviteCard .muted, #inviteMsg { min-height: 20px; }
#launchNote { display: flex; flex-direction: column; align-items: stretch; gap: var(--s2); }
#launchNote[hidden] { display: none; }
#launchNote .btn { min-height: var(--tap); align-self: flex-start; }
@media (min-width: 700px) { #launchNote { flex-direction: row; align-items: center; justify-content: space-between; } #launchNote .btn { align-self: center; margin-left: auto; flex: none; } }
.planLaunch { display: block; font-weight: 600; }
#planNote { display: flex; flex-direction: column; align-items: stretch; gap: var(--s2); }
#planNote[hidden] { display: none; }
#planNote .btn { min-height: var(--tap); align-self: flex-start; }
@media (min-width: 700px) { #planNote { flex-direction: row; align-items: center; justify-content: space-between; } #planNote .btn { align-self: center; margin-left: auto; flex: none; } }

/* ---- shell-css loop 6: first-look findings (R1) ---- */
/* Settings rows: the title on its own line, the help text under it in muted small text, "Saved" on its own line */
.setRow > .setText { margin: 0; color: var(--ink); font-size: var(--fs-md); }
.setText > b, .setText > span { display: block; }
.setText > .muted { color: var(--muted); font-size: var(--fs-sm); margin-top: 2px; }
.setText > .saved:empty { display: none; }
/* a row with nothing visible in it takes no space (no empty bands inside the cards) */
.setCard .row:not(:has(> :not([hidden], :empty, input, select))) { display: none; }
/* the agreement tick: box and sentence on one line, the sentence in ink at normal size */
label:has(> #bmAck) { display: flex; align-items: flex-start; gap: var(--s3); min-height: var(--tap); margin: 0; }
label:has(> #bmAck) > span { display: block; margin: 0; font-size: var(--fs-md); color: var(--ink); }
/* "Hide done tasks" is a small switch in the progress header, not a row that looks like a task */
label.hideDone { display: inline-flex; align-items: center; gap: var(--s2); min-height: var(--tap); margin: 0; font-size: var(--fs-sm); color: var(--muted); cursor: pointer; }
label.hideDone > span { display: inline; margin: 0; font-size: var(--fs-sm); color: var(--muted); }
label.hideDone input[type="checkbox"] { -webkit-appearance: none; appearance: none; flex: none; width: 40px; height: 24px; border-radius: var(--r-pill); background: var(--off); position: relative; cursor: pointer; transition: background-color var(--d-fast) var(--ease-std); }
label.hideDone input[type="checkbox"]::after { content: ""; position: absolute; top: 3px; left: 3px; width: 18px; height: 18px; border-radius: var(--r-pill); background: var(--on-accent); transition: transform var(--d-fast) var(--ease-std); }
label.hideDone input[type="checkbox"]:checked { background: var(--ok); }
label.hideDone input[type="checkbox"]:checked::after { transform: translateX(16px); }
/* the grading scale box opens with a short ease, not a jump */
html { interpolate-size: allow-keywords; }
details.scaleBox::details-content { block-size: 0; overflow: clip; transition: block-size var(--d-base) var(--ease-std), content-visibility var(--d-base) allow-discrete; }
details.scaleBox[open]::details-content { block-size: auto; }
details.scaleBox > summary { font-size: var(--fs-sm); display: flex; align-items: center; }
/* the bell panel: air above the quiet button */
#markRead { margin-top: var(--s3); }
.drawer .list { margin-bottom: 0; }
/* a busy button keeps its spinner beside the label */
.btn > .tl-spin { position: static; flex: none; }

/* ---- shell-css-B6-01: phone-first card and install hints (markup by shell-js, styled by name) ---- */
#phoneFirst { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-m); padding: var(--s5); margin: var(--s4) 0; }
#phoneFirst .btn { display: flex; justify-content: center; width: 100%; min-height: 48px; margin-top: var(--s3); }
#phoneFirst .btn:not(.primary) { margin-top: var(--s2); }
#iosHint { margin: var(--s3) 0; padding: 0; list-style-position: inside; font-size: var(--fs-sm); color: var(--muted); }
#iosHint li { margin: var(--s1) 0; overflow-wrap: break-word; }
#installBtn { min-height: var(--tap); }

/* ---- shell-css-B6-02: lost-key card ---- */
#keyLost { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-s); padding: var(--s3) var(--s4); margin: var(--s3) 0; box-shadow: none; }
#keyLost p { overflow-wrap: break-word; }
#keyLost .row, #keyLost .actions { display: flex; flex-wrap: wrap; gap: var(--s2); margin-top: var(--s2); }
#keyLost .btn { min-height: var(--tap); }
#keyLost .small, #keyLost [data-confirm] { font-size: var(--fs-sm); }
#keyLost .btn.danger, #keyLost [data-act="fresh"] { border-color: transparent; background: transparent; color: var(--bad); }

/* ---- shell-css loop 7: real markup of the plan chooser, invite card, phone-first card, install page; low R1 findings ---- */
#planOpts { display: flex; flex-direction: column; gap: var(--s2); }
#planOpts:empty { display: none; }
#planErr:empty, #planMsg:empty, #inviteStats:empty, #phoneSendMsg:empty { display: none; }
#planMsg, #inviteMsg { min-height: 20px; }
#planBox > h3 { margin: 0; }
#plansRetry { min-height: var(--tap); }
#subscribe { min-height: var(--tap); }
#inviteOut { margin-top: var(--s3); }
#inviteOut[hidden] { display: none; }
#inviteLink { margin: 0; }
#phoneFirst h3 { margin: 0 0 var(--s2); font-size: var(--fs-lg); line-height: var(--lh-t); }
#phoneFirst .row { display: block; margin: 0; }
#phoneMailRow { margin: var(--s2) 0 0; }
#phoneMailRow a { display: inline-flex; align-items: center; min-height: var(--tap); text-decoration: underline; }
/* install page: tick and sentence in one row, the button a pill, one calm "added" row, steps with numbers inside */
.ackRow, .bm-ack { display: flex; align-items: flex-start; gap: var(--s3); min-height: var(--tap); font-size: var(--fs-md); color: var(--ink); }
.ackRow input[type=checkbox], .bm-ack input[type=checkbox] { flex: none; width: 24px; height: 24px; margin: var(--s2) 0 0; }
.bm-ack label { margin: 0; padding: var(--s2) 0; font-size: var(--fs-md); color: var(--ink); min-height: var(--tap); box-sizing: border-box; }
#bookmarklet { display: inline-flex; align-items: center; justify-content: center; min-height: var(--tap); padding: var(--s2) var(--s5); border-radius: var(--r-pill); cursor: grab; text-decoration: none; }
#bookmarklet[aria-disabled="true"] { background: var(--accent-soft); color: var(--accent); border-color: var(--accent); cursor: not-allowed; }
#bmAdded { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s3); margin: var(--s3) 0; padding: var(--s3) var(--s4); background: var(--accent-soft); border-radius: var(--r-s); }
#bmAdded[hidden] { display: none; }
#bmAdded p { margin: 0; }
#bmAddedBtn { min-height: var(--tap); }
#bmSteps { margin: var(--s2) 0; padding: 0; list-style-position: inside; }
#bmSteps li { margin: var(--s1) 0; overflow-wrap: break-word; }
#bmPhoneCard > summary, #bmPhoneDetails > summary { display: flex; align-items: center; min-height: var(--tap); cursor: pointer; font-weight: var(--fw-b); }
#bmFirstOnly, #bmNoClick { font-size: var(--fs-sm); color: var(--muted); }
/* low findings: "Now" label and the active tab keep 4.5:1 on their tinted background; the brand reads at a glance; long codes wrap only at group borders */
.stepNow, nav.tabs .tab[aria-selected="true"] { color: color-mix(in srgb, var(--accent) 70%, var(--ink)); }
.brand { font-size: var(--fs-lg); }
#recoveryCode { display: block; overflow-wrap: anywhere; word-break: normal; font-family: ui-monospace, Menlo, Consolas, monospace; font-size: var(--fs-sm); }
.tab > svg, .tab > .ico { display: block; flex: none; margin: 0; }
.table td, .table th { overflow-wrap: break-word; }

/* ---- loop 8 (shell-css): sign-out panel enters softly (R1-d29-2), slider ends sit clear of the thumb, task list does not jump ---- */
@keyframes panelIn { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
#signOutPanel:not([hidden]) { animation: panelIn var(--d-base) var(--ease-out) both; }
.gradeEnds { padding-inline: var(--s1); font-size: var(--fs-xs); }

/* ---- loop 8 (shell-css): R1 low findings and the four-view look ---- */
/* the brand reads at a glance: the mark is 44 px tall (text about 16 px); its empty right part is folded away so it takes little room (R1-d02-3, d08-4, d17-3, main-7) */
.wordmark { --wm-h: 44px; height: var(--wm-h); margin-right: calc(var(--wm-h) * -1.2); pointer-events: none; flex: none; }
/* the sample-data / status strip lines up with the page column */
#mode { max-width: var(--content); }
/* step bodies: tight, even spacing (R1-d05-3: no 50 px holes between the sentence, "More" and the fields) */
.stepBody p { margin: 0 0 var(--s2); }
.stepBody p:empty, .stepBody .row:empty { display: none; }
.stepBody label { margin: 0; }
.stepBody .row { margin: var(--s2) 0 0; align-items: flex-end; }
.stepBody details { margin: 0; }
.stepBody .btn.primary, .stepBody a.btn { margin-top: var(--s1); }
/* "2 of 5 steps done": a quiet muted line with a small chevron, not a link look */
#startFold { display: inline-flex; align-items: center; gap: var(--s2); color: var(--muted); font-size: var(--fs-sm); text-decoration: none; }
#startFold::before { content: ""; flex: none; width: 6px; height: 6px; border: solid currentColor; border-width: 0 2px 2px 0; transform: rotate(-45deg); transition: transform var(--d-fast) var(--ease-std); }
#startFold[aria-expanded="true"]::before { transform: rotate(45deg); }
/* cards: no dead band under the last visible row (R1-d14-4) */
.setCard > :not([hidden], :empty):not(:has(~ :not([hidden], :empty, div:not([class], [id])))) { margin-bottom: 0; }   /* plain script-made slots take no room */
#accountBox { padding-bottom: var(--s3); }   /* its last row is followed by rows that are only sometimes shown */
/* Danger zone is a quiet row with a small chevron, not an empty card */
#dangerBox > summary { list-style: none; justify-content: space-between; }
#dangerBox > summary::-webkit-details-marker { display: none; }
#dangerBox > summary::after { content: ""; flex: none; width: 8px; height: 8px; border: solid var(--muted); border-width: 0 2px 2px 0; transform: rotate(-45deg); transition: transform var(--d-fast) var(--ease-std); }
#dangerBox[open] > summary::after { transform: rotate(45deg); }
/* the target grade control: 16 px of air each side of the number, thin neutral rings, 44 px buttons (R1-d10-10) */
.gradeBig { gap: var(--s4); }
/* a table never squeezes its columns below a readable width: it scrolls sideways inside its own box (R1-d16-4) */
.tableWrap > .table { min-width: 30rem; }
/* radios and ticks use the app blue, rows in dialogs stay a tidy pair (R1-d20-2) */
input[type="radio"], input[type="checkbox"], input[type="range"] { accent-color: var(--accent); }
dialog label.inline { margin: 0; }
/* the bell panel: a faint dim behind it so the page does not look clickable (R1-d15-2); the close button shows a still ring (R1-d28-2) */
body:has(.drawer[data-open="1"])::after { content: ""; position: fixed; inset: 0; z-index: 5; pointer-events: none; background: color-mix(in srgb, var(--ink) 4%, transparent); animation: ts-fade-in var(--d-base) var(--ease-out) both; animation-iteration-count: 1; }
.drawer .btn { transition: none; }
.drawer .btn:focus-visible { outline: var(--focus); outline-offset: 2px; }
/* easing: nothing is linear (R1-d13-3, d21-2) */
@keyframes viewIn { from { opacity: .4; } to { opacity: 1; } }
.view-enter { animation: viewIn var(--d-base) var(--ease-std) both; animation-iteration-count: 1; }
@keyframes menuIn { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
.moreMenu[open] > .popmenu { animation: menuIn var(--d-base) var(--ease-out) both; animation-iteration-count: 1; }
/* a deleted row fades and closes up in the same 200 ms, so the page below does not jump (R1-d18-5) */
@keyframes rowShut { to { height: 0; min-height: 0; padding-block: 0; border-width: 0; margin-block: 0; } }
.list li.is-leaving { overflow: hidden; animation: rowOut var(--d-base) var(--ease-in) both, rowShut var(--d-base) var(--ease-std) both; animation-iteration-count: 1; }
/* the bell panel starts below the header (it is 88 px tall on a computer, 56 px on a phone) so it never covers the language select */
.drawer { top: calc(var(--header-h) + var(--s7) - var(--s4)); }
@media (max-width: 640px), (pointer: coarse) { .drawer { top: calc(var(--header-h) + var(--s2)); } }
/* an opened "Completed" list sits close under its title (R1-p08-3) */
.completed summary { margin-bottom: 0; }
.completed > .list { margin-top: var(--s1); }

/* ---- shell-css L09: "More" lines in Settings are centred in their 44 px row (the text sat at the top and left a dead band) ---- */
.setMore > summary { display: flex; align-items: center; gap: var(--s2); list-style: none; cursor: pointer; }
.setMore > summary::-webkit-details-marker { display: none; }
.setMore > summary::before { content: ""; flex: none; width: 0; height: 0; border-style: solid; border-width: 4px 0 4px 6px; border-color: transparent transparent transparent currentColor; transition: transform var(--d-fast) var(--ease-std); }
.setMore[open] > summary::before { transform: rotate(90deg); }

/* ---- shell-css L11: R2 findings ---- */
/* R2-trust-1, R2-ui-1: the plan row never crushes its name; on a phone the price drops under the name */
@media (max-width: 420px) {
  .planPrice { grid-column: 2 / -1; grid-row: 2; max-width: none; text-align: start; justify-self: start; font-weight: var(--fw); color: var(--muted); }
  label.planOpt input[type=radio] { grid-row: 1 / span 3; }
  .planOpt .planNote { grid-row: 3; }
}
/* R2-speed-8: the plan area keeps its height while the language swaps (no jump) */
.planTerms { min-height: 0; }   /* loop L04-5: no reserved blank lines under the terms; the box itself (#planBox) keeps its height while the text swaps */
/* R2-a11y-3, R2-look-8, R2-ftu-15: small blue text on the pale tint keeps 4.5:1 (guide.css loads after this file, so the selector is stronger) */
li.step .stepNow, html nav.tabs .tab[aria-selected="true"] { color: color-mix(in srgb, var(--accent) 60%, var(--ink)); }
/* R2-look-4: header and page share one column; the current tab is a flat pill, not a raised blob */
@media (min-width: 641px) and (pointer: fine) {
  .top, main, .foot, #mode { max-width: 720px; }
  .tab[aria-selected="true"] { background: color-mix(in srgb, var(--accent) 14%, var(--surface)); box-shadow: none; }
}
/* R2-look-15: the dark tab pill shows */
html:not([data-theme="light"]) .tab[aria-selected="true"], html:not([data-theme="light"]) nav.tabs .tab[aria-selected="true"] { background: rgb(110 160 255 / .16); }
/* R2-look-7: bell, tabs and the chat button share one 1.5 px stroke look; the chat bubble is drawn, not an emoji */
.tabIcon, .bellIcon { stroke-width: 1.5; }
.icon { color: var(--muted); }
.fab { text-indent: -999px; overflow: hidden; white-space: nowrap; color: var(--muted); }
.fab::before { content: ""; position: absolute; left: 50%; top: 50%; width: 20px; height: 15px; margin: calc(var(--s2) * -1.125) 0 0 calc(var(--s1) * -2.5); border: 1.5px solid currentColor; border-radius: var(--r-s); }
.fab::after { content: ""; position: absolute; left: 50%; top: 50%; width: 6px; height: 6px; margin: calc(var(--s1) * .75) 0 0 calc(var(--s1) * -1.5); border: solid currentColor; border-width: 0 1.5px 1.5px 0; background: var(--surface); transform: rotate(45deg); }
/* R2-look-6, R2-ftu-14: footer links use the text colour with a quiet underline; the sentence and the links sit on two lines */
.foot a { color: inherit; text-decoration: underline; text-decoration-color: var(--line); text-underline-offset: 3px; margin-inline-end: var(--s3); display: inline-block; min-height: var(--tap); line-height: var(--tap); }
.foot a:hover, .foot a:focus-visible { color: var(--accent); text-decoration-color: currentColor; }
.foot > span:first-child { display: block; margin-bottom: var(--s1); }
/* R2-ui-5: on a phone the last row scrolls clear of the round button */
@media (max-width: 640px), (pointer: coarse) { main { padding-bottom: var(--s4); } }   /* the footer below it already clears the tab bar: a second clearance here was dead space (loop L06-10) */
/* R2-look-3, R2-ui-3, R2-ui-4: one message look. .note is the base; .is-error has a warm bar and a "!" mark, .is-ok a tick, .is-news the soft accent.
   The result lines of the settings cards use it as soon as they hold text; the line fades in (shell-js sets is-error / is-ok / is-news). */
.note, :is(#planMsg, #exportMsg, #inviteMsg, #remindersMsg, #phoneMsg, #phoneSendMsg, #newMaterial):not(:empty) { display: block; margin: var(--s2) 0; padding: var(--s2) var(--s3); border-radius: var(--r-s); border-inline-start: 3px solid var(--line); background: var(--surface); color: var(--ink); font-size: var(--fs-sm); animation: ts-fade-in var(--d-base) var(--ease-out) both; animation-iteration-count: 1; }
.note.is-error, .is-error:is(#planMsg, #exportMsg, #inviteMsg, #remindersMsg, #phoneMsg, #phoneSendMsg) { border-inline-start-color: var(--bad); background: var(--bad-soft); font-size: var(--fs-md); color: var(--ink); }
.note.is-ok, .is-ok:is(#planMsg, #exportMsg, #inviteMsg, #remindersMsg, #phoneMsg, #phoneSendMsg) { border-inline-start-color: var(--ok); background: var(--ok-soft); color: var(--ink); }
.note.is-news, #newMaterial:not(:empty) { border-inline-start-color: var(--accent); background: var(--accent-soft); }
.note.is-error::before, .is-error:is(#planMsg, #exportMsg, #inviteMsg, #remindersMsg, #phoneMsg, #phoneSendMsg)::before { content: "!"; display: inline-block; width: 18px; height: 18px; margin-inline-end: var(--s2); border-radius: var(--r-pill); background: var(--bad); color: var(--on-accent); font-weight: var(--fw-b); font-size: var(--fs-xs); line-height: 18px; text-align: center; }
.note.is-ok::before, .is-ok:is(#planMsg, #exportMsg, #inviteMsg, #remindersMsg, #phoneMsg, #phoneSendMsg)::before { content: "\2713"; margin-inline-end: var(--s2); color: var(--ok); font-weight: var(--fw-b); }
/* R2-trust-10, R2-look-2, R2-ui-8: the Account card: one status line, a quiet second line, tight spacing; sign out and download are quiet text buttons away from the pay button */
#accountBox > p { margin: 0 0 var(--s1); }
#accountBox #planLine { color: var(--ink); font-weight: var(--fw-b); font-size: var(--fs-md); }
#accountBox > .row { margin: var(--s3) 0 0; }
#signOut, #exportBtn { border-color: transparent; background: transparent; color: var(--muted); text-decoration: underline; text-underline-offset: 3px; padding-inline: var(--s2); }
#signOut { margin-inline-start: auto; }
#exportBtn { margin-top: var(--s1); }
@media (max-width: 640px) { #subscribe { flex: 1 1 100%; justify-content: center; } #signOut { margin-inline-start: 0; } }
/* R2-look-10: even rhythm in Settings: 16 px between rows, 12 px inside; a switch that is on is the app blue; Danger zone actions are outlined red buttons */
.setCard .setRow { margin: 0 0 var(--s4); }
.setCard .setRow:last-child { margin-bottom: 0; }
.setMore { margin: 0 0 var(--s4); }
.setMore > :not(summary) { margin-top: var(--s3); }
.switch input:checked + span, label.hideDone input[type="checkbox"]:checked { background: var(--accent); }
#dangerBox .btn.danger { border: 1px solid color-mix(in srgb, var(--bad) 50%, var(--line)); background: transparent; color: var(--bad); padding-inline: var(--s4); box-shadow: none; }
/* the card trial (owner 2026-10-07): the Settings trial card and the words under the plan buttons */
#trialCard p { margin: 0 0 var(--s2); }
#trialCard .row, #trialCancelRow .row { margin: var(--s2) 0 0; }
#planTrial p, #planTrialPick { margin: var(--s2) 0 0; }
/* R2-ftu-1: the demo's sample test can open while the first steps run (shell-js sets data-sample="1" on main and builds .sampleCard) */
main[data-onboard][data-sample="1"] :is(#mode, #testPicker, #focusCard, #aiPanel, #nextCard) { display: revert; }
.sampleCard { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-m); padding: 0 var(--s4); margin: var(--s3) 0; }
.sampleCard > summary, .sampleCard > button { display: flex; align-items: center; min-height: var(--tap); cursor: pointer; font-weight: var(--fw-b); }
/* R2-ui-6: a busy button reserves room for the spinner at its start, so the label is never printed over */
.btn.tl-busy, .btn[aria-busy="true"] { position: relative; padding-inline-start: calc(var(--s4) + 24px); }
.btn.tl-busy > .tl-spin, .btn[aria-busy="true"] > .tl-spin { position: absolute; inset-inline-start: var(--s3); top: 50%; translate: 0 -50%; }
/* R2-ui-11: the view change eases out like the other enters */
@keyframes viewIn { from { opacity: .4; } to { opacity: 1; } }
.view-enter { animation: viewIn var(--d-base) var(--ease-out) both; animation-iteration-count: 1; }
/* L12: the one quiet row for Sign out and Download my data (shell-js puts both in .quietRow); result lines and the sample card keep their look */
.quietRow { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2) var(--s3); margin: var(--s3) 0 0; padding-top: var(--s2); border-top: 1px solid var(--line); }
.quietRow > :is(#signOut, #exportBtn) { margin: 0; margin-inline-start: 0; min-height: var(--tap); }
.quietRow > :is(#signOut, #exportBtn):hover { color: var(--ink); }
.quietRow > :is(#signOut, #exportBtn):focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.is-error, .is-ok { overflow-wrap: anywhere; }
.sampleCard[data-sample="1"], main[data-sample="1"] .sampleCard { display: block; }
.sampleCard > :is(summary, button) { width: 100%; text-align: start; background: transparent; border: 0; color: var(--ink); font: inherit; font-weight: var(--fw-b); }
button.sampleCard { display: block; width: 100%; min-height: var(--tap); font: inherit; font-size: var(--fs-md); font-weight: var(--fw-b); text-align: start; color: var(--ink); cursor: pointer; }
button.sampleCard[hidden] { display: none; }
button.sampleCard:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.sampleCard > :is(summary, button):focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: var(--r-m); }
@media (max-width: 640px) { .quietRow { justify-content: space-between; } .sampleCard { padding-inline: var(--s3); } }

/* ---- shell-css L14: a "More" line belongs to the row above it; two of them in a row sit together, 16 px only after the last ---- */
.setCard .setRow:has(+ .setMore) { margin-bottom: 0; }
.setMore:has(+ .setMore) { margin-bottom: 0; }

/* ---- shell-css L15: the items done/L14-shell-js.md left ---- */
/* the open tab is styled by aria-current as well as aria-selected (shell-js may drop aria-selected later) */
.tab[aria-current="page"] { background: var(--surface); color: var(--ink); box-shadow: var(--e1); font-weight: var(--fw-b); }
@media (max-width: 640px) { nav.tabs .tab[aria-current="page"] { color: var(--accent); background: var(--accent-soft); box-shadow: none; } }
html nav.tabs .tab[aria-current="page"] { color: color-mix(in srgb, var(--accent) 60%, var(--ink)); }
@media (min-width: 641px) and (pointer: fine) { .tab[aria-current="page"] { background: color-mix(in srgb, var(--accent) 14%, var(--surface)); box-shadow: none; } }
html:not([data-theme="light"]) .tab[aria-current="page"], html:not([data-theme="light"]) nav.tabs .tab[aria-current="page"] { background: rgb(110 160 255 / .16); }
/* the plan lines keep room for three lines while the language swaps (the options list stays hidden while empty) */
#planOpts:not(:empty) { min-height: calc(var(--fs-sm) * var(--lh) * 3); }
#planTerms { min-height: 0; }
/* phone: the update banner is one line, the text shortens with an ellipsis and Refresh is a quiet text button at its end */
@media (max-width: 640px) {
  .banner.upd { flex-wrap: nowrap; gap: var(--s2); }
  .banner.upd #updText { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .banner.upd #updRefresh { flex: none; min-height: var(--tap); border-color: transparent; background: transparent; color: var(--accent); text-decoration: underline; text-underline-offset: 3px; padding-inline: var(--s2); box-shadow: none; }
  #subscribe, #planFirst ~ #subscribe { width: 100%; flex: 1 1 100%; justify-content: center; }
}
/* the Get started step 5 row: the one filled button sits alone, full width on a phone */
#startMakeRow:not([hidden]) { display: flex; margin: var(--s2) 0 0; }
#startMakeRow > .btn { min-height: var(--tap); }
@media (max-width: 640px) { #startMakeRow > .btn { flex: 1 1 100%; justify-content: center; } }
/* L16 R3-cd-1, R3-bench-1, R3-rage-1: a price, its period and "once" are never broken; the name takes the room that is left */
.planPrice { white-space: nowrap; overflow-wrap: normal; max-width: none; font-variant-numeric: tabular-nums; }
label.planOpt { grid-template-columns: 24px minmax(0, 1fr) max-content; }
@media (max-width: 420px) { .planPrice { max-width: 100%; } }

/* ---- shell-css L17: R3-cd-2/3/6 Today card, R3-cd-5 furniture, R3-cd-7 Settings weight, R3-bench-4/cd-4 sample look, R3-speed-16 ---- */
/* R3-cd-2: the test name is the big line; "in N days" (a real, school-set deadline) is the largest part of the line under it */
#testMeta .title { font-size: var(--fs-xl); }
#testMeta .meta > span.muted { font-size: var(--fs-lg); font-weight: var(--fw-b); color: var(--ink); }
/* R3-cd-6: no card in a card: the study-content panel is a plain section inside the Today card; one filled button (the link to the page), "Make it again" is a quiet text link */
.focusCard #aiPanel { background: transparent; border: 0; border-top: 1px solid var(--line); border-radius: 0; box-shadow: none; padding: var(--s3) 0 0; margin-top: var(--s3); }
.focusCard #aiPanel .aiAgain { border-color: transparent; background: transparent; color: color-mix(in srgb, var(--accent) 65%, var(--ink)); box-shadow: none; text-decoration: underline; text-underline-offset: 3px; padding-inline: var(--s2); }
.focusCard #aiPanel .aiAgain:hover { color: var(--ink); }
.focusCard #aiPanel .aiOpen { margin-top: 0; }
/* R3-cd-6: the test chips are one scrolling row, never an orphan on a second line */
#testPicker { flex-wrap: nowrap; overflow-x: auto; overscroll-behavior-x: contain; scrollbar-width: thin; padding-bottom: var(--s1); }
#testPicker > .chip { flex: none; max-width: 80%; }
/* R3-cd-3: the grade block reads as one calm row: the scale line is small and quiet, "Hide done tasks" is a small line at the end of the progress row */
.focusCard .scaleBox { margin: 0; }
#nextGradeHint { margin: var(--s1) 0; }
.progressSticky label.hideDone { justify-content: flex-end; margin: 0; font-size: var(--fs-sm); color: var(--muted); }
.progressSticky label.hideDone > span { font-size: var(--fs-sm); color: var(--muted); }
/* the learnt box: the two extra lines are plain muted text on their own row */
.learntBox .learntLook, .learntBox .learntAhead { flex: 1 1 100%; margin: 0; font-size: var(--fs-sm); color: var(--muted); overflow-wrap: anywhere; }
.learntBox .learntLook[hidden], .learntBox .learntAhead[hidden] { display: none; }
/* the store problem note: a calm strip, its button on its own row (shell-js builds it with .note.is-error) */
#storeNote { margin: var(--s3) 0; }
#storeNote > p { margin: 0 0 var(--s2); overflow-wrap: anywhere; }
#storeNote .row { margin: 0; }
#storeNote .btn { min-height: var(--tap); }
/* R3-cd-5: "Updated ..." is a small grey line, not a blue bar; the language select has no loud border; the long Moodle sentence shows in Settings only */
.banner.upd { background: transparent; padding: 0; justify-content: flex-end; text-align: end; color: var(--muted); font-size: var(--fs-xs); margin-top: var(--s1); }
.banner.upd.stale { background: var(--warn-soft); padding: var(--s2) var(--s3); color: var(--ink); }
.top #lang { border-color: transparent; background: transparent; color: var(--muted); min-width: 0; font-size: var(--fs-sm); }
.top #lang:hover, .top #lang:focus-visible { border-color: var(--line); color: var(--ink); }
.foot > span[data-i18n="legal.disclaimer"] { display: none; }
body:has(main section.view[data-view="settings"]:not([hidden])) .foot > span[data-i18n="legal.disclaimer"] { display: block; }
.foot { font-size: var(--fs-sm); }
/* R4-a11y-7: footer links are 44 px wide too */
.foot a { padding-inline: var(--s2); margin-inline-end: 0; min-width: var(--tap); text-align: center; }
/* R4-ftu-5: anchors and focus never hide under the sticky top bar */
html { scroll-padding-top: calc(var(--header-h) + var(--s3)); }
.learntBox .textLink, .learntBox a { color: color-mix(in srgb, var(--accent) 65%, var(--ink)); }
/* R3-cd-7: Settings is not seven equal cards: Account leads, the others are flat; the danger zone is a single quiet row until it is opened */
.settingsCol > .setCard:not(#accountBox, #dangerBox) { box-shadow: none; background: transparent; }
#dangerBox { background: transparent; border: 1px solid var(--line); border-radius: var(--r-m); padding: 0 var(--s4); }
#dangerBox[open] { padding-bottom: var(--s3); }
#dangerBox > summary { color: var(--muted); }
/* R3-speed-16: the plan chooser keeps its height while the slider/price lines are redrawn, so the page below does not jump */
#planBox:not([hidden]) { min-height: calc(var(--tap) * 5); }
/* R3-bench-4, R3-cd-4, R4-a11y-1: sample items keep full-contrast text; they are dashed, tagged "Sample" and flat, never faded */
#testPicker > .chip:is(.isSample, [data-sample="1"]) { border-style: dashed; }
main[data-sample="1"] :is(#focusCard, #nextCard) { background: transparent; box-shadow: none; }
.tag.isSample, .chip .tag { background: var(--accent-soft); color: color-mix(in srgb, var(--accent) 60%, var(--ink)); }

/* ---- shell-css L18: the last Today/Material/Settings touches (R3-cd-2/3/4/6/7/8) ---- */
/* the days left: the biggest, boldest part of the line under the test name (a real, school-set deadline only) */
#testMeta .meta .testWhen { font-size: var(--fs-lg); font-weight: var(--fw-b); color: var(--ink); }
/* the grade number is a button that opens the slider: 44 px tap box, a quiet underline that says "tap me" */
#gradeToggle { min-width: var(--tap); min-height: var(--tap); font-size: var(--fs-xl); font-variant-numeric: tabular-nums; text-decoration-thickness: 2px; }
#gradeToggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (hover: hover) { #gradeToggle:hover { color: var(--accent); text-decoration-color: currentColor; } }
/* sample items: the "Sample" tag is a small quiet pill; the dashed look comes from .isSample (no fade) */
.sampleTag { vertical-align: middle; font-weight: var(--fw-b); white-space: nowrap; }
.isSample { border-style: dashed; }
/* a finished test in the picker: calm, with a quiet "Done" word, still readable and tappable */
.chip.chipDone { color: var(--muted); border-color: var(--line); background: transparent; }
.chip.chipDone[aria-pressed="true"] { color: var(--ink); background: var(--accent-soft); border-color: var(--accent); }
/* the study-content panel inside the Today card: no border of its own */
#aiPanel.flat { border: 0; box-shadow: none; background: transparent; }
/* "3 tasks to practise again" + its link: one small quiet line, the link a full tap target */
#practiseRow { margin: var(--s2) 0 0; color: var(--muted); overflow-wrap: anywhere; }
#practiseRow a { color: color-mix(in srgb, var(--accent) 65%, var(--ink)); display: inline-flex; align-items: center; min-height: var(--tap); font-weight: var(--fw-b); }
/* Material: the top summary is one calm line */
#materialSummary { margin: 0 0 var(--s3); color: var(--muted); font-size: var(--fs-sm); overflow-wrap: anywhere; }
#materialSummary:not([hidden]) { color: var(--ink); }
/* the test chips: one scrolling row at any width */
#testPicker { flex-wrap: nowrap; overflow-x: auto; -webkit-overflow-scrolling: touch; }
/* Settings: three folds (details.setGroup > summary > h2, R3-cd-7); the summary is a plain heading row with the same chevron as the danger zone */
.settingsCol > details.setGroup { display: flex; flex-direction: column; gap: var(--s4); margin: 0; }
details.setGroup > summary { list-style: none; display: flex; align-items: center; justify-content: space-between; min-height: var(--tap); margin: var(--s3) 0 0; cursor: pointer; }
details.setGroup > summary::-webkit-details-marker { display: none; }
details.setGroup > summary > h2 { margin: 0; font-size: var(--fs-md); font-weight: var(--fw-b); }
details.setGroup > summary::after { content: ""; flex: none; width: 8px; height: 8px; border: solid var(--muted); border-width: 0 2px 2px 0; transform: rotate(-45deg); transition: transform var(--d-fast) var(--ease-std); }
details.setGroup[open] > summary::after { transform: rotate(45deg); }
details.setGroup[open] > summary { margin-bottom: var(--s2); }
details.setGroup > .setCard + .setCard { margin-top: var(--s3); }
/* the language choice: one quiet row above the footer on every page (R3-cd-5) */
.langFoot { max-width: var(--content); margin: var(--s6) auto 0; display: flex; align-items: center; gap: var(--s2); }
.langFoot #lang { min-height: var(--tap); border-color: transparent; background: transparent; color: var(--muted); font-size: var(--fs-sm); min-width: 0; }
.langFoot #lang:hover, .langFoot #lang:focus-visible { border-color: var(--line); color: var(--ink); }
.langFoot + .foot { margin-top: var(--s2); }
/* "Hide done tasks": a small text link that is a switch; plain look, full 44 px tap box */
button.hideDone { display: inline-flex; align-items: center; min-height: var(--tap); padding: 0 var(--s2); border: 0; background: none; font: inherit; font-size: var(--fs-sm); color: var(--muted); text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }
button.hideDone[aria-checked="true"] { color: var(--ink); font-weight: var(--fw-b); }
button.hideDone[aria-checked="true"]::before { content: "\2713\00a0"; content: "\2713\00a0" / ""; }   /* the tick is decoration: the alternative text keeps it out of the button name (L12-8) */
button.hideDone:is(:hover, :focus-visible) { color: var(--ink); }

/* ---- round 1 of the launch loops (job C: the app shell) ---- */
/* L01-A7: a quiet text link, for the choice that is not the main action ("Use another e-mail", "See Plus") */
.quietLink { display: inline-flex; align-items: center; min-height: var(--tap); padding: 0 var(--s2); border: 0; background: none; font: inherit; font-size: var(--fs-sm); color: color-mix(in srgb, var(--accent) 65%, var(--ink)); text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }
.quietLink:hover { color: var(--ink); }
/* L05-I1 and L01-4: the sign-in step: birth month and year first, then a short honest strip, then the e-mail */
#preAge .row { margin: var(--s2) 0 0; align-items: flex-end; }
#preAgeLive { margin: var(--s1) 0 0; }
#preAgeMsg:not([hidden]) { display: block; margin: var(--s2) 0; }
.trustStrip { margin: var(--s3) 0 var(--s2); font-size: var(--fs-sm); color: var(--muted); }
.trustStrip ul { list-style: none; margin: 0 0 var(--s2); padding: 0; display: grid; gap: var(--s1); }
.trustStrip li, .trustStrip .trustLine { position: relative; padding-inline-start: var(--s4); margin: 0; }
.trustStrip li::before, .trustStrip .trustLine::before { content: "\2713"; position: absolute; inset-inline-start: 0; color: var(--ok); }
.trustMore { margin: 0; }
#blockedRow > p { margin: 0 0 var(--s2); }
/* L01-5: one clear next step on Today; L01-2: the phone's "import on your computer first" line can be read */
.firstWin { margin: var(--s3) 0 var(--s2); font-weight: var(--fw-b); }
.banner.upd.is-note { justify-content: flex-start; text-align: start; background: var(--accent-soft); padding: var(--s2) var(--s3); color: var(--ink); font-size: var(--fs-sm); }
/* L04-1: the maths row sits after the task list, outside the card */
.mathRow { margin: var(--s3) 0; }
/* L04-A1: the chip row says there is more beyond its edge */
#testPicker[data-more-end="1"] { -webkit-mask-image: linear-gradient(to right, black calc(100% - 28px), transparent); mask-image: linear-gradient(to right, black calc(100% - 28px), transparent); }
#testPicker[data-more-start="1"] { -webkit-mask-image: linear-gradient(to left, black calc(100% - 28px), transparent); mask-image: linear-gradient(to left, black calc(100% - 28px), transparent); }
#testPicker[data-more-start="1"][data-more-end="1"] { -webkit-mask-image: linear-gradient(to right, transparent, black 28px, black calc(100% - 28px), transparent); mask-image: linear-gradient(to right, transparent, black 28px, black calc(100% - 28px), transparent); }
/* L04-11: the Material summary is a strip with the page's soft colour, the filter has a visible name and the full width */
#materialSummary { padding: var(--s2) var(--s3); border-radius: var(--r-s); background: var(--accent-soft); }
.filterBox { display: block; flex: 1 1 100%; margin: 0; }
.filterBox > input { width: 100%; }
/* L04-6 / L04-7: Settings groups look alike: the Danger zone row has the weight, size and colour of the other group headings */
#dangerBox > summary { color: var(--ink); font-size: var(--fs-md); font-weight: var(--fw-b); }
/* L04-9: on a phone the grading-scale name sits above its select, not squeezed beside it */
@media (max-width: 480px) {
  #scaleBox label.inline { flex-direction: column; align-items: stretch; gap: var(--s1); }
  #scaleBox label.inline > span { display: block; }
  #scaleBox select { width: 100%; }
}
/* L01-A8 and L01-A9: on a phone the round chat button never sits on the last Get started row; the Today header takes less height */
@media (max-width: 640px), (pointer: coarse) {
  main[data-onboard] #startCard { margin-bottom: calc(var(--tap) + var(--s3)); }
  .view[data-view="today"] > h1 { margin-block: var(--s2) var(--s1); }
  .banner.upd { margin-top: 0; }
  .toast { width: calc(100vw - 24px); }   /* the message gets the whole width (loop L04-8) */
}
/* the plan card's Free / Trial / Plus table (planCompare 'table', loop L09-6): plain numbers, no colour of its own */
.planCmp { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); margin: var(--s2) 0; }
.planCmp th, .planCmp td { padding: var(--s1) var(--s2); text-align: center; border-bottom: 1px solid var(--line); }
.planCmp tbody th { text-align: start; font-weight: normal; color: var(--muted); }
.planCmp thead th { font-weight: var(--fw-b); }

/* ---- round 3, job AX: accessibility (L12-1, 4, 6, 8, 10, 11, A6) ---- */
/* L12-1: the edge of a field is its only edge, so it is 3:1 against the card in both themes (--field-line is --off, tokens.css) */
input[type="checkbox"], input[type="radio"], input[type="range"] { border-color: initial; }
/* L12-4: a control that is busy or saving is aria-disabled, not disabled: focus stays on it and it looks the same as a disabled one */
.btn[aria-disabled="true"] { opacity: 1; color: var(--muted); background: var(--bg); cursor: default; }
.btn.primary[aria-disabled="true"] { background: var(--accent-soft); color: color-mix(in srgb, var(--accent) 75%, var(--ink)); border-color: var(--accent); }
.switch[aria-disabled="true"] { cursor: progress; }
/* L12-6: focus never ends up under a fixed bar (2.4.11): the sticky progress bar (about 100 px) and the phone's tab bar plus chat button */
li.task, #taskList > li { scroll-margin-top: calc(var(--header-h) + 100px); }
@media (max-width: 640px) { li.task, #taskList > li { scroll-margin-top: 110px; } html { scroll-padding-bottom: calc(var(--bar-h) + var(--s7) + var(--s4) + var(--safe-b)); } }
/* L12-10: Windows High Contrast: state that only a background drew is given a border or a system colour */
@media (forced-colors: active) {
  .switch span { border: 2px solid ButtonText; forced-color-adjust: none; background: Canvas; }
  .switch span::after { background: ButtonText; }
  .switch input:checked + span { background: Highlight; }
  .switch input:checked + span::after { background: HighlightText; }
  .bar, .card .bar { border: 1px solid CanvasText; background: Canvas; }
  .bar > i, .card .bar > i { background: Highlight; forced-color-adjust: none; }
  .tab[aria-current="page"] { border-bottom: 3px solid Highlight; }
  .chip[aria-pressed="true"] { border-width: 3px; }
  .btn[aria-disabled="true"], .btn[disabled] { color: GrayText; border-color: GrayText; }
  select, input, textarea { border-color: ButtonText; }
  .toast { border: 1px solid CanvasText; }
  button.hideDone[aria-checked="true"] { text-decoration: underline; }
}

/* install.css */
/* install.css - the quiet "add to Home Screen" block (install.js). Tokens only; no filled button. */
.ti-install { background: var(--accent-soft); border-radius: var(--r-m); padding: var(--s4); margin: var(--s3) 0; max-width: 100%; box-sizing: border-box; }
.ti-install .ti-title { margin: 0 0 var(--s2); font-size: var(--fs-md); font-weight: var(--fw-b); }
.ti-install .ti-steps { margin: 0; padding: 0; list-style: none; counter-reset: ti; }
.ti-install .ti-steps li { counter-increment: ti; display: flex; align-items: center; gap: var(--s3); min-height: var(--tap); }
.ti-install .ti-steps li::before { content: counter(ti); flex: none; width: 1.75em; height: 1.75em; border-radius: var(--r-pill); background: var(--surface); color: var(--accent); font-weight: var(--fw-b); display: inline-flex; align-items: center; justify-content: center; }
.ti-install .ti-icon { flex: none; width: var(--tap); height: var(--tap); color: var(--accent); }
.ti-install .ti-why { margin: var(--s2) 0 0; font-size: var(--fs-sm); }
.ti-install .btn { min-height: var(--tap); }

/* reader.css */
/* reader.css - the "Read pictures and scans" panel (reader.js). No inline styles: everything is a class here. */
#readerPanel .readerBar { max-width: 420px; }
#readerPanel .readerBar > i { transition: width .2s; }
#readerPanel .readerNote { font-size: .92em; }
#readerPanel .readerError { color: var(--bad); }
#readerPanel .readerFailed { margin: 8px 0; border-top: 1px solid var(--line, #e1e5ec); }
#readerPanel .readerFailed summary { display: flex; align-items: center; min-height: var(--tap, 44px); cursor: pointer; list-style: none; color: var(--warn, #9a5b00); }
#readerPanel .readerFailed summary::-webkit-details-marker { display: none; }
#readerPanel .readerFailed summary::before { content: ''; flex: none; width: 7px; height: 7px; margin: 0 var(--s3, 12px) 0 var(--s1, 4px); border-right: 2px solid currentColor; border-bottom: 2px solid currentColor; transform: rotate(-45deg); transition: transform var(--d-fast, 120ms) var(--ease-out, ease-out); }
#readerPanel .readerFailed[open] > summary::before { transform: rotate(45deg); }
@media (prefers-reduced-motion: reduce) { #readerPanel .readerFailed summary::before { transition: none; } }
#readerPanel .readerFailed ul { margin: 0 0 8px; overflow-wrap: anywhere; }
#readerPanel .readerFormulaBox { margin-top: 10px; }
#readerPanel .readerFormula { padding: 8px 10px; border: 1px solid var(--line); border-radius: 10px; background: var(--card); overflow-x: auto; min-height: 2em; }
/* plain-bar fallback when the shared loading kit is not there: an unknown length is a half-lit, still bar (no motion needed) */
#readerPanel .readerBar.indeterminate > i, #aiPanel .aiBar.indeterminate > i, #lessonsPanel .lessonBar.indeterminate > i { opacity: .6; }
/* the combined "Get ready" run lives in the study content panel (lessons.js); its small pieces are styled here so lessons.css stays untouched */
#aiPanel .aiBar { max-width: 420px; margin: 8px 0; }
#aiPanel .aiStep { margin: 10px 0 0; }
#aiPanel .aiActs { margin-top: 8px; }
#aiPanel .aiNotes { margin: 6px 0 0; padding-left: 18px; }
#lessonsPanel .lessonSpin { display: inline-flex; align-items: center; }

/* lessons.css */
/* lessons.css - the video lessons panel and the study content panel (styles for lessons.js only). */
#lessonsPanel, #aiPanel { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow); padding: 14px 16px; margin-top: 16px; }
#lessonsPanel h2, #aiPanel h2 { margin-top: 0; }
#lessonsPanel h3 { font-size: 1rem; margin: 12px 0 6px; }
#lessonsPanel .lessonState { font-weight: 600; margin: 10px 0 2px; }
#lessonsPanel .lessonBar { max-width: var(--content, 640px); }
#lessonsPanel .lessonDone { color: var(--ok); font-weight: 600; }
#lessonsPanel .lessonError { color: var(--bad); overflow-wrap: anywhere; }
#lessonsPanel .credit { margin-bottom: 0; }
#aiPanel .aiMsg { overflow-wrap: anywhere; }
#aiPanel .aiPage { margin: 8px 0 0; }
#aiPanel .aiPage a { font-weight: 600; }
#lessonsPanel .ytList { list-style: none; padding: 0; }
#lessonsPanel .ytItem { margin: 8px 0; }
#lessonsPanel .ytEditor { margin-top: 6px; }
#lessonsPanel .ytText { display: block; width: 100%; max-width: 640px; margin: 6px 0; box-sizing: border-box; }
#lessonsPanel .ytMsg { overflow-wrap: anywhere; }

/* the lane's own progress bar: only transform moves; one slide while the length is unknown, nothing else loops */
#aiPanel .lbar, #lessonsPanel .lbar { display: block; width: 100%; height: 8px; border-radius: var(--r-pill, 999px); background: var(--line); overflow: hidden; }
#aiPanel .lbar > i, #lessonsPanel .lbar > i { display: block; height: 100%; width: 100%; background: var(--accent); transform-origin: left; transform: scaleX(var(--p, 0));
  transition: transform var(--d-base, 200ms) var(--ease-std, cubic-bezier(.2, 0, 0, 1)); }
#aiPanel .lbar.is-indet > i, #lessonsPanel .lbar.is-indet > i { width: 35%; transform: none; transition: none; animation: lbarSlide 1.5s linear infinite; }
#aiPanel .lbar.is-full > i, #lessonsPanel .lbar.is-full > i { background: var(--ok); animation: none; }
@keyframes lbarSlide { from { transform: translateX(-100%); } to { transform: translateX(285%); } }

/* the video lessons panel: Start and Stop keep full strength while they work (label and aria-busy say so, no spinner) */
#lessonsPanel .row button[disabled] { opacity: 1; cursor: progress; }
#lessonsPanel .row button.primary[disabled] { background: var(--accent); color: var(--on-accent, #fff); }
#lessonsPanel .lessonStatus .lbar { max-width: var(--content, 640px); margin: var(--s2, 8px) 0; }
#lessonsPanel .lessonNote { margin: var(--s2, 8px) 0 0; }
#lessonsPanel .lessonSkip { margin: var(--s2, 8px) 0; overflow-wrap: anywhere; }
#lessonsPanel .lessonWhy { display: block; color: var(--muted, #5b6472); font-size: var(--fs-sm, .875rem); }
#lessonsPanel .aiDetail { margin: var(--s1, 4px) 0 0; color: var(--muted, #5b6472); font-size: var(--fs-sm, .875rem); overflow-wrap: anywhere; }
#lessonsPanel .aiDetail summary { cursor: pointer; }

/* the make-content card */
#aiPanel #aiMake[disabled] { opacity: 1; cursor: progress; background: var(--accent); color: var(--on-accent, #fff); }
#aiPanel .aiStage { margin: var(--s2, 8px) 0; }
#aiPanel .aiWait { margin: var(--s1, 4px) 0 0; overflow-wrap: anywhere; }
#aiPanel .aiStageStep { margin-inline-start: var(--s2, 8px); font-size: var(--fs-sm, .875rem); }
#aiPanel[data-state="busy"] .aiProgress { min-height: 56px; }
#aiPanel .aiProgress:not([hidden]) { animation: aiFadeIn var(--d-base, 200ms) var(--ease-out, cubic-bezier(.2, 0, 0, 1)); }
#aiPanel .aiProgress.is-out { opacity: 0; transition: opacity var(--d-fast, 120ms) var(--ease-in, cubic-bezier(.4, 0, 1, 1)); animation: none; }
@keyframes aiFadeIn { from { opacity: 0; } to { opacity: 1; } }
#aiPanel .aiDetail, #aiPanel .aiKept, #aiPanel .aiWhen { margin: var(--s1, 4px) 0 0; overflow-wrap: anywhere; }
#aiPanel .aiOpen { display: inline-flex; align-items: center; justify-content: center; min-height: var(--tap, 44px); text-decoration: none; margin-top: var(--s3, 12px); }
#aiPanel .aiPage { margin: 0; }
#aiPanel .aiMakeRow { margin-top: var(--s3, 12px); }
#aiPanel .aiAgain { min-height: var(--tap, 44px); }
#aiPanel { background: transparent; border: 0; box-shadow: none; padding: 0; }
.focusCard #aiPanel { border-top: 1px solid var(--line); padding-top: var(--s3, 12px); }
#aiPanel .aiHead { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--s2, 8px); }
#aiPanel .aiHead .aiPage { margin-inline-start: auto; }
#aiPanel .aiHead .aiOpen { margin-top: 0; }
#aiPanel[data-state="ready"] .aiMakeRow { margin-top: var(--s1, 4px); }

@media (prefers-reduced-motion: reduce) {
  #aiPanel .lbar > i, #lessonsPanel .lbar > i, #aiPanel .aiProgress, #aiPanel .aiProgress.is-out { transition: none; animation: none; }
  #aiPanel .lbar.is-indet > i, #lessonsPanel .lbar.is-indet > i { width: 50%; transform: none; animation: none; }
}

/* small screens and fingers: every button and button-link is at least 44 px tall, neighbours 8 px apart, small text stays readable */
#lessonsPanel button, #lessonsPanel a.btn, #aiPanel button, #aiPanel a.btn { min-height: var(--tap, 44px); padding-inline: var(--s4, 16px); }
#lessonsPanel .row, #aiPanel .row, #lessonsPanel .ytItem .row { gap: var(--s2, 8px); }
#aiPanel .aiLang { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s2, 8px); }
#lessonsPanel .small, #aiPanel .small { font-size: var(--fs-sm, .875rem); }
#lessonsPanel .muted, #aiPanel .muted { color: var(--muted, #566074); }
#lessonsPanel .lessonDone, #lessonsPanel .lessonError { font-size: var(--fs-md, 1rem); }
#lessonsPanel .ytText { font-size: 16px; min-height: 120px; }
#aiPanel .aiPhone { margin: 0; }

/* blocks that come and go: in = opacity and 4 px over 200 ms, out = opacity only over 120 ms; the heights that jumped are reserved */
#lessonsPanel .blk, #aiPanel .blk { opacity: 0; transform: translateY(4px);
  transition: opacity var(--d-fast, 120ms) var(--ease-in, cubic-bezier(.4, 0, 1, 1)); }
#lessonsPanel .blk.is-in, #aiPanel .blk.is-in { opacity: 1; transform: none;
  transition: opacity var(--d-base, 200ms) var(--ease-out, cubic-bezier(.2, 0, 0, 1)), transform var(--d-base, 200ms) var(--ease-out, cubic-bezier(.2, 0, 0, 1)); }
#lessonsPanel .lessonStatus.is-run { min-height: 64px; }

@media (prefers-reduced-motion: reduce) {
  #lessonsPanel .blk, #aiPanel .blk, #lessonsPanel .blk.is-in, #aiPanel .blk.is-in { transition: none; transform: none; }
}
#aiPanel .aiProvider { margin: var(--s2, 8px) 0 0; font-size: var(--fs-sm, .875rem); }
#aiPanel .aiProviderLink { color: var(--accent, #2f5fd0); text-decoration: underline; }
/* round 3 AX (L12-10): Windows High Contrast */
@media (forced-colors: active) {
  #aiPanel .lbar, #lessonsPanel .lbar { border: 1px solid CanvasText; background: Canvas; }
  #aiPanel .lbar > i, #lessonsPanel .lbar > i { background: Highlight; forced-color-adjust: none; }
}

/* loading.css */
/* loading.css - the shared loading kit (window.TinyLoading in loading.js). Only the app's CSS variables, with fallbacks for pages that do not load app.css.
   Motion: transform and opacity only. The one endless loop is the spinner rotation (tl-rot) and the indeterminate bar (tl-slide) while real work runs. */
.tl-bar[hidden] { display: none; }
.tl-bar { display: block; width: 100%; margin-top: var(--s2, 8px); }
.tl-bar .tl-track { display: block; position: relative; height: 10px; border-radius: var(--r-pill, 999px); background: var(--line, #dfe4ec); overflow: hidden; }
.tl-bar .tl-fill { position: absolute; inset: 0; display: block; background: var(--accent, #1f5eff); transform-origin: left; transform: scaleX(max(calc(var(--tl-pct, 0) / 100), .02)); transition: transform 200ms var(--ease-std, cubic-bezier(.2, 0, 0, 1)); }
.tl-bar.is-indeterminate .tl-fill { right: auto; width: 35%; transform: translateX(-100%); transition: none; animation: tl-slide 1400ms cubic-bezier(.4, 0, .2, 1) infinite; }
.tl-bar .tl-label { display: block; margin-top: var(--s1, 4px); font-size: var(--fs-sm, 14px); color: var(--muted, #5b6675); }
.tl-bar .tl-label:empty { display: none; }
@keyframes tl-slide { from { transform: translateX(-100%); } to { transform: translateX(285%); } }

.tl-spin { position: relative; display: inline-block; width: 24px; height: 24px; vertical-align: middle; flex: none; }
.tl-spin.is-lg { width: 24px; height: 24px; }
.tl-spin.is-sm { width: 16px; height: 16px; }
.tl-spin > i { display: block; box-sizing: border-box; width: 100%; height: 100%; border-radius: 50%; border: 2px solid var(--accent, #1f5eff); border-bottom-color: transparent; animation: tl-rot 1000ms linear infinite; }
.tl-spin-wrap { display: inline-flex; align-items: center; gap: var(--s3, 12px); }
.tl-spin-wrap .tl-spin-label { color: var(--muted, #5b6675); }
@keyframes tl-rot { to { transform: rotate(360deg); } }

.tl-busy { cursor: progress; display: inline-flex; align-items: center; justify-content: center; gap: var(--s2, 8px); }
.tl-busy, .tl-busy:disabled { opacity: 1; }
.tl-busy.btn { transition: transform var(--d-fast, 120ms) var(--ease-std, cubic-bezier(.2, 0, 0, 1)); }
.tl-busy.btn:active { transform: scale(.98); }
.tl-busy.primary .tl-spin > i { border-color: var(--on-accent, var(--accent-ink, #fff)); border-bottom-color: transparent; }
.tl-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

/* skeleton: still placeholder blocks for the first paint (loading.js skeleton) */
.tl-skel { display: grid; gap: var(--s3, 12px); animation: tl-skel-in 150ms var(--ease-out, cubic-bezier(.05, .7, .1, 1)) backwards; }
.tl-skel.is-out { animation: tl-skel-out 120ms var(--ease-in, cubic-bezier(.3, 0, .8, .15)) forwards; }
.tl-skel-title { height: 28px; width: 40%; border-radius: var(--r-s, 8px); background: color-mix(in srgb, var(--line, #dfe4ec) 60%, var(--surface, var(--card, #fff))); }
.tl-skel-card { height: 96px; border-radius: var(--r-m, 16px); background: color-mix(in srgb, var(--line, #dfe4ec) 60%, var(--surface, var(--card, #fff))); }
@keyframes tl-skel-in { from { opacity: 0; } }
@keyframes tl-skel-out { to { opacity: 0; } }

/* language select on standalone pages (langpick.js): a fixed size, so changing the language never moves the header */
header.top > .tl-lang, [data-langpick-slot] > .tl-lang { margin-left: auto; }
.tl-lang { box-sizing: border-box; width: 10rem; max-width: 100%; min-height: var(--tap, 44px); font: inherit; font-size: var(--fs-sm, 14px); color: var(--ink, #172033); background: var(--surface, var(--card, #fff)); border: 1px solid var(--line, #dfe4ec); border-radius: var(--r-s, 8px); padding: 0 var(--s2, 8px); }
.tl-lang:focus-visible, .tl-busy:focus-visible { outline: 2px solid var(--accent, #1f5eff); outline-offset: 2px; }
html.tl-lang-out { opacity: 0; transition: opacity 100ms var(--ease-in, cubic-bezier(.3, 0, .8, .15)); }
html.tl-lang-in { animation: tl-lang-in 150ms var(--ease-out, cubic-bezier(.05, .7, .1, 1)) backwards; }
@keyframes tl-lang-in { from { opacity: 0; } }

@media (prefers-reduced-motion: reduce) {
  .tl-bar .tl-fill, .tl-bar.is-indeterminate .tl-fill, .tl-spin > i, .tl-skel, .tl-skel.is-out, html.tl-lang-in { animation: none; transition: none; }
  html.tl-lang-out { transition: none; opacity: 1; }
  .tl-busy.btn { transition: none; }
  .tl-bar.is-indeterminate .tl-fill { width: 40%; transform: none; margin-left: 30%; }
}
/* round 3 AX (L12-10): Windows High Contrast: the track gets an edge, the fill a system colour */
@media (forced-colors: active) { .tl-bar .tl-track { border: 1px solid CanvasText; background: Canvas; } .tl-bar .tl-fill { background: Highlight; forced-color-adjust: none; } }

/* guide.css */
/* guide.css - the guide kit (guide.js) and the "Get started" card. Colours come from the app tokens (with fallbacks), so light and dark both work.
   Nothing here takes a click except the .tg-back pill: every guide item is pointer-events none. Every animation is finite (the ring pulses
   --tg-plays times, the arrow bobs 3 times, the ghost drags --tg-plays times) and uses transform and opacity only; after it the item is .is-still.
   prefers-reduced-motion: no animation at all, a static ring, arrow and label. */
.tg-layer { position: fixed; inset: 0; pointer-events: none; z-index: 60; overflow: hidden; }
.tg-item, .tg-item * { pointer-events: none; }
.tg-item[hidden] { display: none; }
.tg-item.is-fadein { animation: tg-fadein 200ms var(--ease-out, cubic-bezier(.05, .7, .1, 1)) 150ms backwards; }
@keyframes tg-fadein { from { opacity: 0; } }

/* point: a still ring (it pulses by an overlay), an arrow and a label placed by guide.js on the side with no text */
.tg-ring { position: fixed; box-sizing: border-box; border: 2px solid var(--accent, #1f5eff); border-radius: var(--r-s, 8px); }
.tg-ring::after { content: ""; position: absolute; inset: -2px; border: 2px solid var(--accent, #1f5eff); border-radius: var(--r-s, 8px); opacity: 0; }
.tg-item.is-play .tg-ring::after { animation: tg-pulse 1200ms var(--ease-out, cubic-bezier(.05, .7, .1, 1)) var(--tg-plays, 2); }
@keyframes tg-pulse { from { transform: scale(1); opacity: .5; } to { transform: scale(1.12); opacity: 0; } }
.tg-pin { position: fixed; left: 0; top: 0; width: 0; height: 0; }
.tg-pin .tg-arrowHold { position: fixed; left: 0; top: 0; display: block; width: 28px; height: 28px; line-height: 0; transform: rotate(var(--tg-rot, 0deg)); }
.tg-arrow { display: block; width: 28px; height: 28px; color: var(--accent, #1f5eff); filter: drop-shadow(0 2px 2px color-mix(in srgb, var(--ink, #172033) 25%, transparent)); }
.tg-item.is-play .tg-arrow { animation: tg-bob 900ms ease-in-out 3; }
@keyframes tg-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-6px); } }
.tg-label { display: inline-block; box-sizing: border-box; max-width: min(240px, calc(100vw - 32px)); background: var(--surface, var(--card, #fff)); color: var(--ink, #172033); border: 1px solid var(--accent, #1f5eff); border-radius: var(--r-s, 8px); padding: var(--s2, 8px) var(--s3, 12px); font-size: var(--fs-sm, 14px); font-weight: 600; line-height: 1.3; text-align: center; box-shadow: var(--e1, 0 4px 14px rgb(0 0 0 / .18)); }
.tg-label[hidden] { display: none; }
.tg-pin .tg-label { position: fixed; left: 0; top: 0; }

/* the "Back to the step" pill: the only guide part that takes clicks (guide.js puts it in the page body, outside the aria-hidden layer) */
.tg-back { position: fixed; left: 50%; bottom: 76px; transform: translateX(-50%); z-index: 61; box-sizing: border-box; min-height: 44px; padding: 0 var(--s4, 16px); border: 1px solid var(--accent, #1f5eff); border-radius: var(--r-pill, 999px); background: var(--surface, var(--card, #fff)); color: var(--ink, #172033); font: inherit; font-size: var(--fs-sm, 14px); font-weight: 600; cursor: pointer; box-shadow: var(--e1, 0 4px 14px rgb(0 0 0 / .18)); animation: tg-fadein 200ms var(--ease-out, cubic-bezier(.05, .7, .1, 1)) backwards; }
.tg-back[hidden] { display: none; }
.tg-back:focus-visible { outline: 2px solid var(--accent, #1f5eff); outline-offset: 2px; }

/* drag: a ghost copy moves from the element to the top-left of the drop zone, twice, then is gone; the zone stays as a still outline */
.tg-zone { position: fixed; box-sizing: border-box; display: flex; align-items: center; justify-content: center; gap: var(--s2, 8px); overflow: hidden; border: 2px dashed var(--accent, #1f5eff); border-radius: var(--r-s, 8px); background: color-mix(in srgb, var(--accent, #1f5eff) 14%, var(--surface, var(--card, #fff))); }
.tg-item.is-play .tg-zone { animation: tg-zone 1600ms ease-in-out var(--tg-plays, 2); }
@keyframes tg-zone { 0%, 100% { opacity: 1; } 50% { opacity: .75; } }
.tg-zone .tg-arrowHold { position: static; display: block; width: 20px; height: 20px; }
.tg-zone .tg-arrow { width: 20px; height: 20px; }
.tg-zone .tg-label { position: static; border: 0; box-shadow: none; background: transparent; padding: 0; font-size: var(--fs-sm, 14px); }
.tg-ghost { position: fixed; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid var(--accent, #1f5eff); border-radius: var(--r-s, 8px); background: color-mix(in srgb, var(--accent, #1f5eff) 16%, var(--surface, var(--card, #fff))); color: var(--ink, #172033); font-weight: 600; }
.tg-item.tg-fadeOut { opacity: 0; transition: opacity 200ms var(--ease-std, ease); pointer-events: none; }
.tg-item:not(.is-play) .tg-ghost { display: none; }
.tg-item.is-play .tg-ghost { animation: tg-drag 2400ms var(--ease-std, cubic-bezier(.2, 0, 0, 1)) var(--tg-plays, 2) forwards; }
.tg-ghostText { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; max-width: 100%; padding: 0 6px; }
.tg-cursor { position: absolute; right: -6px; bottom: -14px; width: 20px; height: 20px; color: var(--ink, #172033); filter: drop-shadow(0 1px 1px var(--surface, var(--card, #fff))); }
@keyframes tg-drag {
  0% { transform: translate(0, 0) scale(1); opacity: 0; }
  10%, 20% { transform: translate(0, 0) scale(1.06); opacity: 1; }
  70%, 82% { transform: translate(var(--tg-dx, 0px), var(--tg-dy, 0px)) scale(.94); opacity: 1; }
  92%, 100% { transform: translate(var(--tg-dx, 0px), var(--tg-dy, 0px)) scale(.94); opacity: 0; }
}

/* keys: a still row of keycaps (no animation); inline in the steps list when the caller passes opts.host, else one floating pill above the footer */
.tg-keys { display: flex; align-items: center; gap: var(--s3, 12px); flex-wrap: wrap; box-sizing: border-box; background: var(--surface, var(--card, #fff)); border: 1px solid var(--accent, #1f5eff); border-radius: var(--r-m, 16px); padding: var(--s2, 8px) var(--s3, 12px); box-shadow: var(--e1, 0 4px 14px rgb(0 0 0 / .18)); width: max-content; max-width: calc(100vw - 16px); }
.tg-keys.is-float { position: fixed; left: 50%; bottom: 76px; transform: translateX(-50%); }
.tg-keys.is-inline { position: relative; margin: var(--s2, 8px) 0; max-width: 100%; }
.tg-keys .tg-label { border: 0; box-shadow: none; background: transparent; padding: 0; text-align: start; }
.tg-keyrow { display: inline-flex; align-items: center; gap: var(--s2, 8px); }
.tg-plus { color: var(--muted, #5b6675); font-weight: 600; }
.tg-key { min-width: 2.3em; padding: var(--s1, 4px) var(--s2, 8px); border: 1px solid var(--line, #dfe4ec); border-bottom-width: 3px; border-radius: var(--r-s, 8px); background: var(--surface, var(--card, #fff)); color: var(--ink, #172033); font: 600 var(--fs-sm, 14px) system-ui, sans-serif; text-align: center; }

@media (prefers-reduced-motion: reduce) {
  .tg-item, .tg-item *, .tg-item *::after, .tg-back { animation: none !important; transition: none !important; }
  .tg-ring { box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent, #1f5eff) 25%, transparent); }
  .tg-ghost { opacity: .85; }
}

/* the single "Get started" card on Today: readable steps (no row opacity), the open step grows softly, finished rows can fold away */
.startCard { background: var(--surface, var(--card, #fff)); border: 1px solid var(--line, #dfe4ec); border-radius: var(--r-m, 16px); padding: var(--s3, 12px) var(--s4, 16px); margin: var(--s4, 16px) 0; box-shadow: var(--e1, 0 1px 2px rgb(16 24 40 / .06)); }
.startCard h2 { margin-top: 0; }
.startSteps { list-style: none; margin: var(--s3, 12px) 0 0; padding: 0; display: grid; gap: var(--s2, 8px); }
.step { border: 1px solid var(--line, #dfe4ec); border-radius: var(--r-s, 8px); padding: var(--s3, 12px); }
.step.is-current { border-color: var(--accent, #1f5eff); background: var(--accent-soft, color-mix(in srgb, var(--accent, #1f5eff) 10%, var(--surface, #fff))); }
.stepHead { display: flex; align-items: center; gap: var(--s3, 12px); }
.stepMark { flex: none; box-sizing: border-box; width: 26px; height: 26px; border-radius: 50%; border: 2px solid var(--line, #dfe4ec); display: inline-flex; align-items: center; justify-content: center; font-weight: 400; font-size: var(--fs-sm, 14px); color: var(--muted, #566074); }
.step.is-todo .stepTitle { font-weight: 400; color: var(--muted, #566074); }
.step.is-current .stepMark { background: var(--accent, #1f5eff); border-color: var(--accent, #1f5eff); color: var(--on-accent, var(--accent-ink, #fff)); font-weight: 600; }
.step.is-done .stepMark { background: var(--ok, #157347); border-color: var(--ok, #157347); color: var(--on-ok, light-dark(#fff, #0b1020)); font-weight: 600; animation: tg-tick 180ms var(--ease-out, cubic-bezier(.05, .7, .1, 1)) 1; }
@keyframes tg-tick { from { transform: scale(.8); } to { transform: scale(1); } }
.stepTitle { font-weight: 600; }
.step.is-done .stepTitle { font-weight: 400; color: var(--muted, #566074); text-decoration: line-through; text-decoration-thickness: 1px; }
.stepNow { margin-inline-start: auto; font-size: var(--fs-xs, 12px); font-weight: 600; color: var(--accent, #1f5eff); }
/* the step body opens by grid rows (a calm height change); .stepBodyIn is the clipped inner wrapper; [hidden] still means display none */
.stepBody { display: grid; grid-template-rows: 0fr; opacity: 0; visibility: hidden; margin: 0; margin-inline-start: 38px; transition: grid-template-rows var(--d-base, 200ms) var(--ease-std, cubic-bezier(.2, 0, 0, 1)), opacity var(--d-base, 200ms) var(--ease-std, cubic-bezier(.2, 0, 0, 1)), visibility 0s linear var(--d-base, 200ms); }
.stepBody[hidden] { display: none; }
.step.is-current .stepBody { grid-template-rows: 1fr; opacity: 1; visibility: visible; transition-delay: 0s; }
.stepBodyIn { overflow: hidden; min-height: 0; margin: 0 calc(-1 * var(--s1, 4px)); padding: 0 var(--s1, 4px); }
.stepBodyIn :focus-visible { outline-offset: -2px; }
.stepBodyIn > :first-child { margin-top: var(--s2, 8px); }
.stepBody p { margin: var(--s1, 4px) 0 var(--s2, 8px); }
.startDoneLine { display: inline-flex; align-items: center; gap: var(--s2, 8px); min-height: var(--tap, 44px); padding: 0 var(--s2, 8px); margin: 0 0 0 calc(-1 * var(--s2, 8px)); border: 0; border-radius: var(--r-s, 8px); background: transparent; color: var(--ink, #172033); font: inherit; font-size: var(--fs-sm, 14px); font-weight: 400; cursor: pointer; }
.startDoneLine::after { content: ""; width: 8px; height: 8px; border: solid currentColor; border-width: 0 2px 2px 0; transform: translateY(-2px) rotate(45deg); transition: transform var(--d-fast, 120ms) var(--ease-std, cubic-bezier(.2, 0, 0, 1)); }
.startDoneLine[aria-expanded="true"]::after { transform: translateY(2px) rotate(-135deg); }
.startDoneLine:focus-visible { outline: 2px solid var(--accent, #1f5eff); outline-offset: 2px; }
.startSteps.is-collapsed .step.is-done { display: none; }
@media (prefers-reduced-motion: reduce) {
  .stepBody, .step.is-done .stepMark, .startDoneLine::after { animation: none !important; transition: none !important; }
}

/* welcome.css */
/* welcome.css - the welcome card (welcome.js). Colours come from the app tokens (with fallbacks), so light and dark both work.
   The picture plays ONCE (under 2 s, transform and opacity only). Each element's own style is its END state and the keyframes only lead
   up to it (animation-fill backwards), so under prefers-reduced-motion (no animation) the card shows its finished picture. */
body.tw-open { overflow: hidden; }
.tw-back { position: fixed; inset: 0; z-index: 90; display: grid; place-items: center; padding: var(--s4, 16px); background: color-mix(in srgb, var(--ink, #172033) 55%, transparent); animation: tw-fade 200ms var(--ease-out, cubic-bezier(.05, .7, .1, 1)) backwards; }
.tw-back.is-leaving { animation: tw-fade-out 160ms var(--ease-in, cubic-bezier(.3, 0, .8, .15)) forwards; }
.tw-box { box-sizing: border-box; position: relative; display: flex; flex-direction: column; width: min(440px, 100%); max-height: calc(100vh - 32px); overflow: auto; background: var(--surface, var(--card, #fff)); color: var(--ink, #172033); border: 1px solid var(--line, #dfe4ec); border-radius: var(--r-m, 16px); padding: var(--s5, 20px); box-shadow: var(--e2, 0 12px 32px rgb(16 24 40 / .16)); outline: none; animation: tw-enter 240ms var(--ease-out, cubic-bezier(.05, .7, .1, 1)) backwards; }
.tw-box.is-leaving { animation: tw-fade-out 160ms var(--ease-in, cubic-bezier(.3, 0, .8, .15)) forwards; }
.tw-content { flex: 1 1 auto; transition: opacity 200ms var(--ease-out, cubic-bezier(.05, .7, .1, 1)); }
.tw-content.is-out { opacity: 0; transition-duration: 150ms; }
.tw-stage { height: 200px; margin: 0 0 var(--s4, 16px); border-radius: var(--r-s, 8px); background: color-mix(in srgb, var(--accent, #1f5eff) 7%, var(--bg, #f6f7fb)); border: 1px solid var(--line, #dfe4ec); overflow: hidden; position: relative; }
.tw-count { margin: 0; color: var(--accent, #1f5eff); font-size: var(--fs-xs, 12px); font-weight: 600; letter-spacing: .04em; text-transform: uppercase; }
.tw-title { margin: var(--s1, 4px) 0 var(--s2, 8px); font-size: var(--fs-lg, 20px); font-weight: 600; line-height: 1.25; }
.tw-body { margin: 0; color: var(--muted, #5b6675); font-size: var(--fs-md, 16px); }
.tw-list { margin: 0; padding: 0 0 0 var(--s5, 20px); display: grid; gap: var(--s1, 4px); font-size: var(--fs-md, 16px); font-weight: 400; color: var(--ink, #172033); line-height: 1.4; }
.tw-foot { display: flex; align-items: center; justify-content: space-between; gap: var(--s3, 12px); margin-top: var(--s5, 20px); }
.tw-next { min-width: 120px; min-height: 44px; justify-content: center; margin-left: auto; }
.tw-skip { min-height: 44px; border: 0; background: transparent; color: var(--ink, #172033); font: inherit; font-size: var(--fs-sm, 14px); font-weight: 400; cursor: pointer; padding: 0 var(--s3, 12px); margin-left: calc(-1 * var(--s3, 12px)); border-radius: var(--r-s, 8px); }
.tw-skip:hover { background: color-mix(in srgb, var(--line, #dfe4ec) 60%, transparent); }
.tw-dots { display: flex; gap: var(--s2, 8px); }
.tw-dot { width: 8px; height: 8px; border-radius: var(--r-pill, 999px); background: var(--line, #dfe4ec); transition: transform 150ms var(--ease-std, cubic-bezier(.2, 0, 0, 1)), background-color 150ms; }
.tw-dot.is-on { transform: scaleX(2.75); background: var(--accent, #1f5eff); }
.tw-pic { position: absolute; inset: 0; --tw-ease: var(--ease-out, cubic-bezier(.05, .7, .1, 1)); }

/* ---------- 1. import: Moodle page + bookmarks bar -> Tinystudy (about 1.7 s) ---------- */
.tw-import { --tw-moodle: #f98012; }
.tw-win { position: absolute; left: var(--s3, 12px); top: var(--s4, 16px); width: 54%; bottom: var(--s4, 16px); background: var(--surface, var(--card, #fff)); border: 1px solid var(--line, #dfe4ec); border-radius: var(--r-s, 8px); overflow: hidden; box-shadow: var(--e1, 0 1px 2px rgb(16 24 40 / .06)); }
.tw-winBar { display: flex; gap: var(--s1, 4px); padding: 6px var(--s2, 8px); border-bottom: 1px solid var(--line, #dfe4ec); }
.tw-winBar i { width: 8px; height: 8px; border-radius: 50%; background: var(--line, #dfe4ec); }
.tw-marks { display: flex; gap: var(--s1, 4px); align-items: center; padding: var(--s1, 4px) var(--s2, 8px); border-bottom: 1px solid var(--line, #dfe4ec); background: color-mix(in srgb, var(--line, #dfe4ec) 30%, var(--surface, var(--card, #fff))); }
.tw-markGhost { width: 24px; height: 8px; border-radius: var(--r-pill, 999px); background: var(--line, #dfe4ec); }
.tw-mark { font-size: var(--fs-xs, 12px); font-weight: 600; padding: 1px var(--s2, 8px); border-radius: var(--r-s, 8px); background: var(--accent, #1f5eff); color: var(--on-accent, var(--accent-ink, #fff)); animation: tw-land 400ms var(--tw-ease) 0ms 1 backwards, tw-press 200ms var(--tw-ease) 400ms 1 backwards; }
.tw-winPage { padding: 6px var(--s2, 8px); display: grid; gap: var(--s1, 4px); }
.tw-moodle { font-size: var(--fs-xs, 12px); font-weight: 600; color: var(--tw-moodle, #f98012); }
.tw-course { position: relative; display: flex; align-items: center; gap: 6px; font-size: var(--fs-xs, 12px); line-height: 1.3; padding: 3px 6px; border-radius: var(--r-s, 8px); border: 1px solid var(--line, #dfe4ec); white-space: nowrap; overflow: hidden; }
.tw-course::after { content: ""; position: absolute; inset: -1px; border: 1px solid var(--accent, #1f5eff); border-radius: var(--r-s, 8px); background: color-mix(in srgb, var(--accent, #1f5eff) 10%, transparent); pointer-events: none; animation: tw-pick 200ms var(--tw-ease) 600ms 1 backwards; }
.tw-course2::after { animation-delay: 750ms; }
.tw-course3::after { animation-delay: 900ms; }
.tw-course b { flex: none; width: 10px; height: 10px; border-radius: var(--r-pill, 999px); background: var(--grey, #9aa3b2); }
.tw-flow { position: absolute; left: calc(54% + 18px); top: 50%; transform: translateY(-50%); color: var(--accent, #1f5eff); font-size: var(--fs-lg, 20px); font-weight: 600; animation: tw-flow 400ms var(--tw-ease) 800ms 1 backwards; }
.tw-app { position: absolute; right: var(--s3, 12px); top: 30px; width: calc(46% - 56px); min-width: 110px; bottom: 30px; background: var(--surface, var(--card, #fff)); border: 2px solid var(--accent, #1f5eff); border-radius: var(--r-m, 16px); padding: var(--s2, 8px); display: grid; align-content: start; gap: var(--s1, 4px); }
.tw-appHead { font-size: var(--fs-xs, 12px); font-weight: 600; color: var(--accent, #1f5eff); }
.tw-got { font-size: var(--fs-xs, 12px); font-weight: 600; padding: 3px 6px; border-radius: var(--r-s, 8px); background: color-mix(in srgb, var(--ok, #1a7f4b) 14%, var(--surface, var(--card, #fff))); color: var(--ok, #1a7f4b); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; animation: tw-got 400ms var(--tw-ease) 1000ms 1 backwards; }
.tw-got2 { animation-delay: 1150ms; } .tw-got3 { animation-delay: 1300ms; }
.tw-cursor { position: absolute; left: 66px; top: 50px; width: 16px; height: 16px; animation: tw-cursor 400ms var(--tw-ease) 0ms 1 backwards; }
.tw-cursor::before { content: ""; position: absolute; inset: 0; background: var(--ink, #172033); clip-path: polygon(0 0, 0 100%, 30% 72%, 52% 100%, 66% 92%, 45% 64%, 85% 64%); }
@keyframes tw-cursor { from { opacity: 0; transform: translate(120px, 110px); } }
@keyframes tw-land { from { opacity: 0; transform: translateY(52px); } }
@keyframes tw-press { 0%, 100% { transform: scale(1); } 50% { transform: scale(.92); } }
@keyframes tw-pick { from { opacity: 0; } }
@keyframes tw-flow { from { opacity: 0; transform: translate(-6px, -50%); } }
@keyframes tw-got { from { opacity: 0; transform: translateX(-24px); } }

/* ---------- 2. grade: a task list that grows + the target-grade slider (about 1.4 s) ---------- */
.tw-tasks { position: absolute; left: var(--s3, 12px); right: var(--s3, 12px); top: var(--s3, 12px); display: grid; grid-template-columns: 1fr 1fr; gap: 4px var(--s2, 8px); }
.tw-task { position: relative; height: 24px; border-radius: var(--r-s, 8px); border: 1px dashed var(--line, #dfe4ec); background: var(--surface, var(--card, #fff)); }
.tw-taskIn { position: absolute; inset: 2px; display: flex; align-items: center; gap: 6px; padding: 0 6px; border-radius: var(--r-s, 8px); background: color-mix(in srgb, var(--accent, #1f5eff) 12%, var(--surface, var(--card, #fff))); }
.tw-tick { flex: none; width: 14px; height: 14px; border-radius: var(--r-pill, 999px); background: var(--ok, #1a7f4b); color: #fff; font-size: var(--fs-xs, 12px); line-height: 14px; text-align: center; }
.tw-bar { display: block; height: 6px; border-radius: var(--r-pill, 999px); background: color-mix(in srgb, var(--accent, #1f5eff) 45%, var(--line, #dfe4ec)); }
.tw-w78 { width: 78%; } .tw-w64 { width: 64%; } .tw-w86 { width: 86%; } .tw-w58 { width: 58%; } .tw-w72 { width: 72%; } .tw-w80 { width: 80%; } .tw-w52 { width: 52%; } .tw-w68 { width: 68%; } .tw-w74 { width: 74%; }
.tw-step2 { animation: tw-grow 300ms var(--tw-ease) 500ms 1 backwards; } .tw-step3 { animation: tw-grow 300ms var(--tw-ease) 900ms 1 backwards; }
@keyframes tw-grow { from { opacity: 0; transform: scale(.4); } }
.tw-slider { position: absolute; left: var(--s5, 20px); right: var(--s5, 20px); bottom: var(--s3, 12px); }
.tw-sliderHead { display: flex; justify-content: space-between; align-items: baseline; font-size: var(--fs-sm, 14px); color: var(--muted, #5b6675); font-weight: 600; }
.tw-val { position: relative; display: inline-block; width: 1.4em; height: 1.3em; font-size: var(--fs-lg, 20px); color: var(--accent, #1f5eff); text-align: right; }
.tw-v { position: absolute; right: 0; top: 0; opacity: 0; }
.tw-v2 { opacity: 1; animation: tw-v2 200ms var(--tw-ease) 1000ms 1 backwards; }
.tw-v0 { animation: tw-v0 500ms linear 0ms 1 backwards; } .tw-v1 { animation: tw-v1 1000ms linear 0ms 1 backwards; }
@keyframes tw-v0 { 0%, 80% { opacity: 1; } 100% { opacity: 0; } }
@keyframes tw-v1 { 0%, 45% { opacity: 0; } 50%, 90% { opacity: 1; } 100% { opacity: 0; } }
@keyframes tw-v2 { from { opacity: 0; } }
.tw-track { position: relative; height: 8px; margin-top: 6px; border-radius: var(--r-pill, 999px); background: var(--line, #dfe4ec); container-type: inline-size; }
.tw-fill { position: absolute; inset: 0; border-radius: var(--r-pill, 999px); background: var(--accent, #1f5eff); transform-origin: left; transform: scaleX(.889); animation: tw-fill 1400ms var(--tw-ease) 0ms 1 backwards; }
.tw-thumb { position: absolute; top: 50%; left: 0; width: 20px; height: 20px; margin: -10px 0 0 -10px; border-radius: 50%; background: var(--surface, var(--card, #fff)); border: 3px solid var(--accent, #1f5eff); box-shadow: var(--e1, 0 1px 2px rgb(16 24 40 / .06)); transform: translateX(88.9cqw); animation: tw-thumb 1400ms var(--tw-ease) 0ms 1 backwards; }
@keyframes tw-fill { 0%, 30% { transform: scaleX(.444); } 45%, 60% { transform: scaleX(.667); } 100% { transform: scaleX(.889); } }
@keyframes tw-thumb { 0%, 30% { transform: translateX(44.4cqw); } 45%, 60% { transform: translateX(66.7cqw); } 100% { transform: translateX(88.9cqw); } }
.tw-ends { display: flex; justify-content: space-between; font-size: var(--fs-xs, 12px); color: var(--muted, #5b6675); margin-top: var(--s1, 4px); }

/* ---------- 3. learn: an equation step by step, and the other subjects (about 1.6 s) ---------- */
.tw-eq { position: absolute; left: 50%; top: var(--s4, 16px); transform: translateX(-50%); width: min(260px, calc(100% - 28px)); background: var(--surface, var(--card, #fff)); border: 1px solid var(--line, #dfe4ec); border-radius: var(--r-m, 16px); padding: var(--s2, 8px) var(--s4, 16px); box-shadow: var(--e1, 0 1px 2px rgb(16 24 40 / .06)); font: 600 var(--fs-md, 16px)/1.6 "Cambria Math", "STIX Two Math", Georgia, serif; }
.tw-line { white-space: nowrap; }
.tw-l1 { color: var(--muted, #5b6675); animation: tw-line 300ms var(--tw-ease) 200ms 1 backwards; }
.tw-l2 { color: var(--accent, #1f5eff); animation: tw-line 300ms var(--tw-ease) 600ms 1 backwards; }
.tw-ok { display: inline-block; color: var(--ok, #1a7f4b); font-family: system-ui, sans-serif; animation: tw-ok 300ms var(--tw-ease) 1000ms 1 backwards; }
@keyframes tw-line { from { opacity: 0; transform: translateY(8px); } }
@keyframes tw-ok { from { transform: scale(0); } }
.tw-chips { position: absolute; left: var(--s2, 8px); right: var(--s2, 8px); bottom: var(--s3, 12px); display: flex; flex-wrap: wrap; justify-content: center; gap: 6px; }
.tw-chip { display: inline-flex; align-items: center; font-size: var(--fs-xs, 12px); font-weight: 600; padding: 3px var(--s3, 12px); border-radius: var(--r-pill, 999px); background: var(--surface, var(--card, #fff)); border: 1px solid var(--line, #dfe4ec); animation: tw-chip 300ms var(--tw-ease) 800ms 1 backwards; }
.tw-chip0 { border-color: var(--accent, #1f5eff); color: var(--accent, #1f5eff); }
.tw-chip1 { animation-delay: 900ms; } .tw-chip2 { animation-delay: 1000ms; } .tw-chip3 { animation-delay: 1100ms; } .tw-chip4 { animation-delay: 1200ms; } .tw-chip5 { animation-delay: 1300ms; }
@keyframes tw-chip { from { opacity: 0; transform: translateY(10px); } }

@keyframes tw-fade { from { opacity: 0; } }
@keyframes tw-fade-out { to { opacity: 0; } }
@keyframes tw-enter { from { opacity: 0; } }

@media (max-width: 420px) {
  .tw-box { padding: var(--s4, 16px); }
  .tw-stage { height: 204px; }
  .tw-win { width: 50%; left: 10px; bottom: 10px; }
  .tw-flow { left: calc(50% + 12px); }
  .tw-app { right: 10px; width: calc(50% - 44px); min-width: 108px; bottom: auto; }   /* its height is its own rows: no big empty frame while they arrive (L04-A3) */
  .tw-task { height: 22px; }
}
@media (max-width: 479px) {
  .tw-stage { display: none; }   /* a narrow screen keeps the three lines and the button; the picture is only decoration (the lead's cut, loop L06-8) */
}
@media (prefers-reduced-motion: reduce) {
  .tw-back, .tw-box, .tw-pic *, .tw-pic *::after, .tw-content { animation: none !important; transition: none !important; }
  .tw-dot { transition: none !important; }
  .tw-cursor { display: none; }
}
/* one focus ring for the welcome controls */
.tw-next:focus-visible, .tw-skip:focus-visible { outline: 2px solid var(--accent, #1f5eff); outline-offset: 2px; }
.tw-pointer .tw-next:focus-visible, .tw-pointer .tw-skip:focus-visible { outline: none; }

/* ---------- the button page (bookmark.html and the app's button tab): facts, and a small picture of the drag (bookmark.js; round 1) ----------
   The picture plays three times (about 9 s) and rests on the finished state, also under reduced motion. Decorative: aria-hidden. */
.bm-facts { margin: var(--s2, 8px) 0; color: var(--ink, #172033); font-weight: 600; }
.bm-demo { position: relative; box-sizing: border-box; height: 84px; max-width: 320px; margin: var(--s3, 12px) 0; border: 1px solid var(--line, #dfe4ec); border-radius: var(--r-s, 8px); background: var(--surface, var(--card, #fff)); overflow: hidden; }
.bm-demo-bar { display: flex; gap: var(--s1, 4px); align-items: center; height: 28px; padding: 0 var(--s2, 8px); border-bottom: 1px solid var(--line, #dfe4ec); background: color-mix(in srgb, var(--line, #dfe4ec) 30%, var(--surface, var(--card, #fff))); }
.bm-demo-bar i { width: 26px; height: 8px; border-radius: var(--r-pill, 999px); background: var(--line, #dfe4ec); }
.bm-demo-slot { width: 76px; height: 20px; }
.bm-demo-page { display: grid; gap: var(--s2, 8px); padding: var(--s3, 12px) var(--s2, 8px); }
.bm-demo-page i { height: 8px; width: 70%; border-radius: var(--r-pill, 999px); background: var(--line, #dfe4ec); }
.bm-demo-chip { position: absolute; left: calc(var(--s2, 8px) + 2 * 26px + 2 * var(--s1, 4px)); top: 4px; width: 76px; height: 20px; box-sizing: border-box; border-radius: var(--r-s, 8px); background: var(--accent, #1f5eff); color: var(--on-accent, var(--accent-ink, #fff)); font-size: var(--fs-xs, 12px); font-weight: 600; line-height: 20px; text-align: center; animation: bm-climb 3s var(--ease-out, cubic-bezier(.05, .7, .1, 1)) 0s 3 backwards; }
@keyframes bm-climb { 0%, 20% { opacity: 1; transform: translate(70px, 46px); } 65%, 100% { opacity: 1; transform: translate(0, 0); } }
@media (prefers-reduced-motion: reduce) { .bm-demo-chip { animation: none; } }

/* ask.css */
/* ask.css - the "Ask my material" panel (styles for ask.js only): a one-line question bar, the answer in a card, the sources as one-line chips. */
#askPanel [hidden] { display: none !important; }   /* the hidden attribute wins over the display rules below (the page rule of app.css is not always loaded, e.g. in tests) */
#askPanel { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow); padding: 14px 16px; margin-top: 16px; }
#askPanel h2 { margin-top: 0; }
#askPanel h3 { font-size: 1rem; margin: var(--s3, 12px) 0 var(--s1, 4px); }
#askPanel .askIntro { margin: 0 0 var(--s2, 8px); }
#askPanel .askMsg { overflow-wrap: anywhere; margin: 0 0 var(--s2, 8px); }
#askPanel .askAct { min-height: var(--tap, 44px); margin-bottom: var(--s2, 8px); }
/* the bar: the field takes the width, the button sits on its right; the label is read by screen readers only (the placeholder shows the same words) */
#askPanel .askForm { display: flex; align-items: flex-start; gap: var(--s2, 8px); }
#askPanel .askLabel { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0; }
#askPanel .askBox { flex: 1 1 auto; min-width: 0; box-sizing: border-box; margin: 0; font: inherit; resize: none; }
#askPanel .askGo { flex: 0 0 auto; min-height: var(--tap, 44px); }
#askPanel .askGo[disabled] { opacity: 1; cursor: progress; background: var(--accent); color: var(--on-accent, #fff); }
#askPanel .askLeft { margin: var(--s1, 4px) 0 0; }
/* the answer card: calm tinted block; "not in your material" is a normal answer, so it is a quiet card, not an error */
#askPanel .askOut { margin-top: var(--s3, 12px); padding: var(--s3, 12px) var(--s4, 16px); border-radius: var(--r-s, 8px); border: 1px solid var(--line); background: var(--accent-soft, var(--card)); }
#askPanel .askOut.askNoneCard { background: var(--surface, var(--card)); }
#askPanel .askAnswer { margin: 0; overflow-wrap: anywhere; white-space: pre-line; }
#askPanel .askAnswer.askNone { color: var(--muted, #5b6472); }
#askPanel .askHint { margin: var(--s1, 4px) 0 0; }
/* sources: one chip per file, one line each (a long name is cut with an ellipsis, the full name is the tooltip) */
#askPanel .askSources { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; align-items: flex-start; gap: var(--s1, 4px); }
#askPanel .askSource { max-width: 100%; }
#askPanel .askOpen, #askPanel .askChip { display: block; max-width: 100%; box-sizing: border-box; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: start; font: inherit; font-size: var(--fs-sm, .875rem); border: 1px solid var(--line); border-radius: var(--r-pill, 999px); background: var(--surface, var(--card)); color: var(--ink); padding: var(--s1, 4px) var(--s3, 12px); }
#askPanel .askOpen { min-height: var(--tap, 44px); cursor: pointer; }
#askPanel .askPrivacy { margin-bottom: 0; }
#askPanel[data-state="limit"] .askMsg, #askPanel[data-state="error"] .askMsg { color: var(--ink); }
#askPanel[data-state="limit"] .askMsg { padding: var(--s2, 8px) var(--s3, 12px); border-radius: var(--r-s, 8px); background: var(--warn-soft, transparent); }

/* mathsteps.css */
/* mathsteps.css - the "Maths steps from a photo" panel (mathsteps.js). No inline styles: everything is a class here. */
#mathPanel [hidden] { display: none !important; }   /* the hidden attribute wins over the display rules below (the page rule of app.css is not always loaded, e.g. in tests) */
/* the compact row that opens the panel in place */
#mathPanel .mathRow { display: flex; align-items: center; justify-content: space-between; gap: var(--s3, 12px); width: 100%; min-height: var(--tap, 44px); text-align: start; }
#mathPanel .mathRow::after { content: "\25BE" / ""; transition: transform var(--d-fast, 120ms); }
#mathPanel .mathRow[aria-expanded="true"]::after { transform: rotate(180deg); }
#mathPanel .mathSheet { margin-top: var(--s3, 12px); }
#mathPanel .mathSheet h2 { margin-top: 0; }
#mathPanel .mathBusy { align-items: center; }
#mathPanel .mathBusy .mathStatus { margin: 0; }
/* the live maths line under the task box */
#mathPanel .mathPreview { margin: var(--s2, 8px) 0; padding: var(--s2, 8px) var(--s3, 12px); border: 1px solid var(--line); border-radius: var(--r-s, 8px); background: var(--accent-soft, var(--card)); }
#mathPanel .mathPreviewLabel { margin: 0 0 var(--s1, 4px); }
#mathPanel .mathPreviewBody { overflow-wrap: anywhere; white-space: pre-wrap; }
#mathPanel .mathPreviewBody .mathMath { display: inline-block; vertical-align: middle; overflow-x: auto; max-width: 100%; }
#mathPanel .mathLabel { display: block; margin-top: 12px; font-weight: 600; }
#mathPanel .mathTask { display: block; width: 100%; box-sizing: border-box; min-height: 6em; margin: 6px 0; font: inherit; resize: vertical; }
#mathPanel .mathError { color: var(--bad); }
#mathPanel .mathNotMath { margin: 10px 0; }
#mathPanel .mathSteps { margin: 8px 0; padding-left: 22px; }
#mathPanel .mathStep { margin: 0 0 10px; }
#mathPanel .mathStepText { margin: 0 0 4px; }
#mathPanel .mathMath { overflow-x: auto; max-width: 100%; }
#mathPanel .mathStep .mathMath { padding: 6px 10px; border: 1px solid var(--line); border-radius: 10px; background: var(--card); }
#mathPanel .mathMath.mathRaw { font-family: ui-monospace, Consolas, monospace; white-space: pre-wrap; overflow-wrap: anywhere; }
#mathPanel .mathAnswer { margin: 12px 0 6px; font-size: 1.1em; }
#mathPanel .mathAnswer .mathMath { display: inline-block; vertical-align: middle; }
#mathPanel .mathCheck { margin: 4px 0; color: var(--muted, inherit); }
#mathPanel .mathCheck.mathWarn { color: var(--warn, #9a5b00); font-weight: 600; }

