/* GENERATED FILE — do not edit.
   Built from styles/*.css by scripts/build/css.js (concatenated in filename
   order). Edit the partial that owns the section, then re-run the build. */
/* ==========================================================================
   Nihonpo — design system
   Token-based, light/dark via [data-theme]. Mobile-first.

   Warm, quiet and typographic. Backgrounds are sand, not grey; text is a deep
   plum-black, not neutral black; display headings are a serif at weight 400.
   Chrome is almost colourless so the only real colour on a page is the thing
   carrying meaning: a JLPT level, a study state, or the single loudest CTA.

   This is the ONLY file allowed to define custom properties on :root, and the
   only one allowed to hold a literal hex. See DESIGN.md.
   ========================================================================== */

/* ---- Webfont ------------------------------------------------------------- */
/* Instrument Serif is the whole identity, and the only webfont the site loads.
   It ships ONE weight, 400, which is the point: it makes "headings are never
   bold" structurally true rather than a rule people forget. Latin subset only,
   self-hosted from static/fonts/ so the budget stays a single 15 KB woff2 with
   no third-party origin. Preloaded in lib/layout.js. */
@font-face {
  font-family: 'Instrument Serif';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/instrument-serif-400.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ---- Design tokens ------------------------------------------------------- */
:root {
  /* Accent roles. Three, and they are not interchangeable:
     --accent        a FILL, never text (in light it equals --text)
     --accent-strong the only coloured text in the chrome, and the focus ring
     --cta           the one loudest action on a page, fill only, one per page */
  --accent: #28030f;
  --accent-contrast: #f9f4f1;
  --accent-strong: #1343aa;
  --accent-soft: #f6ece4;
  --cta: #ebdf00;
  --cta-contrast: #28030f;

  /* Planes, back to front: bg → bg-sunken → surface → bg-elev.
     They sit only 1.1:1 apart; the hairline is what actually draws an edge. */
  --bg: #f6efea;
  --bg-elev: #fcfaf8;
  --bg-sunken: #f0dfd1;
  --surface: #ffffff;
  --surface-2: #f6ece4;

  --text: #28030f;
  --text-soft: #6b4a54;
  --text-faint: #806069;
  --border: #f0dfd1;
  --border-strong: #e2cab6;

  /* Exam scans are photographed on white paper, so their frame keeps a light
     ground in both themes rather than butting a bright rectangle against soot. */
  --paper: #ffffff;

  /* Warm-tinted modal scrim. One value for both themes: a plum scrim looked
     right over sand and turned the soot ground purple. */
  --scrim: rgba(26, 23, 23, .5);

  --display: 'Instrument Serif', 'Iowan Old Style', Georgia, 'Times New Roman', serif;
  --sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  --ja: 'Hiragino Kaku Gothic ProN', 'Hiragino Sans', 'Noto Sans JP', 'Yu Gothic', Meiryo, sans-serif;
  --ja-display: 'Noto Serif JP', 'Hiragino Mincho ProN', 'Yu Mincho', serif;

  /* Level palette. Every value clears 4.5:1 on all three light planes and all
     three dark planes, so these are safe as body-sized text as well as fills. */
  --n5: #2b6433; --n4: #2255c3; --n3: #6d4bc4; --n2: #b45518; --n1: #b3242e;

  /* Study-state palette — the single source for the level progress bars,
     their legends, and list/grid card state colours. */
  --st-done: #1a7a44; --st-scheduled: var(--n3); --st-learning: var(--n4);
  --st-due: var(--n2); --st-viewed: var(--text-faint);

  /* Shadows are nearly absent by design. Depth comes from the plane change
     plus the hairline; sm and md are almost invisible on purpose. */
  --shadow-sm: 0 1px 2px rgba(40, 3, 15, .04);
  --shadow-md: 0 2px 6px rgba(40, 3, 15, .06);
  --shadow-lg: 0 16px 48px rgba(40, 3, 15, .12);

  /* 999px (pills) and 50% (circles) stay untokenised: they are shape
     declarations, not scale steps. Buttons are --radius, not pills. */
  --radius-xs: 6px;
  --radius-sm: 10px;
  --radius: 16px;
  --radius-lg: 24px;
  --radius-xl: 36px;

  --shell: 1080px;
  --step: clamp(.94rem, 2.4vw, 1.02rem);
  --header-h: 56px;
}

/* Dark is deliberately NOT a plum inversion of light. Soot carries barely
   enough hue to stay off blue, which leaves the five level hues as the only
   real colour on a night page. Do not reintroduce a tinted dark ground. */
[data-theme="dark"] {
  --accent: #ece5e2;
  --accent-contrast: #1a1717;
  --accent-strong: #b9cfff;
  --accent-soft: #2a2626;
  /* --cta does not flip: sunlight yellow reads the same on sand and on soot. */

  --bg: #141212;
  --bg-elev: #232020;
  --bg-sunken: #0d0c0c;
  --surface: #1f1c1c;
  --surface-2: #2a2626;

  --text: #f6f2f0;
  --text-soft: #cfc7c4;
  --text-faint: #a09794;
  --border: #302b2b;
  --border-strong: #443e3e;

  --n5: #7cac83; --n4: #7aa4ff; --n3: #a79aef; --n2: #e8954f; --n1: #ff8a90;
  --st-done: #7cd6a0;

  /* A resting card has no shadow here at all — its border is its only edge. */
  --shadow-sm: none;
  --shadow-md: 0 2px 8px rgba(0, 0, 0, .45);
  --shadow-lg: 0 20px 56px rgba(0, 0, 0, .65);
}
/* ---- Reset / base -------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
  margin: 0;
  font-family: var(--sans);
  font-size: var(--step);
  line-height: 1.65;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  
}

/* Display headings: the serif at its only weight, set large and tight. There
   is deliberately no font-weight declaration — the family ships 400 alone, so
   omitting it is what keeps "headings are never bold" true. Serif never goes
   below 1.3rem; anything smaller is sans at 600 (h4 and below). */
h1, h2, h3 { font-family: var(--display); font-weight: 400; margin-block: 0; }
h1 { font-size: clamp(2.1rem, 5.5vw, 3.25rem); line-height: 1.02; letter-spacing: -.03em; margin-bottom: .3em; }
h2 { font-size: clamp(1.7rem, 4vw, 2.5rem); line-height: 1.06; letter-spacing: -.03em; margin: 1.4em 0 .4em; }
h3 { font-size: clamp(1.3rem, 2.6vw, 1.6rem); line-height: 1.15; letter-spacing: -.02em; margin: 1.2em 0 .35em; }
h4, h5, h6 { font-family: var(--sans); font-size: 1.02rem; font-weight: 600; line-height: 1.3; letter-spacing: -.01em; margin: 1.2em 0 .35em; }

p { margin: 0 0 1em; }
a { color: var(--accent-strong); text-decoration: none; }
a:hover { text-decoration: underline; }
img { max-width: 100%; display: block; }
button { font-family: inherit; cursor: pointer; }
ul, ol { margin: 0; padding: 0; }
li { list-style: none; }

/* Japanese picks up the gothic stack from the lang attribute alone — do not
   set --ja by hand where lang="ja" already applies. In a display heading it
   switches to mincho instead, so a mixed heading is not half-serif, half-sans. */
:lang(ja) { font-family: var(--ja); }
h1:lang(ja), h2:lang(ja), h3:lang(ja),
h1 :lang(ja), h2 :lang(ja), h3 :lang(ja) { font-family: var(--ja-display); }

/* Global, and not to be overridden per component. The ring is --accent-strong
   (sky), never --accent: in light mode --accent is the same plum as the body
   text, so a ring drawn in it would be invisible against dark UI. */
:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: 2px; border-radius: 4px; }


/* Numeric runs that update in place must not jitter. */
.tabular { font-variant-numeric: tabular-nums; }

.shell { width: 100%; max-width: var(--shell); margin-inline: auto; padding-inline: clamp(20px, 5vw, 40px); }
.visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.skip-link { position: absolute; left: -999px; top: 8px; z-index: 100; background: var(--accent); color: var(--accent-contrast); padding: 8px 14px; border-radius: var(--radius-sm); }
.skip-link:focus { left: 8px; }

main.shell { padding-top: 40px; padding-bottom: 88px; }
/* ---- Header -------------------------------------------------------------- */
.site-header {
  position: sticky; top: 0; z-index: 40;
  background: color-mix(in srgb, var(--bg-elev) 86%, transparent);
  /* Saturation boost is deliberately low: a warm ground turns orange when you
     push it. */
  backdrop-filter: saturate(1.2) blur(14px);
  border-bottom: 1px solid var(--border);
}
/* The header runs wider than page content (--shell) so the nav row plus the
   action cluster stay on one line. Body copy keeps the narrower measure. */
.site-header .shell { max-width: 1240px; }
.header-inner { display: flex; align-items: center; gap: 12px; min-height: var(--header-h); }
/* The wordmark carries the identity, so it is set in the identity face. Weight
   700+ appears nowhere in this system except Japanese display glyphs. */
.brand { display: inline-flex; align-items: center; gap: 8px; font-family: var(--display); font-weight: 400; color: var(--text); letter-spacing: -.02em; font-size: 1.32rem; white-space: nowrap; }
.brand:hover { text-decoration: none; }
.brand-logo { border-radius: var(--radius-xs); }
.main-nav { margin-inline-start: auto; }
.nav-list { display: flex; flex-wrap: wrap; gap: 2px; }
.nav-item { position: relative; }
/* Sized so all nine items clear the brand and the action cluster on one line
   at the header's full 1240px width — see 16-responsive-chrome.css. */
.nav-top {
  display: inline-flex; align-items: center; gap: 3px; padding: 7px;
  border: 0; background: transparent; border-radius: var(--radius-sm);
  color: var(--text-soft); font-weight: 500; font-size: .88rem; white-space: nowrap;
  /* Set, not inherited: the menus are <button>s and Home is an <a>, which
     would otherwise take the body's 1.65 and sit lower than the rest. */
  line-height: 1.2;
  transition: background-color .18s cubic-bezier(.4, 0, .2, 1), color .18s cubic-bezier(.4, 0, .2, 1);
}
.nav-top:hover { background: var(--surface-2); color: var(--text); text-decoration: none; }
.nav-top[aria-current="page"], .nav-top.is-active { color: var(--text); background: var(--accent-soft); font-weight: 600; }
.nav-caret { font-size: .58em; opacity: .65; }
.nav-dropdown {
  position: absolute; top: calc(100% + 6px); left: 0; min-width: 184px; z-index: 50;
  background: var(--bg-elev); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-lg); padding: 6px; display: none; flex-direction: column; gap: 1px;
}
/* Invisible bridge across the 6px gap so hover survives moving from button to dropdown. */
.nav-dropdown::before { content: ""; position: absolute; left: 0; right: 0; top: -6px; height: 6px; }
.nav-item.has-dropdown:hover > .nav-dropdown,
.nav-item.has-dropdown:focus-within > .nav-dropdown,
.nav-item.has-dropdown.is-open > .nav-dropdown { display: flex; }
.nav-dropdown a {
  padding: 8px 12px; border-radius: var(--radius-sm); color: var(--text-soft);
  font-size: .88rem; font-weight: 500; white-space: nowrap;
  transition: background-color .18s cubic-bezier(.4, 0, .2, 1), color .18s cubic-bezier(.4, 0, .2, 1);
}
.nav-dropdown a:hover { background: var(--surface-2); color: var(--text); text-decoration: none; }
.nav-dropdown a.is-current { color: var(--text); background: var(--accent-soft); font-weight: 600; }
/* A second group inside one menu (Guide under Topics). */
.nav-dropdown-h {
  margin: 5px 6px 0; padding: 9px 6px 4px; border-top: 1px solid var(--border);
  font-size: .72rem; font-weight: 600; letter-spacing: .05em; text-transform: uppercase;
  color: var(--text-soft);
}
/* The one nav item that is a destination, not a menu. Accent colour is the only
   thing setting it apart: same padding, same size, same weight as the rest. */
.nav-cta-link.nav-top,
.nav-cta-link.nav-top[aria-current="page"] {
  background: transparent; color: var(--accent); font-weight: 500; box-shadow: none;
}
.nav-cta-link.nav-top:hover { background: var(--surface-2); color: var(--accent); text-decoration: none; }
/* The menu's Search row is for phones; the header button does the job here. */
.nav-search { display: none; }
.header-actions { display: flex; align-items: center; gap: 6px; }
/* Compact circular icon buttons — the whole header action cluster (search,
   progress, theme, account). Labels live in aria-label, not on screen. */
.icon-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; padding: 0; flex: 0 0 auto; line-height: 0;
  border: 1px solid var(--border-strong); border-radius: 50%;
  background: var(--surface); color: var(--text-soft); cursor: pointer;
  transition: background-color .18s cubic-bezier(.4, 0, .2, 1), border-color .18s cubic-bezier(.4, 0, .2, 1), color .18s cubic-bezier(.4, 0, .2, 1);
}
.icon-btn:hover { border-color: var(--accent); color: var(--text); text-decoration: none; }
.icon-btn > span:not(.account-dot) { display: none; } /* hide text labels */
.nav-menu-toggle { display: none; }
.nav-menu-icon,
.nav-menu-icon::before,
.nav-menu-icon::after {
  width: 17px; height: 2px; border-radius: 2px; background: currentColor;
  transition: transform .18s cubic-bezier(.4, 0, .2, 1), opacity .18s cubic-bezier(.4, 0, .2, 1);
}
.nav-menu-icon { position: relative; }
.nav-menu-icon::before,
.nav-menu-icon::after { content: ""; position: absolute; left: 0; }
.nav-menu-icon::before { transform: translateY(-5px); }
.nav-menu-icon::after { transform: translateY(5px); }
.nav-menu-toggle[aria-expanded="true"] .nav-menu-icon { background: transparent; }
.nav-menu-toggle[aria-expanded="true"] .nav-menu-icon::before { transform: rotate(45deg); }
.nav-menu-toggle[aria-expanded="true"] .nav-menu-icon::after { transform: rotate(-45deg); }
[data-theme="dark"] .nav-menu-toggle { color: var(--text); }
/* Ko-fi is the one action that keeps its label — an unlabelled cup icon reads
   as nothing. Tinted so it sits apart from the utility icons beside it, which
   also means it is no longer a circle. */
.kofi-btn { width: auto; gap: 7px; padding-inline: 14px; border-radius: var(--radius-sm); font-size: .88rem; font-weight: 500; background: var(--accent-soft); border-color: transparent; color: var(--accent-strong); }
/* :not() mirrors the rule above so this wins on source order, not weight. */
.kofi-btn > span:not(.account-dot) { display: inline; line-height: 1; }
.kofi-btn:hover { border-color: var(--accent); color: var(--accent-strong); }

/* Account button + dropdown (driven by sync.js) */
.header-account-wrap { position: relative; }
.account-dot { position: absolute; top: 5px; right: 5px; width: 8px; height: 8px; border-radius: 50%; background: var(--st-done); border: 2px solid var(--bg); }
.account-menu {
  position: absolute; top: calc(100% + 8px); right: 0; z-index: 60;
  width: 240px; max-width: calc(100vw - 24px);
  background: var(--bg-elev); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-lg); padding: 12px; display: flex; flex-direction: column; gap: 8px;
}
.account-menu[hidden] { display: none; }
.account-email { font-weight: 600; font-size: .88rem; color: var(--text); word-break: break-word; }
.account-sync { display: inline-flex; align-items: center; gap: 7px; font-size: .82rem; font-weight: 500; color: var(--text-soft); }
.account-note { margin: 0; font-size: .82rem; color: var(--text-soft); line-height: 1.45; }
.account-link {
  display: block; width: 100%; text-align: left;
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--text-soft);
  padding: 9px 12px; font-weight: 500; font-size: .82rem; cursor: pointer;
  transition: background-color .18s cubic-bezier(.4, 0, .2, 1), border-color .18s cubic-bezier(.4, 0, .2, 1), color .18s cubic-bezier(.4, 0, .2, 1);
}
.account-link:hover { background: var(--accent-soft); border-color: var(--accent); color: var(--text); text-decoration: none; }
.account-primary { background: var(--accent); border-color: transparent; color: var(--accent-contrast); }
.account-primary:hover { background: color-mix(in srgb, var(--accent) 86%, var(--surface)); border-color: transparent; color: var(--accent-contrast); }

/* ── Phones: the header stays where it was put ─────────────
   Two rows deep, the brand and icons over nine nav items, is a fifth of a
   phone screen. Held to the top it is a fifth of every screen for the whole
   of a study day, so on a phone it scrolls away with the page and comes back
   at the top of it. What pins itself under the header comes with it: the
   listening player measures the header before it pins (initListenPlayer) and
   the study day's bar goes to the top of the viewport (53-study-plan.css),
   so nothing sits under a header that is no longer there. */
@media (max-width: 720px) {
  .site-header { position: static; }
}
/* ---- Breadcrumbs --------------------------------------------------------- */
.breadcrumbs { border-bottom: 1px solid var(--border); background: var(--bg); }
.breadcrumbs-inner { display: flex; align-items: center; gap: 12px; }
.breadcrumbs ol { display: flex; flex-wrap: wrap; gap: 6px; padding-block: 12px; font-size: .82rem; font-weight: 500; color: var(--text-faint); flex: 1 1 auto; min-width: 0; }
.breadcrumbs li { display: flex; align-items: center; gap: 6px; }
.breadcrumbs li:not(:last-child)::after { content: "›"; color: var(--text-faint); }
.breadcrumbs a { color: var(--text-soft); }
.breadcrumbs li[aria-current] { color: var(--text); font-weight: 600; }

/* ---- Page head / prose --------------------------------------------------- */
.page-head { margin-bottom: 24px; }
.eyebrow { text-transform: uppercase; letter-spacing: .08em; font-size: .72rem; font-weight: 600; color: var(--text-faint); margin: 0 0 .6em; }
/* Serif at display sizes needs a shorter measure than sans does, which is why
   the lede caps tighter than the prose column below it. */
.lede { font-size: clamp(1.02rem, 2.4vw, 1.18rem); line-height: 1.55; color: var(--text-soft); max-width: 62ch; }
.block-head { margin-bottom: 20px; }
.block-head h2 { margin-top: 0; }
.block-head p { color: var(--text-soft); margin: 0; }
/* Nav pill: --radius-sm, not 999px. A pill now means status or toggle. */
.hub-links { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 20px; }
.hub-links a {
  padding: 6px 14px; border-radius: var(--radius-sm); background: var(--surface-2);
  border: 1px solid var(--border); color: var(--text-soft); font-size: .88rem; font-weight: 500;
  transition: background-color .18s cubic-bezier(.4, 0, .2, 1), border-color .18s cubic-bezier(.4, 0, .2, 1), color .18s cubic-bezier(.4, 0, .2, 1);
}
.hub-links a:hover { border-color: var(--border-strong); color: var(--text); text-decoration: none; }
/* Same pill at the foot of a page, sat on the rule the section above ended on. */
.hub-links-foot { margin-top: 40px; padding-top: 24px; border-top: 1px solid var(--border); }

.prose { max-width: 72ch; }
.prose section { margin-top: 40px; }
.prose h2 { margin: 0 0 .4em; }
.prose h3 { margin: 1.2em 0 .35em; }
.prose p, .prose li { color: var(--text-soft); line-height: 1.65; }
.prose ul { padding-inline-start: 1.2em; }
.prose li { margin-bottom: .4em; }
/* Underline is always present in prose, never only on hover: at this contrast
   level colour alone is not enough of a signal. */
.prose a { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: .18em; }
.prose-updated { font-size: .82rem; font-weight: 500; color: var(--text-faint); margin-top: 12px; }

/* Learning guidance reuses the FAQ disclosure pattern used on the content
   hubs, so a long list ends with optional help instead of a second prose page. */
.learning-guide .faq-item ol, .learning-guide .faq-item ul { margin: 10px 0 0; padding-inline-start: 1.25rem; }
.learning-guide .faq-item li { color: var(--text-soft); font-size: var(--step); line-height: 1.6; }
.learning-guide .faq-item li + li { margin-top: 6px; }
.learning-guide .faq-item a { text-decoration: underline; text-underline-offset: .16em; }
.learning-guide .faq-item .learning-guide-source { color: var(--text-faint); font-size: .82rem; }

/* ---- Account row under a section index ----------------------------------- */
.hp-sync { margin-top: 16px; margin-bottom: 24px; font-size: .88rem; }
.hp-sync .sync-row { border-radius: var(--radius); padding: 12px 16px; background: var(--surface-2); border: 1px solid var(--border); }

/* ---- Buttons ------------------------------------------------------------- */
/* Rectangles with a 16px radius. Three fills, and the hierarchy between them
   is the whole point: --cta once per page at most, then --accent, then ghost. */
.btn-cta, .btn-primary, .btn-ghost {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 12px 22px; border-radius: var(--radius);
  font-weight: 500; font-size: var(--step); border: 1px solid transparent;
  transition: background-color .3s cubic-bezier(.4, 0, .2, 1), border-color .3s cubic-bezier(.4, 0, .2, 1), color .3s cubic-bezier(.4, 0, .2, 1);
}
.btn-cta { background: var(--cta); color: var(--cta-contrast); box-shadow: var(--shadow-sm); }
.btn-cta:hover { background: color-mix(in srgb, var(--cta) 88%, #000); text-decoration: none; }
.btn-primary { background: var(--accent); color: var(--accent-contrast); box-shadow: var(--shadow-sm); }
.btn-primary:hover { background: color-mix(in srgb, var(--accent) 86%, var(--surface)); text-decoration: none; }
.btn-ghost { background: transparent; border-color: var(--border-strong); color: var(--text); }
.btn-ghost:hover { background: var(--accent-soft); border-color: var(--accent); text-decoration: none; }
/* The fourth thing a button can be: an icon, or a word that has to be there
   without asking to be pressed. No fill and no border until you are on it. */
.btn-quiet {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 12px; border: 1px solid transparent; border-radius: var(--radius-sm);
  background: none; color: var(--text-soft); font: inherit; font-size: .82rem; font-weight: 500;
  transition: background-color .18s cubic-bezier(.4, 0, .2, 1), color .18s cubic-bezier(.4, 0, .2, 1);
}
.btn-quiet:hover:not(:disabled) { background: var(--surface-2); color: var(--accent-strong); text-decoration: none; }
.btn-quiet:disabled { opacity: .3; cursor: default; }

/* ---- Hero ---------------------------------------------------------------- */
.hero { padding: 56px 0 16px; }
.hero h1 { max-width: 16ch; font-size: clamp(2.6rem, 7vw, 4.25rem); line-height: 1; letter-spacing: -.035em; }
.hero .lede { margin-bottom: 28px; }
.hero-promise { max-width: 54ch; }
.hero-cta { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; }
/* The only button in the hero, so it is sized as the thing to press. */
.hero-cta .btn-cta { justify-content: center; min-width: min(100%, 300px); padding: 20px 44px; font-size: 1.3rem; font-weight: 600; }
.home-block { margin-top: clamp(48px, 8vw, 80px); }

/* ---- Credits ------------------------------------------------------------- */
/* /credits/ only. One row per source: name + licence badge, what it powers,
   then the terms line. The list carries hairlines instead of giving every row
   its own card — the page is mostly small print and should stay quiet. */
.credit-list { list-style: none; margin: 20px 0 0; padding: 0; }
.prose .credit { border-top: 1px solid var(--border); padding: 18px 0; margin: 0; }
.credit:last-child { border-bottom: 1px solid var(--border); }
.prose .credit-name {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 10px;
  margin: 0 0 .35em; font-family: var(--display); font-size: 1.3rem; color: var(--text);
}
.credit-name a { color: inherit; text-decoration: none; }
.credit-name a:hover { text-decoration: underline; }
.credit-badge {
  font-family: var(--sans); font-size: .72rem; font-weight: 500; letter-spacing: .02em;
  color: var(--text-soft); background: var(--surface-2);
  border-radius: 999px; padding: 3px 10px;
}
.prose .credit-what { margin: 0; }
.prose .credit-note { margin: .5em 0 0; font-size: .92rem; color: var(--text-faint); }
.prose .credit-terms { margin: .6em 0 0; font-size: .82rem; font-weight: 500; color: var(--text-faint); }
/* ---- The level mark ------------------------------------------------------
   Not a card, but the piece every level-scoped card is built around, so it
   comes first. The level is set in the display serif at weight 400 in the
   level hue, with no box, no fill and no container. There is no tinted tag
   square anywhere in the system: a 46x46 rounded box holding two characters
   spends the hue on a shape rather than on the level, and five of them in a
   column read as five identical widgets.
   line-height:.8 is what makes the mark's box its ink and nothing else, so
   the card can centre the mark on the body block rather than on a tall line
   box. It used to sit on the title's baseline; against a two- or three-line
   body that put the mark's whole mass ~19px above the block's centre and read
   as unaligned, however exact the baselines were.
   --mark-w is what keeps the titles on one left edge. Every card is its own
   grid, so an `auto` first track is measured from that card's own mark, and
   Instrument Serif has no tabular numerals in the subset: "N1" is 6px narrower
   than "N2", and one kana is half the width of two kanji. Each mark therefore
   pins a min-width, in rem so the track is the same number of pixels in every
   card whatever size that card's mark is set at. Values are the widest mark of
   that kind plus a hair. */
.lvl-mark, .card-mark {
  font-family: var(--display); font-weight: 400; line-height: .8;
  letter-spacing: -.045em; color: var(--lvl, var(--text));
  font-variant-numeric: tabular-nums; white-space: nowrap;
  min-width: var(--mark-w, 0);
}
.lvl-mark { font-size: 2.3rem; --mark-w: 2.05rem; }
.card-mark { font-size: 1.7rem; --mark-w: 1.55rem; }
/* Mincho, not gothic: a Latin serif next to a gothic reads as an accident.
   Kanji are optically larger than "N5" at the same size and mincho is lighter,
   so the Japanese mark steps down in size and up in weight (see DESIGN.md 13). */
.lvl-mark:lang(ja), .card-mark:lang(ja) { font-family: var(--ja-display); font-weight: 600; letter-spacing: 0; }
.lvl-mark:lang(ja) { font-size: 1.8rem; --mark-w: 3.6rem; }
.card-mark:lang(ja) { font-size: 1.35rem; --mark-w: 2.7rem; }
/* A mark that is a word, not a level code ("Extra"). It sets smaller so it
   still fits the track N5-N1 occupy, rather than pushing its own card's title
   25px right of every other title in the grid. */
.lvl-mark-word { font-size: 1.3rem; }

/* ---- Cards: level / section --------------------------------------------- */
.level-card-grid { display: grid; gap: 16px; grid-template-columns: repeat(auto-fill, minmax(248px, 1fr)); }
/* Three tiers stacked in one grid: the mark sits in column 1 of row 1 beside
   the body, then the progress track and the stat line each span both columns.
   Padding is tighter at the bottom because the stat line carries its own
   optical space. */
.level-card {
  display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center;
  column-gap: 14px; row-gap: 13px; padding: 16px 18px 14px;
  border-radius: var(--radius); background: var(--surface);
  border: 1px solid var(--border); box-shadow: var(--shadow-sm); color: var(--text);
  transition: background-color .3s cubic-bezier(.4, 0, .2, 1),
              border-color .3s cubic-bezier(.4, 0, .2, 1),
              box-shadow .3s cubic-bezier(.4, 0, .2, 1);
}
/* Cards no longer lift. The feedback is a warm wash plus a border darkening;
   the hairline stays neutral, because the hue already appears twice inside. */
.level-card:hover { text-decoration: none; background: var(--accent-soft); border-color: var(--border-strong); box-shadow: var(--shadow-md); }
.level-card-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.level-card-body strong { font-size: 1.02rem; font-weight: 600; line-height: 1.3; letter-spacing: -.01em; }
.level-card-meta { font-size: .82rem; font-weight: 500; color: var(--text-faint); }
.level-card-blurb { font-size: .88rem; color: var(--text-soft); }

/* Tier 2. Progress IS what a study card is about, so it is structure, not a
   sentence. Hidden until app.js (initLevelCards) has something to fill in. */
.level-card-track { grid-column: 1 / -1; height: 4px; border-radius: 999px; background: var(--bg-sunken); overflow: hidden; }
.level-card-fill { display: block; height: 100%; border-radius: 999px; background: var(--lvl, var(--text-soft)); }
/* Tier 3. At 0% the percentage drops to --text-faint at weight 500 and the
   stat reads "Not started", so an untouched level does not shout. */
.level-card-stat { grid-column: 1 / -1; display: flex; align-items: baseline; justify-content: space-between; gap: 12px; font-size: .82rem; font-weight: 500; color: var(--text-soft); font-variant-numeric: tabular-nums; }
.level-card-pct { color: var(--lvl, var(--text)); font-weight: 600; }
.level-card-stat.is-empty .level-card-pct { color: var(--text-faint); font-weight: 500; }

/* Level identity. Cards render in a fixed N5 -> N1 order, so the nth-child
   only has to alias --lvl; every component below reads var(--lvl) and works
   at any level. The hue appears exactly twice per card, as the mark and as
   the track fill. It never reaches the border. */
.level-card-grid .level-card:nth-child(1) { --lvl: var(--n5); }
.level-card-grid .level-card:nth-child(2) { --lvl: var(--n4); }
.level-card-grid .level-card:nth-child(3) { --lvl: var(--n3); }
.level-card-grid .level-card:nth-child(4) { --lvl: var(--n2); }
.level-card-grid .level-card:nth-child(5) { --lvl: var(--n1); }

/* ---- Practice level hub: tests primary, flashcards secondary ------------- */
.hub-tests { margin-top: 6px; }
.hub-section-head { margin: 0 0 20px; }
.hub-section-head h2 { margin: 0 0 6px; }
.hub-section-head p { margin: 0; color: var(--text-soft); }
.test-card-grid, .flash-card-grid { display: grid; gap: 16px; grid-template-columns: repeat(auto-fill, minmax(264px, 1fr)); }
/* Roomier, and driven by --lvl from the page rather than by :nth-child. */
.test-card-grid .level-card, .flash-card-grid .level-card { padding: 20px 22px 18px; }
.test-card-grid .lvl-mark, .flash-card-grid .lvl-mark { font-size: 2.6rem; --mark-w: 2.35rem; }
.test-card-grid .lvl-mark:lang(ja) { font-size: 2rem; --mark-w: 4rem; }
.flash-card-grid .lvl-mark:lang(ja) { font-size: 2.2rem; --mark-w: 2.6rem; }
.test-card-grid .level-card-body strong, .flash-card-grid .level-card-body strong { font-size: 1.08rem; }
.hub-flashcards { margin-top: 44px; }

/* ---- Deck rail: one row per section, one chip per level -----------------
   For the pages that belong to no level (home, /practice/, the dashboard with
   no schedule running). A card grid of every deck would be fifteen cards; a
   row per section with the levels as chips is the same fifteen routes in the
   height of three cards, and it never has to guess which level the reader is
   on. The dashboard builds the identical markup in app.js.
   The row is a card, not a link: the chips are the links, and a card that is
   itself clickable with five buttons inside it has no honest hit area. The one
   exception is the dashboard with a schedule running, where the level is
   already decided and the row has no chips left to hold, so the row itself
   becomes the button (.deck-row-go). */
.deck-rows { display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px; }
.deck-row {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center;
  column-gap: 14px; row-gap: 12px; padding: 14px 18px;
  border-radius: var(--radius); background: var(--surface);
  border: 1px solid var(--border); box-shadow: var(--shadow-sm);
}
/* Mincho at 600, same as .lvl-mark:lang(ja), but this mark names a section
   rather than a level so it carries no hue. */
.deck-row-mark {
  font-family: var(--ja-display); font-weight: 600; font-size: 1.7rem;
  line-height: .8; min-width: 1.8rem; color: var(--text-soft);
}
.deck-row-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.deck-row-body strong { font-size: 1.02rem; font-weight: 600; line-height: 1.3; letter-spacing: -.01em; }
.deck-row-meta { font-size: .82rem; font-weight: 500; color: var(--text-faint); }
.deck-row-levels { display: flex; flex-wrap: wrap; gap: 8px; }
/* The chip is the button. Two lines, level over size, so the hue lands on the
   level exactly once and the count stays quiet under it. */
.deck-chip {
  display: inline-flex; flex-direction: column; align-items: center; gap: 1px;
  min-width: 58px; padding: 7px 10px 6px; border-radius: var(--radius-sm);
  background: var(--bg-elev); border: 1px solid var(--border); color: var(--text);
  transition: background-color .3s cubic-bezier(.4, 0, .2, 1),
              border-color .3s cubic-bezier(.4, 0, .2, 1);
}
.deck-chip:hover { text-decoration: none; background: var(--accent-soft); border-color: var(--border-strong); }
.deck-chip b { font-size: .92rem; font-weight: 600; line-height: 1.2; color: var(--lvl, var(--text)); }
.deck-chip span { font-size: .74rem; font-weight: 500; line-height: 1.2; color: var(--text-faint); font-variant-numeric: tabular-nums; }

/* One level already chosen: the row is the link and the arrow is the only
   thing standing in for the chips it no longer has. */
.deck-row-go { color: var(--text); }
.deck-row-go:hover { text-decoration: none; background: var(--accent-soft); border-color: var(--border-strong); box-shadow: var(--shadow-md); }
.deck-row-arrow { color: var(--text-faint); font-size: 1.05rem; }
.deck-row-go:hover .deck-row-arrow { color: var(--accent-strong); }

/* Narrow: the chips drop under the name rather than squeezing it to two
   words per line. */
@media (max-width: 600px) {
  .deck-row { grid-template-columns: auto minmax(0, 1fr); padding: 14px 16px; }
  .deck-row-levels { grid-column: 1 / -1; }
  .deck-chip { flex: 1 1 0; min-width: 0; }
  /* The chipless row keeps its arrow on the right rather than dropping it
     onto a line of its own. */
  .deck-row-go { grid-template-columns: auto minmax(0, 1fr) auto; }
}

/* ---- Section / feature cards -------------------------------------------- */
.section-card-grid { display: grid; gap: 16px; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }
.section-card {
  padding: 20px; border-radius: var(--radius); background: var(--surface);
  border: 1px solid var(--border); box-shadow: var(--shadow-sm); color: var(--text);
  transition: background-color .3s cubic-bezier(.4, 0, .2, 1),
              border-color .3s cubic-bezier(.4, 0, .2, 1),
              box-shadow .3s cubic-bezier(.4, 0, .2, 1);
}
.section-card:hover { text-decoration: none; background: var(--accent-soft); border-color: var(--border-strong); box-shadow: var(--shadow-md); }
/* Card title: sans at 600, not the display serif — 1.02rem is well below the
   serif floor. */
.section-card h3 { font-family: var(--sans); font-size: 1.02rem; font-weight: 600; line-height: 1.3; letter-spacing: -.01em; margin: 0 0 4px; }
.section-card-count { color: var(--accent-strong); font-weight: 500; font-size: .88rem; margin: 0 0 6px; }
.section-card-desc { color: var(--text-soft); font-size: .88rem; margin: 0; }

/* "What's inside" — primary feature cards. A bare line icon, no well and no
   fill: the boxed icon well is retired for the same reason the tag square is,
   it is a container standing in for content. */
.feature-card { padding: 24px; }
.feature-icon { display: block; width: 24px; height: 24px; margin-bottom: 14px; color: var(--text-faint); transition: color .18s cubic-bezier(.4, 0, .2, 1); }
.feature-icon svg { display: block; width: 24px; height: 24px; }
.feature-card:hover .feature-icon { color: var(--text); }
.feature-card .section-card-count { font-size: .82rem; margin: 0 0 6px; }

/* ---- Study path (level hub) --------------------------------------------- */
.study-step-grid { display: grid; gap: 16px; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); }
.study-step {
  display: flex; gap: 14px; align-items: center; padding: 20px;
  border-radius: var(--radius); background: var(--surface);
  border: 1px solid var(--border); box-shadow: var(--shadow-sm); color: var(--text);
  transition: background-color .3s cubic-bezier(.4, 0, .2, 1),
              border-color .3s cubic-bezier(.4, 0, .2, 1),
              box-shadow .3s cubic-bezier(.4, 0, .2, 1);
}
.study-step:hover { text-decoration: none; background: var(--accent-soft); border-color: var(--border-strong); box-shadow: var(--shadow-md); }
.study-step.is-disabled { opacity: .5; pointer-events: none; }
.step-num { flex: 0 0 auto; width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; background: var(--accent); color: var(--accent-contrast); font-weight: 600; font-size: .88rem; }
.step-body { display: flex; flex-direction: column; gap: 2px; }
.step-body strong { font-size: 1.02rem; font-weight: 600; line-height: 1.3; letter-spacing: -.01em; }
.step-count { color: var(--accent-strong); font-weight: 500; font-size: .88rem; }
.step-sub { color: var(--text-soft); font-size: .82rem; }
.step-progress { margin-top: 6px; font-size: .82rem; font-weight: 500; color: var(--text-faint); }
.study-practice { margin-top: 40px; }
.study-practice-title { margin: 0 0 16px; }

/* ---- Practice CTA card ---------------------------------------------------
   The loud one, and the only place --cta appears in a card. ONE per page: a
   second action beside it takes .practice-card-quiet below, anything further
   down the page is a .section-card. */
.practice-cards { display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); margin: 0 0 24px; }
.practice-card {
  display: flex; align-items: center; gap: 14px; padding: 16px 20px; border-radius: var(--radius);
  background: var(--cta); border: 1px solid transparent; color: var(--cta-contrast);
  box-shadow: var(--shadow-sm);
  transition: background-color .3s cubic-bezier(.4, 0, .2, 1), border-color .3s cubic-bezier(.4, 0, .2, 1);
}
.practice-card:hover { text-decoration: none; background: color-mix(in srgb, var(--cta) 88%, #000); }
.practice-card-icon { flex: 0 0 auto; display: grid; place-items: center; color: var(--cta-contrast); }
.practice-card-icon svg { display: block; }
.practice-card-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
.practice-card-body strong { font-size: 1.02rem; font-weight: 600; line-height: 1.3; letter-spacing: -.01em; }
.practice-card-meta { font-size: .82rem; font-weight: 500; color: var(--cta-contrast); opacity: .72; }
.practice-card-go { flex: 0 0 auto; color: var(--cta-contrast); font-weight: 500; font-size: 1.15rem; }

/* The quiet twin: same row, same metrics, no --cta fill. A level page carries
   its flashcard-deck link in one of these so the pair under the heading reads
   as one component at two weights, and the page still spends its single --cta
   on the exam tests. Must follow .practice-card:hover to win on source order. */
.practice-card-quiet { background: var(--surface); border-color: var(--border); color: var(--text); }
.practice-card-quiet:hover { background: var(--accent-soft); border-color: var(--border-strong); box-shadow: var(--shadow-md); }
.practice-card-quiet .practice-card-icon,
.practice-card-quiet .practice-card-go { color: var(--accent-strong); }
.practice-card-quiet .practice-card-meta { color: var(--text-soft); opacity: 1; }
/* ==========================================================================
   Anime for learners — /topics/anime/

   A reference list with pictures, so the card is a poster beside the notes.
   The art is the reason anyone scans this page: it bleeds to three edges of
   the card and is cropped by the card's own height, rather than sitting in
   the padding as a thumbnail. Two cards to a row on a wide screen, so the
   poster can be 180px and the notes still get a readable measure.
   ========================================================================== */

.anime-intro {
  max-width: 62ch; margin: 0 0 24px; color: var(--text-soft);
  font-size: var(--step); line-height: 1.65;
}

/* Band hue. The band IS a JLPT level, which makes it the one thing on the
   page allowed to carry colour (see 00-tokens). It appears twice: as the tick
   above the band heading and as the dot in that band's jump link. Never on a
   card, never on a border. Each band takes the lower of its two levels. */
.anime-band[data-band="n5-n4"], .anime-jump a[data-band="n5-n4"] { --lvl: var(--n5); }
.anime-band[data-band="n4-n3"], .anime-jump a[data-band="n4-n3"] { --lvl: var(--n4); }
.anime-band[data-band="n3-n2"], .anime-jump a[data-band="n3-n2"] { --lvl: var(--n3); }
.anime-band[data-band="n2-n1"], .anime-jump a[data-band="n2-n1"] { --lvl: var(--n2); }

/* ---- Band jump links ----------------------------------------------------- */
/* Four bands, so a plain row of anchors beats a sticky nav. The count sits in
   the link because "how many are down there" is the question being asked. */
.anime-jump { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 36px; }
.anime-jump a {
  display: inline-flex; align-items: baseline; gap: 8px;
  padding: 8px 14px; border-radius: var(--radius-sm);
  background: var(--surface); border: 1px solid var(--border);
  color: var(--text); font-size: .88rem; font-weight: 500;
  transition: background-color .3s cubic-bezier(.4, 0, .2, 1),
              border-color .3s cubic-bezier(.4, 0, .2, 1);
}
.anime-jump a::before {
  content: ""; align-self: center; flex: none;
  width: 7px; height: 7px; border-radius: 50%; background: var(--lvl, var(--text-faint));
}
.anime-jump a:hover { text-decoration: none; background: var(--accent-soft); border-color: var(--border-strong); }
.anime-jump a span { color: var(--text-faint); font-size: .8rem; font-variant-numeric: tabular-nums; }

/* ---- Band ---------------------------------------------------------------- */
.anime-band { margin: 0 0 52px; scroll-margin-top: 80px; }
.anime-band-head { margin: 0 0 20px; }
/* A short hue tick instead of a full rule: it marks the level without drawing
   a second horizontal line across a page that is already a stack of boxes. */
.anime-band-head::before {
  content: ""; display: block; width: 40px; height: 3px; border-radius: 999px;
  background: var(--lvl, var(--border-strong)); margin: 0 0 14px;
}
.anime-band-head h2 { margin: 0 0 6px; font-size: clamp(1.55rem, 3.2vw, 2.1rem); }
.anime-band-blurb { margin: 0; max-width: 62ch; color: var(--text-soft); font-size: var(--step); line-height: 1.6; }
.anime-band-study { margin: 8px 0 0; font-size: .85rem; color: var(--text-faint); }
.anime-band-study a { color: var(--accent-strong); text-decoration: underline; text-underline-offset: .16em; }
.anime-band-study a + a { margin-inline-start: 12px; }

/* ---- The cover wall ------------------------------------------------------ */
/* Covers, at a size worth looking at, and a title. Everything else about a
   show is a sheet away (app.js, initAnimeCards): forty-two entries of notes
   laid out in full is a wall of text nobody reads, and the cover is what
   people actually pick from. */
.anime-grid { display: grid; gap: 24px 16px; grid-template-columns: repeat(auto-fill, minmax(min(100%, 158px), 1fr)); }
.anime-item { min-width: 0; scroll-margin-top: 80px; }
/* Only reachable with JavaScript off, since the summary is intercepted. The
   notes need the whole row when they open in place, and the cover has to be
   held back to its tile width or it fills that row at 2:3. */
.anime-item[open] { grid-column: 1 / -1; }
.anime-item[open] .anime-tile-art { max-width: 158px; }
.anime-item[open] .anime-detail { max-width: 68ch; }

.anime-tile { display: block; cursor: pointer; list-style: none; }
.anime-tile::-webkit-details-marker { display: none; }
.anime-tile:focus-visible { border-radius: var(--radius-sm); }
.anime-tile-art {
  display: block; position: relative; overflow: hidden;
  aspect-ratio: 2 / 3; border-radius: var(--radius-sm);
  background: var(--bg-sunken); border: 1px solid var(--border);
  transition: border-color .3s cubic-bezier(.4, 0, .2, 1),
              box-shadow .3s cubic-bezier(.4, 0, .2, 1);
}
.anime-cover, .anime-cover-blank { display: block; width: 100%; height: 100%; object-fit: cover; }
.anime-tile-title { margin: 10px 0 0; font-size: .98rem; line-height: 1.2; letter-spacing: -.01em; }
.anime-tile:hover .anime-tile-art { border-color: var(--border-strong); box-shadow: var(--shadow-md); }
.anime-tile:hover .anime-tile-title { text-decoration: underline; text-underline-offset: .16em; }

/* ---- One show's notes ---------------------------------------------------- */
/* Written once and worn twice: inside the sheet, and inline underneath the
   cover when there is no JavaScript to move it. The cover only appears in the
   sheet, because inline it is already sitting directly above. */
.anime-detail { padding-top: 16px; }
.anime-detail-top { display: grid; gap: 18px; }
.anime-detail-art { display: none; }
.anime-sheet .anime-detail-top { grid-template-columns: 128px minmax(0, 1fr); }
.anime-sheet .anime-detail-art {
  display: block; overflow: hidden; aspect-ratio: 2 / 3;
  border-radius: var(--radius-sm); background: var(--bg-sunken); border: 1px solid var(--border);
}
.anime-sheet .anime-detail-art img { display: block; width: 100%; height: 100%; object-fit: cover; }
.anime-sheet .set-card { max-width: 560px; }

.anime-title-ja { margin: 0; font-family: var(--ja-display); font-size: 1rem; color: var(--text-soft); }

/* Year, episode count, runtime, studio. A spec strip, not a sentence, so the
   items are spaced rather than punctuated: a dot separator ends up dangling at
   the end of the line every time a long studio name wraps. */
.anime-meta {
  display: flex; flex-wrap: wrap; gap: 3px 16px; margin: 9px 0 0; padding: 0;
  list-style: none; font-size: .8rem; color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}
/* What the show is about carries the weight here; the tags are a footnote to
   it, which is why they are a line of text and not a row of pills. */
.anime-tags { margin: 12px 0 0; font-size: .8rem; color: var(--text-faint); }
.anime-premise { margin: 12px 0 0; font-size: .98rem; line-height: 1.55; color: var(--text); }

/* Two notes, each under a micro-label. The label sits over its note rather
   than beside it: a label column would take a third of the measure. */
.anime-notes { margin: 16px 0 0; padding: 14px 0 0; border-top: 1px solid var(--border); }
.anime-notes dt {
  font-size: .68rem; font-weight: 600; text-transform: uppercase; letter-spacing: .09em;
  color: var(--text-faint);
}
.anime-notes dd { margin: 3px 0 0; font-size: .9rem; line-height: 1.55; color: var(--text-soft); }
.anime-notes dd + dt { margin-top: 12px; }

@media (max-width: 560px) {
  .anime-band { margin-bottom: 40px; }
  .anime-grid { gap: 20px 14px; grid-template-columns: repeat(auto-fill, minmax(min(100%, 140px), 1fr)); }
  .anime-tile-title { font-size: .92rem; }
  .anime-sheet .anime-detail-top { grid-template-columns: 104px minmax(0, 1fr); gap: 14px; }
}
/* ---- Grammar list -------------------------------------------------------- */
.grammar-list { display: grid; gap: 8px; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }
.grammar-row { display: flex; align-items: center; gap: 10px; padding: 12px 16px; border-radius: var(--radius-sm); background: var(--surface); border: 1px solid var(--border); }
.grammar-row:hover { background: var(--accent-soft); border-color: var(--border-strong); }
.grammar-row > a { display: flex; flex-direction: column; gap: 1px; flex: 1 1 auto; min-width: 0; color: var(--text); }
.grammar-row > a:hover { text-decoration: none; }
.grammar-row-form { font-family: var(--ja); font-size: 1.1rem; font-weight: 700; }
.grammar-row-gloss { font-size: .82rem; color: var(--text-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.grammar-row-meta { flex: 0 0 auto; font-size: .72rem; color: var(--text-faint); font-weight: 600; }
.grammar-row-learned { display: none; flex: 0 0 auto; color: var(--st-done); }
.grammar-row-learned svg { display: block; }
/* .is-learned card colours live in the shared .recall-cell state block below. */
.grammar-row.is-learned .grammar-row-learned { display: inline-flex; }

/* ---- Grammar / kanji detail --------------------------------------------- */
.grammar-detail, .kanji-detail { max-width: 760px; }

/* ---- Level contents sidebar (desktop only) ------------------------------ */
/* On wide screens the grammar detail page becomes two columns: a sticky list
   of every point in this level on the left, the article on the right. Below
   the breakpoint the sidebar is hidden and the article flows as before. */
.grammar-nav { display: none; }
@media (min-width: 1000px) {
  .grammar-detail-layout { display: grid; grid-template-columns: 220px minmax(0, 1fr); gap: 32px; align-items: start; }
  .grammar-nav {
    display: flex; flex-direction: column; position: sticky;
    top: calc(var(--header-h) + 16px); align-self: start;
    max-height: calc(100vh - var(--header-h) - 32px);
  }
}
.grammar-nav-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding-bottom: 10px; margin-bottom: 8px; border-bottom: 1px solid var(--border); }
.grammar-nav-title { margin: 0; font-size: .72rem; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--text-faint); }
.grammar-nav-count { flex: 0 0 auto; font-size: .72rem; font-weight: 600; color: var(--text-faint); background: var(--surface-2); padding: 2px 9px; border-radius: 999px; }
.grammar-nav-scroll { flex: 1 1 auto; overflow-y: auto; margin-inline: -6px; padding-inline: 6px; scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent; }
.grammar-nav-list { display: flex; flex-direction: column; gap: 1px; list-style: none; margin: 0; padding: 0; }
.grammar-nav-link { display: flex; align-items: center; gap: 8px; padding: 6px 9px; border-radius: var(--radius-sm); color: var(--text-soft); }
.grammar-nav-link span[lang="ja"] { flex: 1 1 auto; min-width: 0; font-family: var(--ja); font-size: .88rem; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.grammar-nav-link:hover { background: var(--surface-2); color: var(--text); text-decoration: none; }
.grammar-nav-link.is-current { background: var(--accent-soft); color: var(--accent-strong); }
.grammar-nav-check { display: none; flex: 0 0 auto; color: var(--st-done); }
.grammar-nav-check svg { display: block; }
.grammar-nav-link.is-learned .grammar-nav-check { display: inline-flex; }
.grammar-nav-link.is-learned span[lang="ja"] { color: var(--text); }
.detail-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 14px 24px; flex-wrap: wrap; padding-bottom: 18px; border-bottom: 1px solid var(--border); margin-bottom: 8px; }
.detail-head-main { flex: 1 1 auto; min-width: 0; }
.detail-eyebrow { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; font-size: .82rem; font-weight: 600; }
.detail-head h1 { font-family: var(--ja); font-size: clamp(1.9rem, 6vw, 2.7rem); margin: 0; line-height: 1.15; }
.detail-gloss { font-size: 1.15rem; color: var(--text-soft); margin: 6px 0 0; }
.detail-romaji { font-size: .88rem; color: var(--text-faint); }
.detail-title-romaji { font-family: var(--sans); font-weight: 600; font-size: .55em; color: var(--text-faint); white-space: nowrap; }
.detail-def { font-size: 1.05rem; margin: 0 0 12px; }
.detail-def strong span[lang="ja"] { font-family: var(--ja); }
.detail-head .learn-toggle { margin-top: 2px; flex: 0 0 auto; }
.badge { display: inline-flex; align-items: center; padding: 3px 10px; border-radius: 999px; font-size: .72rem; font-weight: 600; background: var(--surface-2); color: var(--text-soft); text-transform: uppercase; letter-spacing: .04em; }
.learn-toggle { display: inline-flex; align-items: center; gap: 8px; margin-top: 16px; padding: 8px 14px; border-radius: 999px; border: 1px solid var(--border-strong); background: var(--surface); font-size: .88rem; font-weight: 600; color: var(--text-soft); cursor: pointer; }
.learn-toggle:hover { border-color: var(--accent); }
/* The checkbox itself is hidden — the label text ("Learned") and the green
   styling are the indicator, so there's no redundant tick. */
.learn-toggle input { position: absolute; opacity: 0; width: 1px; height: 1px; margin: 0; pointer-events: none; }
.learn-toggle.is-learned { background: color-mix(in srgb, var(--st-done) 14%, var(--surface)); border-color: color-mix(in srgb, var(--st-done) 55%, var(--border)); color: var(--st-done); }

.detail-section { margin-top: 24px; }
.detail-section h2 { display: flex; align-items: center; gap: 10px; margin: 0 0 10px; font-family: var(--sans); font-size: .72rem; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--text-faint); }
.count-chip { font-size: .82rem; font-weight: 600; color: var(--accent-strong); background: var(--accent-soft); padding: 2px 10px; border-radius: 999px; }

/* ---- Examples ------------------------------------------------------------ */
.example-list { display: flex; flex-direction: column; gap: 10px; }
.example-list.is-collapsed > *:nth-child(n+4) { display: none; }
.example-more { margin-top: 12px; padding: 12px 22px; font-size: .88rem; font-weight: 500; color: var(--text); background: transparent; border: 1px solid var(--border-strong); border-radius: var(--radius); cursor: pointer; }
.example-more:hover { border-color: var(--accent); color: var(--accent-strong); }
.example { padding: 14px 16px; border-radius: var(--radius-sm); background: var(--surface); border: 1px solid var(--border); }
.example-ja-row { display: flex; align-items: flex-start; gap: 12px; }
.example-ja { font-family: var(--ja); font-size: 1.18rem; line-height: 1.7; margin: 0; }
.example-en { color: var(--text-soft); margin: 8px 0 0; padding-inline-start: 40px; font-size: var(--step); }

/* ---- Audio button -------------------------------------------------------- */
.audio-btn {
  flex: 0 0 auto; width: 30px; height: 30px; margin-top: 4px; border-radius: 50%;
  border: 1px solid var(--border-strong); background: var(--surface); color: var(--accent-strong);
  display: inline-grid; place-items: center; transition: background .18s cubic-bezier(.4, 0, .2, 1);
}
.audio-btn:hover { background: var(--accent-soft); border-color: var(--accent); }
.audio-btn.is-playing { background: var(--accent); border-color: var(--accent); color: var(--accent-contrast); }
.audio-btn svg { width: 13px; height: 13px; }
/* Server-rendered audio buttons are intentionally empty until app.js enhances
   them. Keep the play mark visible for late-inserted gated content too. */
.audio-btn:empty::before {
  content: ""; width: 0; height: 0; margin-left: 2px;
  border-top: 4px solid transparent; border-bottom: 4px solid transparent;
  border-left: 7px solid currentColor;
}
.audio-btn:active { transform: scale(.92); }

/* ---- Furigana ------------------------------------------------------------ */
ruby rt { font-size: .68em; color: var(--text-soft); font-weight: 500; transition: opacity .18s cubic-bezier(.4, 0, .2, 1); }
ruby.furigana-hidden rt { opacity: 0; }

/* ---- prev/next + related ------------------------------------------------- */
/* In-flow prev / next pager below the examples (prev left, all-grammar middle,
   next right). */
.detail-pager { display: grid; grid-template-columns: 1fr auto 1fr; gap: 10px; align-items: stretch; margin-top: 30px; padding-top: 20px; border-top: 1px solid var(--border); }
.detail-pager a { display: flex; flex-direction: column; justify-content: center; gap: 2px; padding: 10px 14px; border-radius: var(--radius-sm); border: 1px solid var(--border); background: var(--surface); color: var(--text); }
.detail-pager a:hover { background: var(--accent-soft); border-color: var(--border-strong); text-decoration: none; }
.pager-next { text-align: right; align-items: flex-end; }
.pager-dir { font-size: .72rem; color: var(--text-faint); }
.detail-pager strong { font-family: var(--ja); font-size: 1.02rem; }
.pager-up { justify-content: center; align-items: center; text-align: center; font-size: .82rem; font-weight: 600; color: var(--text-soft); }
.related { margin-top: 34px; }
.related h2 { font-family: var(--sans); font-size: .72rem; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--text-faint); margin: 0 0 12px; }
.related-list { display: grid; gap: 8px; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
.related-list a { display: flex; flex-direction: column; gap: 1px; padding: 10px 13px; border-radius: var(--radius-sm); background: var(--surface-2); border: 1px solid var(--border); color: var(--text); }
.related-list a:hover { background: var(--accent-soft); border-color: var(--border-strong); text-decoration: none; }
.related-list a span[lang="ja"] { font-family: var(--ja); font-weight: 700; }
.related-list a span:not([lang="ja"]):not(.related-level) { font-size: .82rem; color: var(--text-soft); }
.related-list a .related-level { font-size: .72rem; font-weight: 600; letter-spacing: .04em; color: var(--text-faint); }

/* Per-word vocab pages, hub FAQ blocks, and Japanese section labels */
.vocab-word a { color: inherit; text-decoration: none; }
.vocab-word a::after { content: ""; position: absolute; inset: 0; z-index: 1; }
.vocab-word a:hover { color: var(--text); }
.detail-head-audio { flex: 0 0 auto; margin-top: 2px; }
.reading-label-ja, .section-label-ja { font-size: .82em; font-weight: 600; color: var(--text-faint); margin-left: 4px; }
.faq-section { margin-top: 40px; }
.faq-section h2 { font-family: var(--sans); font-size: .72rem; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--text-faint); margin: 0 0 12px; }
.faq-list { display: grid; gap: 8px; }
.faq-item { border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface-2); padding: 12px 14px; }
.faq-item summary { font-weight: 600; cursor: pointer; }
.faq-item p { margin: 8px 0 0; color: var(--text-soft); font-size: var(--step); }
/* ---- Vocab cards --------------------------------------------------------- */
.vocab-list { display: grid; gap: 12px; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); }
.vocab-card { position: relative; padding: 14px 16px; border-radius: var(--radius-sm); background: var(--surface); border: 1px solid var(--border); transition: border-color .3s cubic-bezier(.4, 0, .2, 1), box-shadow .3s cubic-bezier(.4, 0, .2, 1); }
.vocab-card:hover { background: var(--accent-soft); border-color: var(--border-strong); }
.vocab-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.vocab-word { font-family: var(--ja); font-size: 1.5rem; font-weight: 700; margin: 0; }
.vocab-reading { font-family: var(--ja); color: var(--text-soft); font-size: var(--step); }
.vocab-head-end { margin-inline-start: auto; display: inline-flex; align-items: center; gap: 10px; position: relative; z-index: 2; pointer-events: none; }
.vocab-head-end > * { pointer-events: auto; }
.vocab-head-end .audio-btn { margin-top: 0; }
.vocab-pos { font-size: .72rem; font-weight: 600; color: var(--text-faint); background: var(--surface-2); padding: 2px 9px; border-radius: 999px; text-transform: lowercase; }
.vocab-meaning { margin: 8px 0 0; }

/* ── Recall study mode ───────────────────────────────────────
   A toggle that blurs the readings & meanings in a vocab list or on a word
   page so you can self-test, revealing each on hover, keyboard focus, or tap
   and re-blurring on leave. Injected and driven by app.js (the toggle ships
   hidden and is only unhidden when JS runs). The effect is a pure CSS filter,
   so the text stays in the DOM untouched — indexing, rich-result snippets and
   screen readers are unaffected; only the on-screen pixels are blurred. */
.recall-bar { margin: 0 0 14px; }
/* Hosted inside the progress control's row: no own margin (the row gap spaces
   it), grouped next to the Flashcards button on the right (view-mode toggles,
   separate from the status/scheduling group on the left). */
.recall-bar-inline { margin: 0; }
.recall-bar-inline .recall-toggle { gap: 4px; padding: 3px 9px; font-size: .72rem; }
.recall-bar-inline .recall-toggle svg { width: 12px; height: 12px; }
.recall-toggle {
  display: inline-flex; align-items: center; gap: 7px; cursor: pointer;
  padding: 7px 13px; border: 1px solid var(--border-strong); border-radius: 999px;
  background: var(--surface); color: var(--text-soft); font-size: .82rem; font-weight: 600;
}
.recall-toggle:hover { border-color: var(--accent); color: var(--accent-strong); }
/* The level lists' jump buttons ("First page", "Next to study"), added beside
   the toggle by app.js (initListJump) and drawn as the same pill. */
.recall-bar:not(.recall-bar-inline):not([hidden]) { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.list-jump { display: inline-flex; flex-wrap: wrap; gap: 8px; margin-inline-start: auto; }
.list-jump:empty { display: none; }
.list-jump-btn {
  display: inline-flex; align-items: center; padding: 7px 13px;
  border: 1px solid var(--border-strong); border-radius: 999px;
  background: var(--surface); color: var(--text-soft); font-size: .82rem; font-weight: 600;
}
.list-jump-btn:hover { border-color: var(--accent); color: var(--accent-strong); text-decoration: none; }
/* The item "Next to study" lands on. A ring, not a fill, so it sits over
   whatever study state the row already shows. */
.recall-cell.is-next-up { outline: 2px solid var(--accent-strong); outline-offset: 2px; }
.recall-toggle[aria-pressed="true"] { background: var(--accent-soft); border-color: var(--accent); color: var(--accent-strong); }

.recall-scope.is-recall .recall-hide,
.recall-scope.is-recall .recall-cell rt {
  filter: blur(5px); user-select: none; cursor: pointer; transition: filter .18s cubic-bezier(.4, 0, .2, 1);
}
/* A blurred field is a click target, so give it room to be hit — the negative
   margin keeps the padding from shifting anything around it. */
.recall-scope.is-recall .recall-hide { display: inline-block; padding: 2px 6px; margin: -2px -6px; border-radius: var(--radius-xs); }
/* Opened by a click: readable until clicked again, and tinted so it is clear
   this one is staying open. Hover deliberately does NOT reveal — a peek that
   closes itself when the pointer drifts away reads as the answer re-blurring
   on its own. One field at a time either way: checking one answer must never
   spoil the one beside it, so the target is the hidden field, not its row. */
.recall-scope.is-recall .recall-hide.is-revealed { background: var(--accent-soft); }
/* Keyboard focus reveals; a mouse click must NOT, or clicking a pinned field to
   put it back would leave it visible (the click focuses it), which reads as the
   toggle being broken. :focus-visible is exactly that distinction. */
.recall-scope.is-recall .recall-hide:focus-visible,
.recall-scope.is-recall .recall-hide:has(:focus-visible),
.recall-scope.is-recall .recall-hide.is-revealed,
.recall-scope.is-recall .recall-cell ruby.is-revealed rt { filter: none; user-select: auto; }
.recall-scope.is-recall .recall-hide:focus { outline: none; }
.recall-scope.is-recall .recall-hide:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: 2px; }
/* In recall mode the whole list card is one self-test cell, so its stretched
   link must not swallow taps meant to reveal it (the word text stays a link). */
.recall-scope.is-recall .vocab-card .vocab-word a::after { pointer-events: none; }

.vocab-examples { display: flex; flex-direction: column; }
.vocab-example { display: flex; gap: 10px; margin-top: 12px; padding-top: 12px; border-top: 1px dashed var(--border); }
.vocab-example[hidden] { display: none; }
.vocab-example-ja { font-family: var(--ja); margin: 0; font-size: 1rem; }
.vocab-example-en { margin: 4px 0 0; color: var(--text-soft); font-size: .88rem; }
.show-more-examples { margin-top: 12px; align-self: flex-start; padding: 6px 14px; background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm); font-size: .82rem; font-weight: 500; color: var(--text-soft); cursor: pointer; transition: background-color .18s cubic-bezier(.4, 0, .2, 1), border-color .18s cubic-bezier(.4, 0, .2, 1), color .18s cubic-bezier(.4, 0, .2, 1); }
.show-more-examples:hover { background: var(--accent-soft); color: var(--text); border-color: var(--border-strong); }

/* ---- Sentence cards ------------------------------------------------------ */
.sentence-list { display: flex; flex-direction: column; gap: 12px; }
.sentence-card { padding: 16px; border-radius: var(--radius-sm); background: var(--surface); border: 1px solid var(--border); }
.sentence-ja-row { display: flex; align-items: flex-start; gap: 12px; }
.sentence-ja { font-family: var(--ja); font-size: 1.22rem; line-height: 1.75; margin: 0; }
.sentence-reveal { margin-inline-start: auto; flex: 0 0 auto; align-self: center; font-size: .72rem; font-weight: 600; color: var(--accent-strong); background: var(--surface-2); border: 1px solid var(--border); padding: 4px 12px; border-radius: 999px; cursor: pointer; transition: background .18s cubic-bezier(.4, 0, .2, 1), border-color .18s cubic-bezier(.4, 0, .2, 1); }
.sentence-reveal:hover { background: var(--accent-soft); border-color: var(--accent); }
.sentence-reveal[aria-expanded="true"] { color: var(--text-soft); }
.sentence-details { margin-top: 8px; }
.sentence-reading { font-family: var(--ja); font-size: 1rem; line-height: 1.6; color: var(--text-soft); margin: 0; padding-inline-start: 42px; }
.sentence-en { color: var(--text-soft); margin: 8px 0 0; padding-inline-start: 42px; }
.sentence-vocab { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 12px; padding-top: 12px; border-top: 1px dashed var(--border); }
.sentence-vocab li { display: inline-flex; align-items: center; gap: 6px; padding: 4px 9px; border-radius: 999px; background: var(--surface-2); font-size: .82rem; }
.sv-word { font-family: var(--ja); font-weight: 700; }
.sv-reading { font-family: var(--ja); color: var(--text-faint); }
.sv-meaning { color: var(--text-soft); }
.sv-level { font-size: .72rem; font-weight: 600; color: var(--accent-strong); }

/* ---- Kanji --------------------------------------------------------------- */
.kanji-grid { display: grid; gap: 10px; grid-template-columns: repeat(auto-fill, minmax(92px, 1fr)); }
.kanji-cell { display: flex; flex-direction: column; align-items: center; gap: 4px; padding: 12px 6px; border-radius: var(--radius-xs); background: var(--surface); border: 1px solid var(--border); color: var(--text); text-align: center; transition: border-color .18s cubic-bezier(.4, 0, .2, 1); }
.kanji-cell:hover { background: var(--accent-soft); border-color: var(--border-strong); text-decoration: none; transform: translateY(-2px); }
.kanji-cell-glyph { font-family: var(--ja); font-size: 2rem; font-weight: 700; }
.kanji-cell-meaning { font-size: .72rem; color: var(--text-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
.kanji-detail-head { display: flex; gap: 20px; align-items: center; flex-wrap: wrap; padding-bottom: 18px; border-bottom: 1px solid var(--border); }
.kanji-glyph { font-family: var(--ja); font-size: clamp(4rem, 18vw, 6.5rem); font-weight: 700; line-height: 1; }
.kanji-detail-meta h1 { font-size: clamp(1.3rem, 4vw, 1.8rem); }
.kanji-stat-chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0 0; }
.kanji-stat-chips li { font-size: .82rem; font-weight: 600; color: var(--text-soft); background: var(--surface-2); border: 1px solid var(--border); padding: 3px 10px; border-radius: 999px; }
.kanji-cats { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0 0; }
.kanji-cats span { font-size: .72rem; color: var(--accent-strong); background: var(--accent-soft); padding: 3px 10px; border-radius: 999px; }
.kanji-readings { display: flex; flex-direction: column; gap: 16px; margin: 0; }
.kanji-reading-row { display: flex; gap: 20px; align-items: flex-start; }
.kanji-reading-row dt { flex: 0 0 110px; display: flex; flex-direction: column; gap: 4px; font-weight: 600; color: var(--text); padding-top: 4px; }
.kanji-reading-row dt .reading-label-ja { margin-left: 0; font-size: .72rem; font-weight: 600; }
.kanji-reading-row dd { margin: 0; display: flex; flex-wrap: wrap; gap: 10px; flex: 1; }
.kanji-reading-row dd span { font-family: var(--ja); padding: 6px 14px; font-size: 1.05rem; border-radius: var(--radius-sm); background: var(--surface); border: 1px solid var(--border); box-shadow: var(--shadow-sm); transition: border-color .18s cubic-bezier(.4, 0, .2, 1); }
.kanji-reading-row dd span:hover { border-color: var(--accent); }
.kanji-examples { display: grid; gap: 12px; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }
.kanji-examples li { display: flex; flex-direction: column; gap: 8px; padding: 16px; border-radius: var(--radius); background: var(--surface); border: 1px solid var(--border); box-shadow: var(--shadow-sm); transition: box-shadow .3s cubic-bezier(.4, 0, .2, 1), border-color .3s cubic-bezier(.4, 0, .2, 1); }
.kanji-examples li:hover { background: var(--accent-soft); box-shadow: var(--shadow-md); border-color: var(--border-strong); }
.ke-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.ke-word { font-family: var(--ja); font-weight: 700; font-size: 1.35rem; line-height: 1.2; }
.ke-reading { font-family: var(--ja); color: var(--accent-strong); font-weight: 600; font-size: .88rem; background: var(--accent-soft); padding: 3px 10px; border-radius: 999px; }
.ke-meaning { color: var(--text-soft); font-size: .88rem; line-height: 1.5; }

/* ---- Kanji stroke order (inlined KanjiVG) -------------------------------- */
.stroke-order { display: flex; align-items: flex-start; gap: 12px; }
.stroke-order-figure {
  position: relative; width: 200px; height: 200px; max-width: 100%;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface); box-shadow: var(--shadow-sm);
}
/* dashed centre crosshair — the classic writing-practice guide */
.stroke-order-figure::before, .stroke-order-figure::after { content: ""; position: absolute; pointer-events: none; }
.stroke-order-figure::before { top: 8%; bottom: 8%; left: 50%; border-left: 1px dashed var(--border-strong); }
.stroke-order-figure::after { left: 8%; right: 8%; top: 50%; border-top: 1px dashed var(--border-strong); }
.stroke-order-figure .kvg { position: relative; display: block; width: 100%; height: 100%; }
.kvg-strokes path { stroke: var(--text); stroke-width: 3.4; }
.kvg-numbers text { fill: var(--accent-strong); font-family: var(--sans); font-weight: 600; }
/* numbers hidden while toggled off or during the draw animation */
[data-stroke-order].numbers-off .kvg-numbers, [data-stroke-order].is-animating .kvg-numbers { display: none; }
/* icon-only controls, stacked at the diagram's top-right */
.stroke-order-controls { display: flex; flex-direction: column; gap: 8px; }
.so-btn { display: inline-flex; align-items: center; justify-content: center; width: 34px; height: 34px; padding: 0; border-radius: 999px; color: var(--text-soft); background: var(--surface); border: 1px solid var(--border-strong); box-shadow: var(--shadow-sm); cursor: pointer; transition: color .18s cubic-bezier(.4, 0, .2, 1), border-color .18s cubic-bezier(.4, 0, .2, 1), background .18s cubic-bezier(.4, 0, .2, 1); }
.so-btn:hover { border-color: var(--accent); color: var(--accent-strong); }
.so-btn:active { transform: scale(.94); }
.so-numbers[aria-pressed="true"] { background: var(--accent-soft); border-color: var(--accent); color: var(--accent-strong); }
/* trace mode: glyph fades to a guide, user ink lives on a canvas overlay */
.so-draw[aria-pressed="true"] { background: var(--accent-soft); border-color: var(--accent); color: var(--accent-strong); }
.stroke-draw-canvas { position: absolute; inset: 0; width: 100%; height: 100%; touch-action: none; cursor: crosshair; z-index: 1; border-radius: inherit; }
[data-stroke-order].is-drawing .kvg-strokes path { stroke: var(--border-strong); }
[data-stroke-order].is-drawing .kvg-strokes path.kvg-done { stroke: var(--text); }
[data-stroke-order].is-drawing .kvg-strokes path.kvg-hint { stroke: var(--accent); }
[data-stroke-order].is-complete .kvg-strokes path.kvg-done { stroke: var(--accent-strong); }
.so-count { position: absolute; right: 8px; bottom: 6px; z-index: 2; pointer-events: none; font-size: .72rem; font-weight: 600; color: var(--text-soft); background: var(--surface); border: 1px solid var(--border); border-radius: 999px; padding: 1px 8px; }
/* ---- Practice index & hub – text links (minimal) ------------------------ */
.text-link-list { list-style: none; padding: 0; margin: 16px 0; display: flex; flex-direction: column; gap: 14px; }
.text-link-list li { font-size: 1.15rem; }
.text-link-list a { font-weight: 500; color: var(--accent-strong); text-decoration: underline; text-underline-offset: .18em; text-decoration-thickness: 1px; }
.text-link-list a:hover { color: var(--accent-strong); }
.tll-meta { color: var(--text-soft); font-size: var(--step); margin-left: 8px; font-weight: 500; }


/* ---- Practice exercise list (stacked buttons) --------------------------- */
.ex-rows { display: flex; flex-direction: column; gap: 8px; margin-top: 8px; }
.ex-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 14px 18px; border-radius: var(--radius-sm); border: 1px solid var(--border); background: var(--surface); color: var(--text); font-weight: 600; text-decoration: none; transition: border-color .18s cubic-bezier(.4, 0, .2, 1), background .18s cubic-bezier(.4, 0, .2, 1); }
.ex-row:hover { border-color: var(--border-strong); background: var(--accent-soft); color: var(--text); text-decoration: none; }
.ex-row-go { color: var(--text-faint); font-weight: 600; }
.ex-row-source { font-size: .82rem; font-weight: 500; color: var(--text-faint); margin-left: 10px; }
.ex-row:hover .ex-row-go { color: var(--text); }
.practice-access-message { margin: 24px 0; padding: 24px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); }
.practice-access-message h2 { margin-top: 0; }
.practice-access-message p { color: var(--text-soft); }
.practice-access-message p:empty { display: none; }
.practice-access-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 16px; }
.practice-access-actions button:disabled { opacity: .45; cursor: default; pointer-events: none; }
.practice-access-switch { margin: 12px 0 0 -12px; }
.practice-free-link { display: block; margin-top: 16px; }

/* ---- Practice single-exercise prev/next nav ----------------------------- */
.ex-nav { display: flex; justify-content: space-between; gap: 10px; margin-top: 28px; }
.ex-nav-link { padding: 12px 22px; border-radius: var(--radius); border: 1px solid var(--border-strong); background: var(--surface); color: var(--text); font-size: .88rem; font-weight: 600; text-decoration: none; }
.ex-nav-link:hover { border-color: var(--accent); color: var(--accent-strong); text-decoration: none; }
.ex-nav-link.is-disabled { color: var(--text-faint); border-color: var(--border); opacity: .5; pointer-events: none; }

/* ---- Study / practice hub ----------------------------------------------- */
.game-blocks { display: flex; flex-direction: column; gap: 12px; }
.game-block { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; padding: 18px 20px; border-radius: var(--radius); background: var(--surface); border: 1px solid var(--border); box-shadow: var(--shadow-sm); }
.game-block-head { flex: 1 1 280px; min-width: 0; }
.game-block-head h2 { font-size: clamp(1.3rem, 2.6vw, 1.6rem); line-height: 1.15; letter-spacing: -.02em; margin: 0 0 4px; }
.game-block-head h2 a { color: var(--text); }
.game-block-head h2 a:hover { color: var(--accent-strong); }
.game-block-head p { margin: 0; color: var(--text-soft); font-size: .88rem; line-height: 1.5; }
.game-block-levels, .study-guides { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-inline-start: auto; }
.game-block-label { font-size: .72rem; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--text-faint); }
.game-block-levels a, .study-guides a { min-width: 40px; text-align: center; padding: 6px 14px; border-radius: var(--radius-sm); background: var(--surface-2); border: 1px solid var(--border); color: var(--text-soft); font-size: .88rem; font-weight: 500; }
.game-block-levels a:hover, .study-guides a:hover { border-color: var(--accent); color: var(--accent-strong); text-decoration: none; }
.study-guides { justify-content: flex-end; margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--border); }

/* ---- Pagination ---------------------------------------------------------- */
.pagination { display: flex; align-items: center; justify-content: center; gap: 8px; flex-wrap: wrap; margin-top: 32px; }
.pagination a, .pagination span { padding: 7px 12px; border-radius: var(--radius-sm); font-weight: 600; font-size: .88rem; }
.page-prev, .page-next { border: 1px solid var(--border-strong); background: var(--surface); color: var(--text); }
.page-prev:hover, .page-next:hover { border-color: var(--accent); text-decoration: none; }
.page-prev.is-disabled, .page-next.is-disabled { opacity: .4; pointer-events: none; }
.page-nums { display: flex; gap: 4px; align-items: center; }
.page-num { color: var(--text-soft); }
.page-num:hover { background: var(--surface-2); text-decoration: none; }
.page-num.is-current { background: var(--accent); color: var(--accent-contrast); }
.page-gap { color: var(--text-faint); }
/* ---- Search overlay ------------------------------------------------------ */
.search-overlay { position: fixed; inset: 0; z-index: 60; background: var(--scrim); backdrop-filter: blur(4px); display: flex; align-items: flex-start; justify-content: center; padding: clamp(20px, 8vh, 90px) 16px 16px; }
.search-overlay[hidden] { display: none; }
.search-panel { width: 100%; max-width: 600px; background: var(--bg-elev); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow-lg); overflow: hidden; display: flex; flex-direction: column; max-height: 76vh; }
.search-input-row { display: flex; align-items: center; gap: 10px; padding: 14px 16px; border-bottom: 1px solid var(--border); }
.search-input-row input { flex: 1; border: 0; background: transparent; font-size: 1.1rem; color: var(--text); outline: none; }
.search-close { font-size: .72rem; font-weight: 600; color: var(--text-faint); border: 1px solid var(--border); border-radius: var(--radius-xs); padding: 4px 8px; background: var(--surface); }
.search-scope { padding: 8px 16px; font-size: .82rem; color: var(--text-faint); }
.search-results { overflow-y: auto; padding: 4px 8px 10px; }
.search-result { display: flex; align-items: center; gap: 12px; padding: 10px 12px; border-radius: var(--radius-sm); color: var(--text); }
.search-result:hover, .search-result.is-active { background: var(--surface-2); text-decoration: none; }
.search-result-kind { flex: 0 0 auto; font-size: .72rem; font-weight: 600; text-transform: uppercase; letter-spacing: .05em; padding: 3px 7px; border-radius: var(--radius-xs); background: var(--accent-soft); color: var(--accent-strong); }
.search-result-term { font-family: var(--ja); font-weight: 700; font-size: 1.05rem; }
.search-result-reading { font-family: var(--ja); color: var(--text-faint); font-size: .82rem; margin-inline-start: 6px; }
.search-result-meaning { color: var(--text-soft); font-size: .88rem; margin-inline-start: auto; text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 45%; }
.search-empty { padding: 26px 16px; text-align: center; color: var(--text-faint); }

/* ---- Footer -------------------------------------------------------------- */
.site-footer { border-top: 1px solid var(--border); background: var(--bg-elev); margin-top: 48px; }
.footer-grid { display: grid; gap: 32px 40px; grid-template-columns: minmax(170px, 1fr) auto auto minmax(0, 2.4fr); padding-block: 40px; }
.footer-brand { display: flex; flex-direction: column; align-items: flex-start; gap: 12px; }
.footer-tag { color: var(--text-soft); font-size: .88rem; line-height: 1.5; max-width: 26ch; margin: 0; }
.footer-mail { color: var(--text-faint); font-size: .82rem; line-height: 1.5; margin: 0; }
.footer-mail a { color: var(--text-soft); font-weight: 500; }
.footer-mail a:hover { color: var(--accent-strong); }
.footer-support { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; margin: 0; }
.footer-support-link {
  display: inline-flex; align-items: center; gap: 7px; padding: 6px 14px;
  border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface-2);
  color: var(--text-soft); font-size: .82rem; font-weight: 500;
  transition: background-color .18s cubic-bezier(.4, 0, .2, 1), border-color .18s cubic-bezier(.4, 0, .2, 1), color .18s cubic-bezier(.4, 0, .2, 1);
}
.footer-support-link:hover { border-color: var(--border-strong); color: var(--text); text-decoration: none; }
.footer-support-note { color: var(--text-faint); font-size: .82rem; line-height: 1.5; max-width: 30ch; }
.footer-col { display: flex; flex-direction: column; gap: 9px; }
.footer-col a { color: var(--text-soft); font-size: .88rem; font-weight: 500; }
.footer-col a:hover { color: var(--accent-strong); }
.footer-col-title { font-size: .72rem; font-weight: 600; text-transform: uppercase; letter-spacing: .09em; color: var(--text-faint); margin: 0 0 14px; }
.footer-matrix-grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 18px; }
.footer-matrix-col { display: flex; flex-direction: column; gap: 7px; }
/* The level is a display-serif mark here too — never a tinted box, never a
   bold sans label (see DESIGN.md 6.3). */
.footer-matrix-level { font-family: var(--display); font-weight: 400; font-size: 1.35rem; line-height: 1; letter-spacing: -.03em; color: var(--lvl, var(--text)); margin-bottom: 4px; }
.footer-matrix-level:hover { text-decoration: none; opacity: .82; }
.footer-matrix-col a { color: var(--text-soft); font-size: .88rem; }
.footer-matrix-col a:hover { color: var(--accent-strong); }
/* Sibling sites (sisterRow() in lib/layout.js): one row between the footer
   grid and the copyright line. Deliberately quieter than .footer-col so two
   outbound links on 16.5k pages never read as the loudest thing down here. */
.footer-sisters { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px 18px; padding-block: 16px; border-top: 1px solid var(--border); }
.footer-sisters-label { font-size: .72rem; font-weight: 600; text-transform: uppercase; letter-spacing: .09em; color: var(--text-faint); }
.footer-sisters-label:hover { color: var(--accent-strong); }
.footer-sisters nav { display: flex; flex-wrap: wrap; gap: 8px 16px; }
.footer-sisters nav a { display: inline-flex; align-items: baseline; gap: 6px; color: var(--text-soft); font-size: .88rem; font-weight: 500; }
.footer-sisters nav a:hover { color: var(--accent-strong); }
.footer-sisters-lang { color: var(--text-faint); font-size: .72rem; font-weight: 500; }

.footer-note { padding-block: 20px; border-top: 1px solid var(--border); color: var(--text-faint); font-size: .82rem; }

/* ---- Level rail (foot of every level-scoped page) ------------------------ */
/* 6% is the house ratio for a strip wash — the level hues are dark now, so the
   same percentage reads much louder than it used to. */
.level-rail { border-top: 1px solid var(--border); background: color-mix(in srgb, var(--lvl) 6%, var(--bg-elev)); }
.level-rail-inner { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 18px; padding-block: 20px; }
.level-rail-title { margin: 0; font-size: var(--step); color: var(--text-soft); }
.level-rail-title strong { color: var(--lvl, var(--text)); font-weight: 600; }
.level-rail-sub { color: var(--text-faint); font-size: .82rem; }
.level-rail-links { display: flex; flex-wrap: wrap; gap: 8px; margin: 0; padding: 0; list-style: none; margin-inline-start: auto; }
.level-rail-links a {
  display: inline-flex; align-items: center; padding: 6px 14px; border-radius: var(--radius-sm);
  background: var(--surface-2); border: 1px solid var(--border); color: var(--text-soft);
  font-size: .88rem; font-weight: 500;
  transition: background-color .18s cubic-bezier(.4, 0, .2, 1), border-color .18s cubic-bezier(.4, 0, .2, 1), color .18s cubic-bezier(.4, 0, .2, 1);
}
.level-rail-links a:hover { border-color: var(--lvl); color: var(--lvl); text-decoration: none; }
/* --accent-contrast tracks the theme the same way the level hues do: dark on
   the light-mode hue, light on the dark-mode one. A literal #fff would be
   unreadable on the pale dark-mode levels. */
.level-rail-links a.is-current { background: var(--lvl); border-color: transparent; color: var(--accent-contrast); font-weight: 600; }

/* ---- Scroll-top ---------------------------------------------------------- */
.scroll-top {
  position: fixed; right: 18px; bottom: 18px; z-index: 30;
  width: 44px; height: 44px; border-radius: 50%;
  border: 1px solid var(--border-strong); background: var(--bg-elev); color: var(--text);
  box-shadow: var(--shadow-md); font-size: 1.1rem; opacity: 0; pointer-events: none;
  transform: translateY(8px);
  transition: opacity .3s cubic-bezier(.4, 0, .2, 1), transform .3s cubic-bezier(.4, 0, .2, 1),
              border-color .18s cubic-bezier(.4, 0, .2, 1), color .18s cubic-bezier(.4, 0, .2, 1);
}
.scroll-top.is-visible { opacity: 1; pointer-events: auto; transform: translateY(0); }
.scroll-top:hover { border-color: var(--accent); color: var(--text); }
/* ---- Responsive ---------------------------------------------------------- */
@media (max-width: 980px) {
  .footer-grid { grid-template-columns: minmax(160px, 1fr) auto auto; }
  .footer-matrix { grid-column: 1 / -1; }
}
/* The one-line nav only clears the brand and the icons when the header shell is
   at its full 1240px. Below that, drop the nav onto its own full-width row
   instead of letting it wrap mid-list beside the brand. */
@media (max-width: 1239px) {
  .header-inner { flex-wrap: wrap; gap: 6px 12px; padding-top: 8px; padding-bottom: 8px; }
  .header-actions { margin-inline-start: auto; }
  .main-nav { order: 3; width: 100%; margin-inline-start: 0; padding-top: 7px; border-top: 1px solid var(--border); }
}
@media (max-width: 720px) {
  :root { --header-h: 52px; }
  .nav-menu-toggle.is-ready { display: inline-flex; }
  .icon-btn.nav-menu-toggle > .nav-menu-icon { display: block; }
  .main-nav.is-collapsible { display: none; }
  .main-nav.is-collapsible.is-open {
    display: block; max-height: calc(100svh - 64px); overflow-y: auto;
    overscroll-behavior: contain;
  }
  .nav-list { flex-direction: column; flex-wrap: nowrap; gap: 2px; }
  .nav-top { width: 100%; justify-content: space-between; padding: 8px 10px; font-size: .86rem; }
  .nav-cta-link.nav-top { justify-content: center; margin-bottom: 3px; background: var(--accent-soft); }
  /* Search moves out of the header into the menu's first row, drawn like a
     field so it reads as search rather than one more section. */
  .search-trigger { display: none; }
  .nav-search { display: block; }
  .nav-search-btn.nav-top {
    justify-content: flex-start; gap: 8px; margin-bottom: 4px;
    border: 1px solid var(--border-strong); background: var(--surface); color: var(--text-soft);
  }
  /* Everything in the top row a size down. */
  .header-actions { gap: 4px; }
  .brand { font-size: 1.16rem; }
  .icon-btn { width: 34px; height: 34px; }
  .nav-dropdown {
    position: static; min-width: 0; margin: 2px 0 6px; padding: 4px;
    border-radius: var(--radius-sm); box-shadow: none; background: var(--surface);
  }
  .nav-dropdown::before { display: none; }
  .nav-dropdown a { padding: 9px 12px; white-space: normal; }
  .nav-item.has-dropdown:not(.is-open) > .nav-dropdown { display: none; }
  .nav-caret { transition: transform .18s cubic-bezier(.4, 0, .2, 1); }
  .nav-item.is-open > .nav-top .nav-caret { transform: rotate(180deg); }
  /* Label gone, so it goes back to being a circle like the icons beside it. */
  .kofi-btn { width: 34px; padding-inline: 0; border-radius: 50%; }
  .kofi-btn > span:not(.account-dot) { display: none; }
  .detail-pager { grid-template-columns: 1fr 1fr; }
  .pager-up { grid-column: 1 / -1; order: -1; }
  .footer-grid { grid-template-columns: 1fr 1fr; gap: 22px 28px; }
  .footer-brand { grid-column: 1 / -1; }
  .footer-matrix-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .level-rail-links { margin-inline-start: 0; }
  .level-rail-inner { gap: 10px 14px; }
  .sentence-en, .example-en { padding-inline-start: 0; }
}
/* The top row carries the brand plus the round action buttons. Below ~460px that
   only fits if both sides give a little; below ~360px the wordmark drops and
   the logo carries the link home. */
@media (max-width: 460px) {
  .site-header .shell { padding-inline: 14px; }
  .header-inner { gap: 6px 8px; }
  .header-actions { gap: 3px; }
  .icon-btn, .kofi-btn { width: 31px; height: 31px; }
  .brand { font-size: var(--step); gap: 6px; }
  .brand-logo { width: 22px; height: 22px; }
}
/* Signed in, the account button is one more circle in the row, which pushes it
   onto a second line at 375px and leaves it 1px spare at 390px. The logo alone
   carries the link home there, as it already does for everyone below 360px. */
@media (max-width: 399px) {
  .header-inner:has(.header-account-wrap:not([hidden])) .brand-name { display: none; }
}
@media (max-width: 359px) {
  .brand-name { display: none; }
}
/* ---- Affiliate recommendation card --------------------------------------- */
/* Emitted by shopCard() in scripts/build/lib/affiliate.js as the last block
   inside <main> on guide, study-plan and reading-practice pages.

   Styled as a *quiet* card on purpose: a plain surface with a hairline border
   rather than the accent fill .practice-card uses, so it reads as a footnote
   recommendation and never competes with the real study CTAs above it. */

.shop-rec {
  margin: 48px 0 8px;
  padding-top: 24px;
  border-top: 1px solid var(--border);
}

.shop-rec-link {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 15px 17px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-elev);
  color: var(--text);
  transition: border-color .3s cubic-bezier(.4, 0, .2, 1), background-color .3s cubic-bezier(.4, 0, .2, 1);
}
.shop-rec-link:hover,
.shop-rec-link:focus-visible {
  text-decoration: none;
  border-color: var(--border-strong);
  background: var(--surface);

}

.shop-rec-icon {
  flex: 0 0 auto;
  display: grid; place-items: center;
  color: var(--text-faint);
  transition: color .18s cubic-bezier(.4, 0, .2, 1);
}
.shop-rec-icon svg { display: block; width: 24px; height: 24px; }
.shop-rec-link:hover .shop-rec-icon { color: var(--text); }

.shop-rec-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1 1 auto; }
.shop-rec-title { font-size: var(--step); font-weight: 600; line-height: 1.3; }
.shop-rec-desc { font-size: .82rem; color: var(--text-soft); line-height: 1.45; }

.shop-rec-go {
  flex: 0 0 auto;
  font-size: .82rem; font-weight: 500; white-space: nowrap;
  color: var(--accent-strong);
}
.shop-rec-link:hover .shop-rec-go { text-decoration: underline; }

/* FTC disclosure. Small, but never hidden — it has to be readable next to the
   link it qualifies. */
.shop-rec-note {
  margin: 8px 2px 0;
  font-size: .72rem;
  line-height: 1.45;
  color: var(--text-faint);
}

/* Stack on narrow screens: the CTA drops under the copy rather than squeezing
   the description into two-word lines. */
@media (max-width: 560px) {
  .shop-rec-link { flex-wrap: wrap; gap: 12px; }
  .shop-rec-body { flex-basis: calc(100% - 38px); }
  .shop-rec-go { flex-basis: 100%; padding-left: 38px; }
}

/* ---- Footer line --------------------------------------------------------- */
/* Twin of .footer-support: one line under the Ko-fi ask, same visual weight. */
.footer-shop {
  margin: 8px 0 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: .82rem;
}
.footer-shop-link { font-weight: 500; color: var(--text-soft); }
.footer-shop-link:hover { color: var(--accent-strong); }
.footer-shop-note { color: var(--text-faint); font-size: .72rem; }
/* ── Practice games (Cloze Sprint, Word Match, Grammar Drill) ─ */
.cz-game { --cz-ok: var(--st-done); --cz-no: var(--n1); max-width: 640px; margin: 8px auto 0; }
.cz-game[data-empty="true"]::after { content: "No questions available for this level yet."; display: block; color: var(--text-faint); text-align: center; padding: 40px 0; }
.cz-bar { display: flex; justify-content: space-between; align-items: baseline; font-size: .88rem; color: var(--text-soft); margin-bottom: 8px; }
.cz-stat { font-variant-numeric: tabular-nums; font-weight: 600; }
.cz-track { height: 5px; border-radius: 999px; background: var(--surface-2); border: 1px solid var(--border); overflow: hidden; margin-bottom: 18px; }
.cz-track-fill { display: block; height: 100%; background: var(--accent); transition: width .18s cubic-bezier(.4, 0, .2, 1); }
.cz-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 24px 22px; box-shadow: var(--shadow-lg); }
.cz-prompt { font-size: clamp(1.25rem, 3.4vw, 1.7rem); line-height: 1.75; margin: 0 0 22px; }
.cz-label { font-size: .72rem; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--text-faint); margin: 0 0 10px; }
.cz-q { font-size: clamp(1.3rem, 3.6vw, 1.8rem); line-height: 1.5; font-weight: 700; margin: 0 0 6px; }
.cz-q[lang="ja"] { line-height: 1.7; font-weight: 600; }
.cz-sub { color: var(--text-soft); font-size: var(--step); margin: 0 0 20px; }
.cz-q:last-child { margin-bottom: 22px; }
.cz-blank { display: inline-block; min-width: 3.2em; text-align: center; padding: 0 .3em; margin: 0 .1em; border-bottom: 2px dashed var(--accent); color: var(--text-faint); font-weight: 700; }
.cz-blank.is-right { color: var(--cz-ok); border-bottom-color: var(--cz-ok); }
.cz-blank.is-wrong { color: var(--cz-no); border-bottom-color: var(--cz-no); }
.cz-choices { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.cz-choice { display: flex; align-items: center; gap: 10px; padding: 13px 15px; font-size: 1.08rem; text-align: left; background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm); color: var(--text); cursor: pointer; transition: border-color .18s cubic-bezier(.4, 0, .2, 1), background .18s cubic-bezier(.4, 0, .2, 1); }
.cz-choice:hover:not(:disabled) { border-color: var(--accent); }
.cz-choice:disabled { cursor: default; }
.cz-choice.is-right { border-color: var(--cz-ok); background: color-mix(in srgb, var(--cz-ok) 14%, var(--surface)); }
.cz-choice.is-wrong { border-color: var(--cz-no); background: color-mix(in srgb, var(--cz-no) 12%, var(--surface)); }
.cz-key { flex: 0 0 auto; width: 22px; height: 22px; display: grid; place-items: center; font-size: .72rem; font-weight: 600; border-radius: var(--radius-xs); background: var(--surface); border: 1px solid var(--border); color: var(--text-faint); }
.cz-reveal { display: none; margin-top: 12px; padding: 14px 18px 16px; border-radius: var(--radius); background: var(--surface-2); border: 1px solid var(--border); }
.cz-reveal.is-shown { display: block; }
.cz-reveal-top { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 8px; }
.cz-reveal-top .cz-next { margin-top: 0; flex: 0 0 auto; }
.cz-verdict { font-size: .72rem; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; margin-bottom: 6px; }
.cz-reveal-top .cz-verdict { margin-bottom: 0; }
.cz-verdict.ok { color: var(--cz-ok); }
.cz-verdict.no { color: var(--cz-no); }
.cz-answer { font-size: 1.05rem; margin-bottom: 6px; }
.cz-answer strong { font-size: 1.15rem; }
.cz-reading { color: var(--text-faint); font-size: .92em; }
.cz-ex { font-size: 1.05rem; line-height: 1.7; margin: 10px 0 2px; }
.cz-prompt-q { text-align: center; }
.cz-en { display: flex; align-items: center; gap: 8px; color: var(--text-soft); margin: 8px 0 0; }
.cz-audio { flex: 0 0 auto; width: 30px; height: 30px; border-radius: 999px; border: 1px solid var(--border); background: var(--surface); cursor: pointer; font-size: .88rem; }
.cz-audio:hover { border-color: var(--accent); }
.cz-next { margin-top: 14px; padding: 12px 22px; font-size: var(--step); font-weight: 500; border: none; border-radius: var(--radius); background: var(--accent); color: var(--accent-contrast); cursor: pointer; }
.cz-next:hover { background: color-mix(in srgb, var(--accent) 86%, var(--surface)); }
.cz-result { text-align: center; }
.cz-result-score { font-size: 2.6rem; font-weight: 600; margin: 4px 0; }
.cz-result-pct { font-size: 1.1rem; color: var(--text-soft); margin: 0 0 6px; }
.cz-result-meta { color: var(--text-faint); font-size: .88rem; margin: 0 0 6px; font-variant-numeric: tabular-nums; }
.cz-result-best { color: var(--accent-strong); font-size: .88rem; font-weight: 600; margin: 0 0 18px; font-variant-numeric: tabular-nums; }
.cz-result-btns { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; }
.cz-ghost { background: var(--surface-2); color: var(--text); border: 1px solid var(--border); }
.cz-ghost:hover { background: var(--surface-2); border-color: var(--accent); }
.cz-noscript { text-align: center; color: var(--text-faint); padding: 40px 0; }
@media (max-width: 520px) { .cz-choices { grid-template-columns: 1fr; } }

/* ── Practice quiz pages ─────────────────────────────────── */
.quiz { margin-top: 20px; }
.ja-underline { text-decoration: underline; text-decoration-thickness: 2px; text-underline-offset: 3px; text-decoration-color: currentColor; font-weight: 700; }
.quiz-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 28px; }
.quiz-q { padding: 0; }
.quiz-q-text { display: flex; gap: 6px; align-items: baseline; margin: 0 0 10px; font-size: 1.06rem; line-height: 1.7; color: var(--text); }
.quiz-q-num { flex: none; font-weight: 600; color: var(--text); }
.quiz-q-text [lang="ja"] { font-weight: 400; }
.quiz-options { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.quiz-option { display: flex; gap: 8px; align-items: baseline; padding: 2px 0; cursor: pointer; }
.quiz-option-id { flex: none; font-size: var(--step); color: var(--text-soft); }
.quiz-option-id::before { content: '○ '; }
.quiz-option-text { font-size: var(--step); color: var(--text); }
.quiz-option.is-selected .quiz-option-id { color: var(--text); }
.quiz-option.is-selected .quiz-option-id::before { content: '● '; }
.quiz-option.is-correct .quiz-option-id { color: var(--st-done); font-weight: 600; }
.quiz-option.is-correct .quiz-option-id::before { content: '● '; }
.quiz-option.is-wrong .quiz-option-id { color: var(--n1); }
.quiz-option.is-wrong .quiz-option-id::before { content: '● '; }
.quiz-answer { margin-top: 10px; }
.quiz-reveal { cursor: pointer; display: inline-flex; align-items: center; gap: 4px; padding: 0; background: none; border: none; color: var(--accent-strong); font-size: .88rem; font-weight: 600; list-style: none; text-decoration: underline; text-underline-offset: 2px; }
.quiz-reveal::-webkit-details-marker { display: none; }
.quiz-reveal:hover { color: var(--text); }
.quiz-answer[open] .quiz-reveal { color: var(--text-soft); text-decoration: none; }
.quiz-answer-body { margin: 6px 0 0; font-size: var(--step); color: var(--text); }
.quiz-answer-body strong { color: var(--st-done); }
.quiz-explain { margin-top: 8px; padding: 8px 14px; border-left: 2px solid var(--border-strong); font-size: .88rem; color: var(--text-soft); }
.quiz-explain p { margin: 0; }
.quiz-explain p + p { margin-top: 4px; }
.quiz-explain-translation { font-style: italic; }
.quiz-explain-options { margin: 6px 0 0; padding: 0; list-style: none; }
.quiz-explain-options li { margin: 0; }
.quiz-explain-options li + li { margin-top: 4px; }
.quiz-explain-option-id { margin-right: 6px; font-weight: 600; color: var(--text-soft); }
.quiz-explain-option-id::after { content: '.'; }
.quiz-explain-vocab { margin: 6px 0 0; padding: 6px 0 0; list-style: none; border-top: 1px dashed var(--border); }
.quiz-explain-vocab li { margin: 0; }
.quiz-explain-vocab li + li { margin-top: 2px; }
.quiz-explain-vocab-gloss { color: var(--text-soft); }
.quiz-disclaimer { margin: 24px 0 0; font-size: .82rem; color: var(--text-soft); }
.quiz-disclaimer a { color: var(--accent-strong); }

/* ── Practice vs Test mode ───────────────────────────────── */
/* Ships hidden; app.js reveals it so no-JS visitors never see a dead control. */
.quiz-modes[hidden] { display: none; }
.quiz-modes { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px; margin: 4px 0 20px; }
.quiz-modes-seg { display: inline-flex; padding: 3px; gap: 2px; border: 1px solid var(--border); border-radius: 999px; background: var(--surface); }
.quiz-mode-btn { padding: 6px 16px; border: none; border-radius: 999px; background: none; color: var(--text-soft); font-size: .88rem; font-weight: 500; cursor: pointer; transition: background-color .18s cubic-bezier(.4, 0, .2, 1), color .18s cubic-bezier(.4, 0, .2, 1); }
.quiz-mode-btn:hover { color: var(--text); }
.quiz-mode-btn.is-active { background: var(--accent); color: var(--accent-contrast); font-weight: 600; box-shadow: var(--shadow-sm); }

/* Test mode: keep answers embedded for SEO but hidden from the reader until
   they submit; on submit (.is-submitted) reveal the graded explanations. */
.quiz.is-test-mode .quiz-answer { display: none; }
.quiz.is-test-mode.is-submitted .quiz-answer { display: block; }
.quiz.is-test-mode.is-submitted .quiz-reveal { display: none; }
.quiz-q.is-unanswered .quiz-q-text { position: relative; }
.quiz-q.is-unanswered .quiz-q-num::after { content: ' •'; color: var(--accent-strong); }

.quiz-submit-bar { margin: 32px 0 0; display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px; padding-top: 24px; border-top: 1px solid var(--border); }
.quiz-submit-note { margin: 0; font-size: .82rem; color: var(--text-soft); }

.quiz-score { margin: 28px 0 0; display: flex; flex-wrap: wrap; align-items: center; gap: 18px; padding: 24px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); box-shadow: var(--shadow-sm); scroll-margin-top: 90px; }
.quiz-score:focus { outline: none; }
.quiz-score-figure { flex: none; }
/* The score is the one display figure on the page, so it is set in the
   display serif — and it is where the pass / borderline / fail hue lives now
   that the card's border is neutral. */
.quiz-score-pct { font-family: var(--display); font-size: 3.2rem; font-weight: 400; letter-spacing: -.03em; line-height: .9; color: var(--text); font-variant-numeric: tabular-nums; }
.quiz-score-pct small { font-family: var(--sans); font-size: .88rem; font-weight: 500; letter-spacing: 0; color: var(--text-faint); }
.quiz-score.is-high .quiz-score-pct { color: var(--st-done); }
.quiz-score.is-mid .quiz-score-pct { color: var(--n2); }
.quiz-score.is-low .quiz-score-pct { color: var(--n1); }
.quiz-score-body { flex: 1 1 220px; }
.quiz-score-head { margin: 0; font-size: 1.05rem; color: var(--text); }
.quiz-score-sub { margin: 4px 0 0; font-size: .88rem; color: var(--text-soft); }
.quiz-score-actions { margin-top: 12px; }
.quiz-score .btn-ghost { padding: 8px 16px; font-size: .88rem; }

/* ── Reading tests: passage blocks ───────────────────────── */
.quiz-block + .quiz-block { margin-top: 36px; padding-top: 28px; border-top: 1px solid var(--border); }
.quiz-passage { margin: 0 0 18px; padding: 16px 18px; border: 1px solid var(--border); border-left: 2px solid var(--border-strong); border-radius: var(--radius-sm); background: var(--surface); font-size: 1.02rem; line-height: 1.95; }
.quiz-passage p { margin: 0; }
.quiz-passage p + p { margin-top: 10px; }

/* Scanned flyers/posters from the 情報検索 mondai. The scans are black ink on
   white paper, so the frame keeps its own light background in dark mode rather
   than letting a bright rectangle butt straight against a dark page. */
.quiz-figure { margin: 0 0 14px; padding: 8px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--paper); }
/* Image-only flyer blocks have no passage box after the scans, so the last one
   takes over that box's spacing before the question list. */
.quiz-block > .quiz-figure:last-of-type { margin-bottom: 18px; }
.quiz-figure img { display: block; width: 100%; height: auto; border-radius: var(--radius-xs); }

/* ── Listening tests: player, mondai sections, transcripts ─ */
/* The player is the page. It sits at the top in the normal flow, and app.js
   adds .is-pinned once it scrolls out of view so pause/seek stays in reach
   through a 30-question section. Height is reserved by the placeholder app.js
   leaves behind, so pinning doesn't make the page jump. */
.listen-player { margin: 0 0 8px; }
.listen-player-inner { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 18px; padding: 14px 16px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); box-shadow: var(--shadow-sm); }
.listen-player-meta { flex: 0 0 auto; display: flex; flex-direction: column; gap: 2px; }
.listen-player-title { font-size: var(--step); font-weight: 600; color: var(--text); }
.listen-player-sub { font-size: .82rem; color: var(--text-soft); }
.listen-audio { flex: 1 1 260px; width: 100%; min-width: 0; height: 40px; }

/* Sits directly under the sticky site header (z-index 40, top 0) rather than
   over it — one below in the stack, one header-height down. */
.listen-player.is-pinned { position: fixed; top: var(--header-h); left: 0; right: 0; z-index: 39; margin: 0; padding: 8px 0; background: var(--bg); border-bottom: 1px solid var(--border); box-shadow: var(--shadow-sm); }
.listen-player.is-pinned .listen-player-inner { max-width: 1180px; margin: 0 auto; padding: 8px 20px; border: none; border-radius: 0; background: none; box-shadow: none; }
.listen-player.is-pinned .listen-player-sub { display: none; }
@media (max-width: 700px) {
  .listen-player-meta { flex: 1 1 100%; }
  .listen-player.is-pinned .listen-player-meta { display: none; }
}

.listen-howto { margin: 14px 0 4px; font-size: .88rem; color: var(--text-soft); }

/* Mondai heading: 問題N plus the task's Japanese name and its English gloss,
   then the examiner's spoken instruction exactly as the paper prints it. */
.quiz-mondai-head { margin: 0 0 20px; padding: 12px 14px; border-radius: var(--radius-sm); background: var(--surface); }
.quiz-mondai-title { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; margin: 0; font-size: clamp(1.3rem, 2.6vw, 1.6rem); line-height: 1.15; letter-spacing: -.02em; color: var(--text); }
.quiz-mondai-tag { font-size: var(--step); font-weight: 600; color: var(--accent-strong); }
.quiz-mondai-en { font-size: .82rem; font-weight: 500; color: var(--text-soft); }
.quiz-mondai-instruction { margin: 8px 0 0; font-size: .88rem; line-height: 1.85; color: var(--text-soft); }

/* Picture questions (発話表現, and the N4/N5 mondai whose four options are
   drawings). The scan carries the whole question, so it gets more room than a
   reading figure but stays narrow enough to sit beside the option list. */
.quiz-figure-listening { max-width: 420px; margin: 0 0 12px; }

.quiz-spoken-note { margin: 0 0 8px; font-size: .82rem; font-style: italic; color: var(--text-faint); }
.quiz-nokey-note { margin: 10px 0 0; font-size: .82rem; color: var(--text-faint); }

.quiz-transcript { margin-top: 10px; }
.quiz-transcript > summary { cursor: pointer; display: inline-flex; align-items: center; gap: 4px; padding: 0; color: var(--text-soft); font-size: .88rem; font-weight: 600; list-style: none; text-decoration: underline; text-underline-offset: 2px; }
.quiz-transcript > summary::-webkit-details-marker { display: none; }
.quiz-transcript > summary:hover { color: var(--text); }
.quiz-transcript-body { margin-top: 8px; padding: 10px 14px; border-left: 2px solid var(--border-strong); border-radius: 0 var(--radius-sm) var(--radius-sm) 0; background: var(--surface); font-size: var(--step); line-height: 1.9; }
.quiz-transcript-body p { margin: 0; }
.quiz-transcript-body p + p { margin-top: 6px; }

/* Test mode hides the answers until submit; a transcript is an answer too — it
   spells out every word of the audio. */
.quiz.is-test-mode .quiz-transcript { display: none; }
.quiz.is-test-mode.is-submitted .quiz-transcript { display: block; }

/* ── Practice quiz sidebar layout ────────────────────────── */
.quiz-layout,
.page-sidebar-layout { display: block; }
.quiz-sidebar { display: none; }

@media (min-width: 900px) {
  .quiz-layout,
  .page-sidebar-layout { display: grid; grid-template-columns: 1fr 240px; gap: 40px; align-items: start; }
  .quiz-sidebar {
    display: flex; flex-direction: column; gap: 24px;
    position: sticky; top: calc(var(--header-h) + 16px);
    max-height: calc(100vh - var(--header-h) - 48px);
    overflow-y: auto;
  }
}

.qs-block { display: flex; flex-direction: column; gap: 6px; }
.qs-heading {
  font-family: var(--sans); font-size: .72rem; font-weight: 600; letter-spacing: .08em;
  text-transform: uppercase; color: var(--text-faint); margin: 0;
  padding-bottom: 6px; border-bottom: 1px solid var(--border);
}
.qs-links { display: flex; flex-direction: column; gap: 2px; }
.qs-links a {
  display: flex; justify-content: space-between; align-items: baseline; gap: 4px;
  padding: 4px 6px; border-radius: var(--radius-xs); font-size: .88rem; color: var(--text-soft);
  text-decoration: none; transition: background .18s cubic-bezier(.4, 0, .2, 1), color .18s cubic-bezier(.4, 0, .2, 1);
}
.qs-links a:hover { background: var(--surface-2); color: var(--text); text-decoration: none; }
.qs-links a.is-current { color: var(--accent-strong); font-weight: 600; background: var(--accent-soft); }
.qs-count { font-size: .72rem; color: var(--text-faint); font-weight: 400; white-space: nowrap; }

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


/* ---- Graded reading ------------------------------------------------------ */
/* /reading/ — level lists of passages, and the passage page itself. The
   passage box reuses .quiz-passage from 22-practice-reading.css and only opens
   up the leading, since these are read end to end rather than scanned for an
   answer. */

.reading-list { list-style: none; margin: 0 0 28px; padding: 0; display: grid; gap: 12px; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); }
.reading-card > a {
  display: flex; flex-direction: column; gap: 6px; height: 100%; padding: 14px 16px;
  border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface);
  color: var(--text); box-shadow: var(--shadow-sm);
  transition: box-shadow .3s cubic-bezier(.4, 0, .2, 1), border-color .3s cubic-bezier(.4, 0, .2, 1);
}
.reading-card > a:hover { text-decoration: none; background: var(--accent-soft); box-shadow: var(--shadow-md); border-color: var(--border-strong); }
.rc-title { font-family: var(--ja); font-size: 1.05rem; font-weight: 700; }
.rc-preview { font-family: var(--ja); font-size: .88rem; color: var(--text-soft); line-height: 1.7; }
.rc-meta { margin-top: auto; font-size: .82rem; font-weight: 600; color: var(--accent-strong); }

.reading-text { font-family: var(--ja); line-height: 2.1; }
.reading-text p + p { margin-top: 14px; }

.reading-audio {
  display: flex; align-items: center; gap: 10px; margin: 0 0 12px;
  color: var(--text-soft); font-size: .88rem; font-weight: 600;
}
.reading-audio .audio-btn { margin-top: 0; }

/* Auth and the private passage arrive independently. While either one is in
   flight, reserve the passage's real shape instead of replacing it with a
   small status card that makes the page jump when the text arrives. */
.reading-access-loading {
  min-height: 350px; margin: 24px 0; cursor: wait;
}
.reading-loading-skeleton { display: grid; gap: 18px; }
.reading-loading-shape {
  display: block; border-radius: var(--radius-xs);
  background: linear-gradient(100deg, var(--surface-2) 22%, var(--surface) 40%, var(--surface-2) 58%);
  background-size: 240% 100%;
  animation: reading-loading-shimmer 1.35s ease-in-out infinite;
}
.reading-loading-audio { display: flex; align-items: center; gap: 10px; }
.reading-loading-circle { width: 30px; height: 30px; flex: none; border-radius: 50%; }
.reading-loading-label { width: min(10rem, 42%); height: 11px; }
.reading-loading-passage {
  display: grid; gap: 13px; padding: 22px 20px;
  border: 1px solid var(--border); border-left: 2px solid var(--border-strong);
  border-radius: var(--radius-sm); background: var(--surface);
}
.reading-loading-line { width: 94%; height: 13px; }
.reading-loading-line.is-wide { width: 100%; }
.reading-loading-line.is-medium { width: 82%; }
.reading-loading-line.is-short { width: 56%; }
.reading-loading-translation { width: 100%; height: 48px; border: 1px solid var(--border); border-radius: var(--radius-sm); }

@keyframes reading-loading-shimmer {
  from { background-position: 100% 0; }
  to { background-position: -100% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .reading-loading-shape { animation: none; background: var(--surface-2); }
}

.reading-translation { margin: 0 0 18px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--bg-elev); }
.reading-translation > summary { cursor: pointer; padding: 12px 16px; font-weight: 600; color: var(--accent-strong); }
.reading-translation[open] > summary { border-bottom: 1px solid var(--border); }
.reading-translation-body { padding: 14px 16px; }
.reading-translation-body p { margin: 0; }
.reading-translation-body p + p { margin-top: 10px; }

.reading-gloss { margin: 0 0 18px; }
.reading-gloss h2 { font-size: clamp(1.3rem, 2.6vw, 1.6rem); line-height: 1.15; letter-spacing: -.02em; margin: 0 0 6px; }
.reading-gloss-note { margin: 0 0 12px; font-size: .88rem; color: var(--text-soft); }
.rg-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.rg-list li {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 6px 10px;
  padding: 9px 12px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface);
}
.rg-word { font-family: var(--ja); font-size: 1.05rem; font-weight: 700; }
.rg-reading { font-family: var(--ja); font-size: .88rem; color: var(--text-soft); }
.rg-meaning { font-size: .88rem; }
.rg-meaning.is-missing { color: var(--text-faint); font-style: italic; }
.rg-tag { margin-left: auto; font-size: .72rem; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--accent-strong); background: var(--accent-soft); border-radius: 999px; padding: 2px 8px; }

.reading-source { margin: 0 0 20px; font-size: .88rem; color: var(--text-soft); }

/* ---- Word help inside a passage ------------------------------------------ */
/* Every identified word is its own element carrying a reading and a meaning.
   Resting state is invisible on purpose: a passage full of underlines is a
   link farm, not something to read. The mark appears under the pointer, and
   the card itself is painted by initWordPop in app.js. */

.reading-text .rw { color: inherit; text-decoration: none; border-radius: 3px; box-decoration-break: clone; }
.reading-text a.rw:hover,
.reading-text a.rw:focus-visible,
.reading-text a.rw.is-open,
.reading-text span.rw:hover,
.reading-text span.rw.is-open {
  background: var(--accent-soft);
  box-shadow: 0 1px 0 var(--accent-strong);
  cursor: pointer;
}
.reading-text span.rw:hover, .reading-text span.rw.is-open { box-shadow: 0 1px 0 var(--border-strong); cursor: help; }
.reading-text a.rw:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: 2px; }

.word-pop {
  position: absolute; z-index: 60; max-width: min(280px, calc(100vw - 24px));
  padding: 10px 12px; border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--text); box-shadow: var(--shadow-lg);
  font-family: var(--sans); font-size: .88rem; line-height: 1.45;
}
.word-pop[hidden] { display: none; }
.wp-word { font-family: var(--ja); font-size: 1.05rem; font-weight: 700; }
.wp-reading { font-family: var(--ja); font-size: .85rem; color: var(--text-soft); margin-left: 6px; }
.wp-meaning { margin: 4px 0 0; }
.wp-go { display: block; margin-top: 6px; font-size: .78rem; font-weight: 600; color: var(--accent-strong); }

.reading-translation-note { margin-top: 10px; font-size: .82rem; color: var(--text-faint); }

/* The word-help switch, sat in the page's link row and shaped like the pills
   beside it. Ships hidden; app.js unhides it on a passage that has word data. */
.wordpop-toggle {
  display: inline-flex; align-items: center; gap: 7px; cursor: pointer;
  padding: 6px 14px; border-radius: var(--radius-sm); background: var(--surface-2);
  border: 1px solid var(--border); color: var(--text-soft); font-size: .88rem; font-weight: 500;
  transition: background-color .18s cubic-bezier(.4, 0, .2, 1), border-color .18s cubic-bezier(.4, 0, .2, 1), color .18s cubic-bezier(.4, 0, .2, 1);
}
.wordpop-toggle[hidden] { display: none; }
.wordpop-toggle:hover { border-color: var(--border-strong); color: var(--text); }
.wordpop-toggle[aria-pressed="true"] { background: var(--accent-soft); border-color: var(--accent); color: var(--accent-strong); }
/* Switched off, a plain word is not a target: no mark, no help cursor. Links
   keep their hover, because clicking one still opens its page. */
.reading-text.is-nopop span.rw:hover { background: none; box-shadow: none; cursor: auto; }
/* ── Kanji Flashcards ──────────────────────────────────── */
.fc-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 22px;
  max-width: 760px;
  margin: 20px auto 0;
}
.fc-card-wrap {
  perspective: 1000px;
  width: 100%;
  aspect-ratio: 16 / 10.5;
  min-height: 420px;
}
.fc-card {
  width: 100%;
  height: 100%;
  position: relative;
  transform-style: preserve-3d;
  transition: 0 .18s cubic-bezier(.4, 0, .2, 1), 0.2 .18s cubic-bezier(.4, 0, .2, 1), 1) .18s cubic-bezier(.4, 0, .2, 1);
  cursor: pointer;
}
.fc-card.is-flipped {
  transform: rotateY(180deg);
}
.fc-face {
  position: absolute;
  width: 100%;
  height: 100%;
  backface-visibility: hidden;
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-lg);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  padding: 36px;
}
.fc-front {
  background: var(--surface);
  color: var(--text);
  font-family: var(--ja);
  font-size: clamp(5.5rem, 16vw, 8.5rem);
  font-weight: 700;
}
.fc-front::after {
  content: "Tap or Space to Flip";
  position: absolute;
  bottom: 18px;
  font-family: var(--sans);
  font-size: .72rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-faint);
  font-weight: 600;
}
.fc-back {
  background: var(--surface);
  color: var(--text);
  transform: rotateY(180deg);
  overflow-y: auto;
  align-items: stretch;
  justify-content: flex-start;
  gap: 16px;
}
.fc-back-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-bottom: 1px solid var(--border);
  padding-bottom: 10px;
}
.fc-back-glyph {
  font-family: var(--ja);
  font-size: 2.8rem;
  font-weight: 700;
}
.fc-back-meanings {
  font-size: 1.25rem;
  font-weight: 600;
  color: var(--accent-strong);
  margin: 0;
}
.fc-back-readings {
  display: flex;
  flex-direction: column;
  gap: 8px;
  font-size: var(--step);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 14px 18px;
}
.fc-back-reading-row {
  display: flex;
  gap: 12px;
}
.fc-back-reading-label {
  font-weight: 600;
  color: var(--text-soft);
  flex: 0 0 85px;
}
.fc-back-reading-val {
  font-family: var(--ja);
  color: var(--text);
  font-size: 1.05rem;
}
.fc-back-examples {
  border-top: 1px solid var(--border);
  padding-top: 14px;
  margin-top: auto;
}
.fc-back-ex-title {
  font-size: .72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-faint);
  margin: 0 0 8px;
}
.fc-back-ex-list {
  display: flex;
  flex-direction: column;
  gap: 0;
  font-size: .88rem;
}
.fc-back-ex-item {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 8px 0;
  border-bottom: 1px dashed var(--border);
}
.fc-back-ex-item:last-child {
  border-bottom: none;
  padding-bottom: 0;
}
.fc-back-ex-item:first-child {
  padding-top: 0;
}
.fc-back-ex-item .audio-btn,
.fc-ex-line .audio-btn {
  width: 26px; height: 26px; margin-top: 0; align-self: center;
}
.fc-back-ex-item .audio-btn svg,
.fc-ex-line .audio-btn svg { width: 11px; height: 11px; }
.fc-back-ex-word {
  font-family: var(--ja);
  font-weight: 700;
  font-size: 1.05rem;
  flex-shrink: 0;
  white-space: nowrap;
}
.fc-back-ex-reading {
  font-family: var(--ja);
  color: var(--text-faint);
  flex-shrink: 0;
  white-space: nowrap;
}
.fc-back-ex-meaning {
  color: var(--text-soft);
  margin-inline-start: auto;
  font-size: .88rem;
  text-align: right;
  padding-left: 12px;
}
/* Words and grammar show one sentence; the rest are on the item's page. Sits
   under the sentence as a quiet link rather than a button, because it leaves
   the card and the grade buttons below it are the ones meant to be pressed. */
.fc-back-ex-more {
  display: inline-block;
  margin-top: 10px;
  font-size: .82rem;
  font-weight: 600;
}
.fc-controls {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  gap: 12px;
  margin-top: 10px;
}
.fc-btn {
  padding: 12px 22px;
  font-size: .88rem;
  font-weight: 500;
  border-radius: var(--radius);
  border: 1px solid var(--border-strong);
  background: var(--surface);
  color: var(--text-soft);
  transition: background-color .18s cubic-bezier(.4, 0, .2, 1), border-color .18s cubic-bezier(.4, 0, .2, 1), color .18s cubic-bezier(.4, 0, .2, 1);
}
.fc-btn:hover {
  border-color: var(--accent);
  color: var(--accent-strong);
}
.fc-btn-primary {
  background: var(--accent);
  color: var(--accent-contrast);
  border-color: transparent;
}
.fc-btn-primary:hover {
  background: color-mix(in srgb, var(--accent) 86%, var(--surface));
  color: var(--accent-contrast);
}
.fc-bar {
  display: flex;
  justify-content: space-between;
  width: 100%;
  font-size: .82rem;
  color: var(--text-soft);
  margin-bottom: -10px;
}

/* ── Flashcard review (the progress-control "Flashcards" button + modal) ── */
.pc-flashcards { display: inline-flex; align-items: center; gap: 4px; }
.pc-flashcards svg { flex: 0 0 auto; }
/* As a link (kanji detail pages → SM-2 deck) it must still read as a pc-btn */
a.pc-flashcards { color: var(--text-soft); }
a.pc-flashcards:hover { text-decoration: none; color: var(--accent-strong); }

.fc-overlay {
  position: fixed; inset: 0; z-index: 70;
  background: var(--scrim); backdrop-filter: blur(4px);
  display: flex; align-items: flex-start; justify-content: center;
  padding: clamp(14px, 5vh, 56px) 14px 14px; overflow-y: auto;
}
.fc-overlay[hidden] { display: none; }
.fc-modal {
  width: 100%; max-width: 720px; background: var(--bg-elev);
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg); display: flex; flex-direction: column; overflow: hidden;
}
.fc-modal-head { display: flex; align-items: center; gap: 12px; padding: 12px 16px; border-bottom: 1px solid var(--border); }
.fc-modal-title { font-weight: 600; font-size: 1rem; }
.fc-viewed-toggle { display: inline-flex; align-items: center; gap: 6px; margin-left: auto; font-size: .82rem; font-weight: 600; color: var(--text-soft); cursor: pointer; }
.fc-viewed-toggle[hidden] { display: none; }
.fc-viewed-toggle input { accent-color: var(--accent); width: 15px; height: 15px; }
.fc-modal-close { border: 1px solid var(--border-strong); background: var(--surface); color: var(--text-soft); border-radius: var(--radius-sm); padding: 3px 9px; font-size: .72rem; font-weight: 600; }
.fc-modal-close:hover { border-color: var(--accent); color: var(--accent-strong); }
.fc-modal-body { padding: 16px; display: flex; flex-direction: column; gap: 14px; }
.fc-modal-body[hidden] { display: none; }

/* The modal is the only card: flatten the 3D flip-card so there is no nested
   card chrome and no flip animation — just swap front/back via .is-flipped. */
.fc-overlay .fc-bar { margin: 0; }
.fc-overlay .cz-track { margin: 0; }
.fc-overlay .fc-controls { margin: 0; }
.fc-overlay .fc-card-wrap { perspective: none; aspect-ratio: auto; min-height: 0; height: auto; }
.fc-overlay .fc-card { transform: none; transition: none; transform-style: flat; height: auto; cursor: pointer; }
.fc-overlay .fc-card.is-flipped { transform: none; }
/* Front and back share one fixed height so the card never resizes on flip; an
   overflowing back face scrolls inside that height instead of growing the card. */
.fc-overlay .fc-face {
  position: static; width: 100%; height: clamp(300px, 50vh, 460px); padding: 26px;
  border: 1px solid var(--border); box-shadow: var(--shadow-sm);
  border-radius: var(--radius-lg); background: var(--surface);
  transform: none; backface-visibility: visible;
}
.fc-overlay .fc-front {
  display: flex; align-items: center; justify-content: center;
  font-size: inherit; gap: 14px;
  background: linear-gradient(180deg, var(--surface) 0%, var(--surface-2) 160%);
}
.fc-overlay .fc-card:hover .fc-front { border-color: var(--border-strong); }
.fc-overlay .fc-front::after { display: none; }
.fc-overlay .fc-back { display: none; overflow-y: auto; }
.fc-overlay .fc-card.is-flipped .fc-front { display: none; }
.fc-overlay .fc-card.is-flipped .fc-back { display: flex; }
.fc-front-main { font-family: var(--ja); font-weight: 700; font-size: clamp(3rem, 11vw, 5.6rem); line-height: 1.12; text-align: center; }

/* Back face: meaning, a details block (readings / strokes / examples), then the
   grade controls pinned to the bottom. */
.fc-back-details { display: flex; flex-direction: column; gap: 14px; }
.fc-back-details:empty { display: none; }
.fc-ex-sentence { flex-direction: column; align-items: flex-start; gap: 3px; }
/* Play button and Japanese on one line, the translation under both. */
.fc-ex-line { display: flex; align-items: center; gap: 10px; }
.fc-ex-ja { font-family: var(--ja); font-weight: 600; font-size: 1rem; }
.fc-ex-en { color: var(--text-soft); font-size: .88rem; }

/* Controls sit below the card so they never flip or shift: a single Reveal
   button while the front shows, then the verdict row once it is flipped. */
.fc-overlay .fc-controls { margin: 0; display: block; }
.fc-count { font-weight: 600; color: var(--text); }
.fc-hint { color: var(--text-faint); }

.fc-reveal { display: flex; justify-content: center; }
.fc-reveal[hidden] { display: none; }
.fc-btn-reveal { min-width: 240px; font-size: 1rem; padding: 14px 38px; }

.fc-grade { display: flex; flex-wrap: wrap; align-items: stretch; gap: 10px; }
.fc-grade[hidden] { display: none; }
.fc-grade-btn {
  flex: 1 1 130px; display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 3px; padding: 12px 16px; border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong); background: var(--surface); color: var(--text);
  transition: background-color .18s cubic-bezier(.4, 0, .2, 1), border-color .18s cubic-bezier(.4, 0, .2, 1), color .18s cubic-bezier(.4, 0, .2, 1);
}
.fc-grade-btn:hover { }
.fc-grade-btn:active { transform: translateY(0); }
.fc-grade-label { font-weight: 600; font-size: var(--step); }
.fc-grade-hint { font-size: .72rem; color: var(--text-faint); font-weight: 600; }
.fc-grade-learn { border-color: color-mix(in srgb, var(--n4) 55%, var(--border)); }
.fc-grade-learn:hover, .fc-grade-learn:focus-visible {
  background: color-mix(in srgb, var(--n4) 12%, var(--surface)); border-color: var(--n4); color: var(--n4);
}
.fc-grade-learn:hover .fc-grade-hint, .fc-grade-learn:focus-visible .fc-grade-hint { color: var(--n4); }
.fc-grade-good { border-color: color-mix(in srgb, var(--n3) 55%, var(--border)); }
.fc-grade-good:hover, .fc-grade-good:focus-visible {
  background: color-mix(in srgb, var(--n3) 12%, var(--surface)); border-color: var(--n3); color: var(--n3);
}
.fc-grade-good:hover .fc-grade-hint, .fc-grade-good:focus-visible .fc-grade-hint { color: var(--n3); }
.fc-grade-done { border-color: color-mix(in srgb, var(--n5) 55%, var(--border)); }
.fc-grade-done:hover, .fc-grade-done:focus-visible {
  background: color-mix(in srgb, var(--n5) 12%, var(--surface)); border-color: var(--n5); color: var(--n5);
}
.fc-grade-done:hover .fc-grade-hint, .fc-grade-done:focus-visible .fc-grade-hint { color: var(--n5); }

.fc-empty .fc-btn-primary { margin-top: 16px; }

.fc-empty { padding: 40px 24px; text-align: center; }
.fc-empty[hidden] { display: none; }
.fc-empty-title { font-weight: 600; font-size: 1.05rem; margin: 0 0 8px; }
.fc-empty-note { color: var(--text-soft); font-size: .88rem; margin: 0 auto; max-width: 360px; }


/* ── Phones ────────────────────────────────────────────────
   An example row puts the meaning in a right-aligned column across the card
   from the word it belongs to. At a phone's width that column has no room to
   hold a line, so it wrapped into a ragged block that read as its own thing.
   It goes under the word instead, left-aligned: one entry, one paragraph. */
@media (max-width: 560px) {
  .fc-back-ex-item { flex-wrap: wrap; gap: 2px 7px; padding: 7px 0; }
  .fc-back-ex-word { font-size: 1rem; }
  .fc-back-ex-meaning { flex: 1 1 100%; margin-inline-start: 0; padding-left: 0; text-align: left; font-size: .82rem; }
  .fc-back-readings { gap: 6px; padding: 11px 13px; }
  .fc-back-reading-label { flex-basis: 72px; }
}
/* ── Spaced-repetition decks (/practice/flashcards/ and the per-section pages) ──
   Reuses the fc-card flip + back-face styles; everything ak-* is the
   scheduler chrome: deck header, queue counts, graded answer buttons. */
.ak { gap: 16px; margin-bottom: 24px; }
.ak-top { display: flex; justify-content: space-between; align-items: center; width: 100%; gap: 12px; flex-wrap: wrap; }
.ak-deck { margin: 0; display: flex; flex-direction: column; }
.ak-deck-name { font-weight: 600; font-size: 1.05rem; }
.ak-deck-sub { color: var(--text-faint); font-size: .82rem; font-weight: 600; }
/* Shown only while a schedule is running for this level: the deck and the
   day share one progress store, and this is the only place that says so. */
.ak-plan-note {
  width: 100%; margin: -8px 0 0; color: var(--text-faint);
  font-size: .82rem; font-weight: 600;
}
.ak-plan-note[hidden] { display: none; }
.ak-tools { display: flex; align-items: center; gap: 10px; }
.ak-tool {
  border: 1px solid var(--border-strong); background: var(--surface); color: var(--text-soft);
  border-radius: var(--radius-sm); padding: 5px 12px; font-size: .82rem; font-weight: 600;
}
.ak-tool:hover { border-color: var(--accent); color: var(--accent-strong); }
.ak-tool[hidden] { display: none; }
.ak-newlimit-label { font-size: .82rem; font-weight: 600; color: var(--text-soft); display: inline-flex; align-items: center; gap: 6px; }
.ak-newlimit {
  border: 1px solid var(--border-strong); background: var(--surface); color: var(--text);
  border-radius: var(--radius-sm); padding: 4px 8px; font-size: .82rem; font-weight: 600;
}

/* Open layout — no card chrome. The "card" is just a show/hide region on the
   page background, Anki-style: question view, then header + divider + answer. */
.ak-stage { width: 100%; }
.ak .fc-card { position: relative; height: auto; transform: none; transition: none; transform-style: flat; cursor: pointer; }
.ak .fc-card.is-flipped { transform: none; cursor: default; }
.ak .fc-card[hidden] { display: none; }
.ak .fc-card:focus-visible { outline: none; }
.ak .fc-face {
  position: static; width: 100%; height: auto; padding: 0;
  border: 0; border-radius: 0; background: transparent; box-shadow: none;
  backface-visibility: visible; transform: none;
}
.ak .fc-front {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 22px; min-height: 44vh; font-size: clamp(5.5rem, 17vw, 9.5rem);
}
.ak .fc-front::after { display: none; }
.ak .fc-back { display: none; }
.ak .fc-card.is-flipped .fc-front { display: none; }
.ak .fc-card.is-flipped .fc-back {
  display: flex; flex-direction: column; align-items: center;
  gap: 18px; padding: 8px 0 4px; overflow: visible;
}
/* Answer view: centered and open, a divider under the header like Anki. */
.ak .fc-back-header {
  flex-direction: column; align-items: center; gap: 10px;
  width: min(620px, 100%); margin: 0 auto;
  padding-bottom: 18px; border-bottom: 1px solid var(--border);
}
.ak .fc-back-glyph { font-size: clamp(3.2rem, 9vw, 4.6rem); line-height: 1.1; }
.ak-back-side { display: flex; align-items: center; gap: 10px; justify-content: center; }
/* The clip sits with the pencil and the page link, on the answer side only:
   on the front it would say the reading the card is asking for. */
.ak-back-side .audio-btn { width: 34px; height: 34px; margin-top: 0; }
.ak-back-side .audio-btn svg { width: 14px; height: 14px; }
.ak-page-link { font-size: .82rem; font-weight: 600; white-space: nowrap; }
.ak .fc-back-meanings { font-size: clamp(1.25rem, 3vw, 1.6rem); text-align: center; }
.ak .fc-back-readings {
  background: transparent; border: 0; padding: 0;
  flex-direction: row; justify-content: center; gap: 36px; flex-wrap: wrap;
}
.ak .fc-back-reading-row { flex-direction: column; align-items: center; gap: 3px; }
.ak .fc-back-reading-label { flex: none; font-size: .72rem; text-transform: uppercase; letter-spacing: .07em; color: var(--text-faint); }
.ak .fc-back-reading-val { font-size: 1.15rem; text-align: center; }
.ak .fc-back-examples { width: min(620px, 100%); border-top: 1px solid var(--border); margin-top: 2px; padding-top: 14px; }

/* Word/grammar decks (.ak-words): multi-character fronts need smaller type
   than a lone kanji glyph; the back term shrinks to match and the vocab
   reading sits under it. */
.ak-words .fc-front { font-size: clamp(2.4rem, 9vw, 4.8rem); }
.ak-words .fc-back-glyph { font-size: clamp(2rem, 6vw, 3.2rem); }
.ak-back-reading { font-family: var(--ja); font-size: 1.2rem; color: var(--text-soft); }
.ak-back-reading[hidden] { display: none; }

/* Grammar is a phrase, not a glyph. It wraps, so it takes type a word front
   would overflow at, on both faces. */
.ak[data-ak-kind="g"] .fc-front { font-size: clamp(1.8rem, 6vw, 3.2rem); line-height: 1.3; text-align: center; }
.ak[data-ak-kind="g"] .fc-back-glyph { font-size: clamp(1.5rem, 4.5vw, 2.4rem); line-height: 1.3; text-align: center; }

.ak-counts { display: flex; align-items: center; gap: 10px; margin: 0; font-size: .82rem; font-weight: 600; color: var(--text-soft); }
.ak-count b { font-weight: 600; font-variant-numeric: tabular-nums; }
.ak-count-new b { color: var(--n4); }
.ak-count-learn b { color: var(--n1); }
.ak-count-due b { color: var(--n5); }
.ak-count-sep { color: var(--text-faint); }
.ak-counts[data-ak-active="new"] .ak-count-new,
.ak-counts[data-ak-active="learn"] .ak-count-learn,
.ak-counts[data-ak-active="review"] .ak-count-due { text-decoration: underline; text-underline-offset: 4px; }

.ak-answer {
  width: 100%; display: flex; flex-direction: column; align-items: center;
  gap: 14px; background: var(--bg);
}
/* On the deck pages the bar is fixed to the window, Anki-style: Show answer
   and the four grades sit in the same place on every card, whether the back is
   one line or a screen of examples. Sticky could not do it, because the bar is
   the last thing in .ak and so had no room inside its own container to travel.
   The study day moves these controls into its card footer, out of .ak, and
   puts them back in flow (53-study-plan.css). */
.ak > .ak-answer {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 20;
  box-sizing: border-box;
  padding: 12px 16px calc(14px + env(safe-area-inset-bottom, 0px));
  box-shadow: 0 -1px 0 var(--border);
}
/* Room under the deck for the bar it sits over. */
.ak:has(> .ak-answer:not([hidden])) { padding-bottom: 150px; }
/* Show answer and the four grades are one control in two states, so they sit
   in one grid cell rather than following each other: the box is always the
   grade strip's size, whichever face the card is on, and Show answer stretches
   to fill it. Nothing in the footer moves when a card turns. */
.ak-choice { display: grid; width: 100%; max-width: 640px; }
.ak-choice > .ak-show, .ak-choice > .ak-grades { grid-area: 1 / 1; }
/* The inactive one keeps its place and gives up its ink. visibility takes it
   out of the tab order and the accessibility tree too, so `hidden` still
   means hidden to everything but the layout. */
.ak-choice > .ak-show[hidden] { display: inline-flex; }
.ak-choice > .ak-grades[hidden] { display: grid; }
.ak-choice > [hidden] { visibility: hidden; }
.ak-show {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 240px; font-size: 1rem; padding: 13px 38px;
}
.ak-show[hidden] { display: none; }
/* Four equal grades, then Done on the end at whatever width its word needs. */
.ak-grades { display: grid; grid-template-columns: repeat(4, 1fr) auto; gap: 10px; width: 100%; }
.ak-grades[hidden] { display: none; }
.ak-grade {
  position: relative; display: flex; flex-direction: column; align-items: center;
  gap: 2px; padding: 10px 8px 12px; border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong); background: var(--surface); color: var(--text);
  transition: background-color .18s cubic-bezier(.4, 0, .2, 1), border-color .18s cubic-bezier(.4, 0, .2, 1), color .18s cubic-bezier(.4, 0, .2, 1);
}
.ak-grade:hover { }
.ak-grade:active { transform: translateY(0); }
.ak-grade-ivl { font-size: .72rem; font-weight: 600; color: var(--text-faint); font-variant-numeric: tabular-nums; }
.ak-grade-name { font-size: var(--step); font-weight: 600; }
.ak-grade-key {
  position: absolute; top: 6px; right: 8px; font-size: .72rem; font-weight: 600;
  color: var(--text-faint); border: 1px solid var(--border); border-radius: var(--radius-xs); padding: 0 4px;
}
.ak-grade-again { border-color: color-mix(in srgb, var(--n1) 55%, var(--border)); }
.ak-grade-again:hover, .ak-grade-again:focus-visible { background: color-mix(in srgb, var(--n1) 12%, var(--surface)); border-color: var(--n1); color: var(--n1); }
.ak-grade-hard { border-color: color-mix(in srgb, var(--n2) 55%, var(--border)); }
.ak-grade-hard:hover, .ak-grade-hard:focus-visible { background: color-mix(in srgb, var(--n2) 12%, var(--surface)); border-color: var(--n2); color: var(--n2); }
.ak-grade-good { border-color: color-mix(in srgb, var(--n5) 55%, var(--border)); }
.ak-grade-good:hover, .ak-grade-good:focus-visible { background: color-mix(in srgb, var(--n5) 12%, var(--surface)); border-color: var(--n5); color: var(--n5); }
.ak-grade-easy { border-color: color-mix(in srgb, var(--n4) 55%, var(--border)); }
.ak-grade-easy:hover, .ak-grade-easy:focus-visible { background: color-mix(in srgb, var(--n4) 12%, var(--surface)); border-color: var(--n4); color: var(--n4); }
.ak-grade:hover .ak-grade-ivl, .ak-grade:focus-visible .ak-grade-ivl { color: inherit; }
/* Not a grade: every button to its left schedules another look, this one says
   there is no need for another look. Quiet, and green only on the way in. */
.ak-done {
  border: 1px solid var(--border-strong); background: var(--surface); color: var(--text-soft);
  border-radius: var(--radius-sm); padding: 5px 14px; font: inherit; font-size: .82rem;
  font-weight: 600; cursor: pointer;
  transition: background-color .18s cubic-bezier(.4, 0, .2, 1), border-color .18s cubic-bezier(.4, 0, .2, 1), color .18s cubic-bezier(.4, 0, .2, 1);
}
.ak-done:hover, .ak-done:focus-visible {
  background: color-mix(in srgb, var(--st-done) 12%, var(--surface));
  border-color: var(--st-done); color: var(--st-done);
}

.ak-finish {
  width: 100%; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 10px;
  padding: 64px 16px; text-align: center;
}
.ak-finish[hidden] { display: none; }
.ak-finish-title { font-weight: 600; font-size: 1.15rem; margin: 0; }
.ak-finish-note { color: var(--text-soft); margin: 0; max-width: 440px; font-size: .88rem; }
.ak-finish-stats { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; margin: 6px 0 0; padding: 0; list-style: none; font-size: .82rem; font-weight: 600; color: var(--text-soft); }
.ak-finish-stats li { border: 1px solid var(--border); border-radius: 999px; padding: 4px 12px; background: var(--surface-2); }
.ak-more { margin-top: 8px; }

@media (max-width: 560px) {
  /* Five across is too many, so Done takes the line under the four. */
  .ak-grades { grid-template-columns: repeat(4, 1fr); }
  .ak-done { grid-column: 1 / -1; }
  .ak-grade { padding: 8px 4px 10px; }
  .ak-grade-name { font-size: .82rem; }
  .ak-grade-key { display: none; }
  /* One card on an otherwise empty screen still has to fit on the screen. At
     17vw the front glyph took half a phone's height and pushed the answer bar
     under the fold, and the answer view under it was set at the sizes a
     desktop column can afford. Everything comes down to reading size. */
  .ak { margin-bottom: 16px; }
  .ak .fc-front { min-height: 32vh; gap: 14px; font-size: clamp(3.4rem, 13vw, 5rem); }
  .ak-words .fc-front { font-size: clamp(2rem, 7.5vw, 3.4rem); }
  .ak[data-ak-kind="g"] .fc-front { font-size: clamp(1.5rem, 5vw, 2.2rem); }
  .ak .fc-card.is-flipped .fc-back { gap: 12px; }
  .ak .fc-back-header { gap: 7px; padding-bottom: 12px; }
  .ak .fc-back-glyph { font-size: clamp(2.6rem, 9vw, 3.6rem); }
  .ak-words .fc-back-glyph { font-size: clamp(1.7rem, 6vw, 2.4rem); }
  .ak[data-ak-kind="g"] .fc-back-glyph { font-size: clamp(1.25rem, 4.4vw, 1.7rem); }
  .ak .fc-back-meanings { font-size: 1.08rem; }
  .ak-back-reading { font-size: 1.02rem; }
  .ak .fc-back-readings { gap: 8px 26px; }
  .ak .fc-back-reading-val { font-size: 1.02rem; }
  .ak .fc-back-examples { margin-top: 0; padding-top: 10px; }
  .ak > .ak-answer { gap: 8px; padding: 10px 12px calc(12px + env(safe-area-inset-bottom, 0px)); }
  .ak:has(> .ak-answer:not([hidden])) { padding-bottom: 182px; }
}

/* Visually-hidden utility: present for assistive tech, removed from view. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}


/* ── Cards ⇄ List ──────────────────────────────────────────
   Two views of one deck. The list reuses the study plan's item cards
   (.day-item, .sp-block and the review actions), because they are the same set.
   The switch is the deck's own: the study day dropped its copy when the day
   became a session, and Cards/List there stopped being a choice. */
.ak-top-end { display: flex; align-items: center; gap: 14px; margin-left: auto; flex-wrap: wrap; }
.sp-tabs { display: inline-flex; gap: 4px; padding: 3px; border-radius: 999px; background: var(--surface-2); }
.sp-tab { display: inline-flex; align-items: center; gap: 7px; padding: 6px 15px; border: 0; border-radius: 999px; background: none; color: var(--text-soft); font: inherit; font-size: .88rem; font-weight: 600; cursor: pointer; }
.sp-tab:hover { color: var(--accent-strong); }
.sp-tab .count-chip { background: var(--bg-elev); font-size: .72rem; padding: 1px 8px; }
.sp-tab.is-active { background: var(--surface); color: var(--text); box-shadow: var(--shadow-sm); }
.sp-tab.is-active .count-chip { background: var(--accent-soft); color: var(--accent-strong); }
.ak-views .sp-tab { padding: 5px 14px; }
.ak-list { width: 100%; }
.ak-list[hidden] { display: none; }
.ak-list-top { display: flex; justify-content: flex-end; gap: 12px; margin-bottom: 4px; }
.ak-list .sp-block:first-of-type { border-top: 0; padding-top: 0; margin-top: 0; }
.ak-list .ak-finish { padding: 40px 16px; }
.ak-listing { gap: 14px; }
.ak-stage[hidden], .ak-answer[hidden] { display: none; }

/* ── Practice on the new cards ──────────────────────────────────────
   The study day's question round (53-study-plan.css, .day-practice) run under
   the deck. Under the list it is a section on the page; over the cards there
   is nothing to put it under, so it opens as a sheet. */
/* The Practice button scrolls the page here, and the site header is pinned
   over the top of it. */
.ak-practice { width: 100%; margin-top: 26px; padding-top: 22px; border-top: 1px solid var(--border); scroll-margin-top: 72px; }
.ak-practice[hidden] { display: none; }
/* The sheet is a reading surface rather than a form, so it takes the wider
   width and lets the round fill it. */
.practice-modal .set-card { max-width: 680px; }
.practice-modal .day-practice { max-width: none; padding: 0; }
/* Minimal kana reference. Stroke modal styles below are shared site-wide. */
.kana-minimal { max-width: 720px; margin: 0 auto; }
.kana-page-head { display: flex; justify-content: space-between; align-items: center; gap: 16px; margin-bottom: 24px; }
.kana-page-head h1 { margin: 0; font-size: clamp(1.8rem, 4vw, 2.25rem); }
.kana-page-head > a { flex-shrink: 0; font-size: .88rem; }
.kana-toolbar { margin-bottom: 16px; }
.kana-segments { display: inline-flex; gap: 4px; padding: 4px; background: var(--surface-2); border-radius: var(--radius-sm); }
.kana-segments button { min-height: 40px; padding: 8px 20px; border: 0; border-radius: var(--radius-xs); background: transparent; color: var(--text-soft); font-size: .88rem; }
.kana-segments [aria-pressed="true"] { background: var(--accent); color: var(--accent-contrast); }
.kana-toolbar-bottom { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 16px; margin-top: 12px; }
.kana-filters { display: flex; flex-wrap: wrap; gap: 4px; }
.kana-filters button { min-height: 40px; padding: 8px 12px; border: 0; border-radius: var(--radius-xs); background: transparent; color: var(--text-soft); font-size: .82rem; }
.kana-filters button:hover, .kana-filters [aria-pressed="true"] { background: var(--surface-2); color: var(--text); }
.kana-filters [aria-pressed="true"] { font-weight: 600; }
.kana-toggle { display: inline-flex; align-items: center; gap: 8px; min-height: 40px; font-size: .82rem; color: var(--text-soft); cursor: pointer; }
.kana-toggle input { width: 16px; height: 16px; margin: 0; accent-color: var(--accent); }
.kana-section { margin-bottom: 24px; scroll-margin-top: 90px; }
.kana-native { font-size: .62em; color: var(--text-soft); }
.kana-group h3 { font-family: var(--sans); font-size: .82rem; font-weight: 600; letter-spacing: 0; margin: 20px 0 12px; color: var(--text-soft); }
.kana-table-wrap { overflow-x: auto; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface); }
.kana-table { border-collapse: collapse; width: 100%; table-layout: fixed; }
.kana-table th, .kana-table td { border: 1px solid var(--border); text-align: center; }
.kana-table thead th, .kana-table .kana-row-label { background: var(--surface-2); color: var(--text-soft); font-size: .72rem; font-weight: 500; }
.kana-table thead th { padding: 6px 4px; }
.kana-table .kana-corner { width: 36px; }
.kana-row-label a { display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 52px; color: var(--text-soft); }
.kana-row-label a span { font-size: .62rem; color: var(--text-faint); }
.kana-cell { position: relative; padding: 0; scroll-margin-top: 90px; }
.kana-select { display: block; position: relative; width: 100%; min-height: 64px; padding: 8px 4px; border: 0; background: transparent; color: var(--text); }
.kana-select:hover { background: var(--surface-2); }
.kana-cell:target .kana-select { outline: 2px solid var(--accent-strong); outline-offset: -3px; }
.kana-glyph { display: block; font-size: clamp(1.35rem, 3vw, 1.7rem); font-weight: 700; line-height: 1.3; }
.kana-romaji { display: block; font-size: .72rem; color: var(--text-soft); margin-top: 2px; }
.kana-cell-state { position: absolute; top: 2px; right: 6px; font-size: .62rem; color: var(--st-done); }
.kana-empty { background: var(--surface-2); }
.kana-hide-readings .kana-romaji { visibility: hidden; }
.kana-footnote { margin: 12px 0 0; font-size: .72rem; color: var(--text-faint); }
.is-enhanced .kana-section-title, .is-enhanced .kana-group h3 { display: none; }
.is-enhanced .kana-section { margin-bottom: 0; }
#kana-explorer [hidden] { display: none; }
@media (max-width: 420px) {
  .kana-filters button { padding-inline: 8px; }
  .kana-table .kana-corner { width: 28px; }
  .kana-select { min-height: 60px; }
}
@media print {
  .kana-toolbar, .kana-footnote, .kana-page-head > a { display: none !important; }
  #kana-explorer .kana-section[hidden], #kana-explorer .kana-group[hidden] { display: block; }
  .kana-hide-readings .kana-romaji { visibility: visible; }
  .is-enhanced .kana-section-title, .is-enhanced .kana-group h3 { display: block; }
  .kana-table-wrap { break-inside: avoid; }
}

/* Pencil in the corner of each cell → opens the stroke-order modal. It sits
   quiet until the cell is hovered so the chart still reads as a chart; on
   touch, where there is no hover, it stays visible. */
.kana-draw-btn {
  position: absolute; top: 2px; right: 2px;
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; padding: 0;
  border: 0; border-radius: var(--radius-xs); background: none; cursor: pointer;
  color: var(--text-soft); opacity: .35;
  transition: opacity .18s cubic-bezier(.4, 0, .2, 1), color .18s cubic-bezier(.4, 0, .2, 1), background .18s cubic-bezier(.4, 0, .2, 1);
}
.kana-draw-btn[hidden] { display: none; }
.kana-cell:hover .kana-draw-btn { opacity: 1; }
.kana-draw-btn:hover { background: var(--surface); color: var(--accent-strong); opacity: 1; }
.kana-draw-btn:focus-visible { opacity: 1; outline: 2px solid var(--accent-strong); outline-offset: 1px; }
@media (hover: none) {
  .kana-draw-btn { opacity: .6; }
}

/* On the flashcard drill the pencil is a primary affordance rather than a
   chart adornment, so it is bigger, bordered and always at full strength. */
.kp-card { position: relative; }
.kp-draw {
  top: 10px; right: 10px; width: 32px; height: 32px; opacity: 1;
  border: 1px solid var(--border-strong); border-radius: 999px;
  background: var(--bg-elev); box-shadow: var(--shadow-sm);
}
.kp-draw svg { width: 15px; height: 15px; }
@media (hover: none) {
  .kp-draw { opacity: 1; }
}

/* ---- Kana stroke-order modal -------------------------------------------- */
.kd-overlay {
  position: fixed; inset: 0; z-index: 70;
  background: var(--scrim); backdrop-filter: blur(4px);
  display: flex; padding: 14px; overflow-y: auto;
}
.kd-overlay[hidden] { display: none; }
/* `margin: auto` rather than `align-items: center`: it centres the modal the
   same way, but a modal taller than the viewport still scrolls from its top
   instead of having the head clipped off. */
.kd-modal {
  margin: auto;
  width: 100%; max-width: 480px; background: var(--bg-elev);
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg); display: flex; flex-direction: column; overflow: hidden;
}
.kd-head { display: flex; align-items: center; gap: 10px; padding: 10px 14px; border-bottom: 1px solid var(--border); }
.kd-title { display: flex; align-items: baseline; gap: 8px; margin: 0; font-size: 1rem; }
.kd-glyph { font-family: var(--ja); font-size: 1.5rem; font-weight: 700; line-height: 1; }
.kd-romaji { font-size: .82rem; font-weight: 600; color: var(--text-soft); }
.kd-strokes { margin-left: auto; font-size: .72rem; font-weight: 600; color: var(--text-soft); }
.kd-body { padding: 16px 14px; display: flex; flex-direction: column; align-items: center; gap: 12px; }
/* The hint swaps between "watch" and "your turn" on its own as the animation
   hands over to trace mode. Reserve two lines so the centred modal does not
   jump when the longer string wraps. */
.kd-hint { margin: 0; min-height: 3em; font-size: .82rem; line-height: 1.5; color: var(--text-soft); text-align: center; }
.kd-stage { justify-content: center; }

/* The figure is the canvas host; the writing boxes (one per glyph, so yoon
   pairs get two) carry the border and crosshair guide. */
.stroke-order-figure.kd-figure {
  position: relative; display: flex; gap: 10px;
  width: auto; height: auto; border: 0; background: none; box-shadow: none;
}
.stroke-order-figure.kd-figure::before, .stroke-order-figure.kd-figure::after { display: none; }
.kd-box {
  position: relative; width: 200px; height: 200px; flex: 0 0 auto;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface); box-shadow: var(--shadow-sm);
}
.kd-box::before, .kd-box::after { content: ""; position: absolute; pointer-events: none; }
.kd-box::before { top: 8%; bottom: 8%; left: 50%; border-left: 1px dashed var(--border-strong); }
.kd-box::after { left: 8%; right: 8%; top: 50%; border-top: 1px dashed var(--border-strong); }
.kd-box .kvg { position: relative; display: block; width: 100%; height: 100%; }

/* strokesvg paints a filled glyph shape (the shadow) with thick clipped
   strokes on top; drawing the strokes in order fills the shadow exactly. */
.ks { --shadow: var(--border); --stroke: var(--text); }
[data-stroke-order].is-drawing .ks { --stroke: transparent; --shadow: var(--border-strong); }
.ks-stroke.kvg-done path { stroke: var(--text); }
.ks-stroke.kvg-hint path { stroke: var(--accent); }
[data-stroke-order].is-complete .ks-stroke.kvg-done path { stroke: var(--accent-strong); }

.kd-foot {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 14px; border-top: 1px solid var(--border); background: var(--surface);
}
.kd-nav {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 4px 9px; border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  background: var(--bg-elev); color: var(--text-soft);
  font-size: .72rem; font-weight: 600; cursor: pointer;
}
.kd-nav:hover:not(:disabled) { border-color: var(--accent); color: var(--accent-strong); }
.kd-nav:disabled { opacity: .4; cursor: default; }
.kd-nav[hidden] { display: none; } /* single-trigger pages: nothing to page through */
.kd-credit { margin: 0 auto; font-size: .72rem; color: var(--text-soft); }

/* Too narrow to sit the control column beside a two-box yoon pair, so drop the
   controls into a row underneath. */
@media (max-width: 460px) {
  .kd-box { width: 150px; height: 150px; }
  .kd-stage { flex-direction: column; align-items: center; }
  .kd-stage .stroke-order-controls { flex-direction: row; }
}
/* Verb conjugation table (vocab detail pages). */
.conj-intro { margin-bottom: 12px; }
/* The boxed grid is gone: rules run horizontally only, which is what lets a
   dense table sit on a warm ground without looking like a spreadsheet. */
.conj-table-wrap { overflow-x: auto; }
.conj-table { border-collapse: collapse; width: 100%; min-width: 420px; }
.conj-table th,
.conj-table td { border: 0; border-bottom: 1px solid var(--border); padding: 12px 14px; text-align: left; }
.conj-table thead th {
  background: none;
  border-bottom: 1px solid var(--border-strong);
  color: var(--text-faint);
  font-size: .72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .06em;
}
.conj-table tbody th {
  color: var(--text-soft);
  font-size: .82rem;
  font-weight: 600;
  white-space: nowrap;
}
.conj-table tbody tr:last-child td, .conj-table tbody tr:last-child th { border-bottom: 0; }
.conj-table tbody tr:hover td { background: var(--surface-2); }
.conj-table td[lang="ja"] { font-family: var(--ja); font-weight: 600; }
.conj-kana { font-size: 0.82em; font-weight: 500; color: var(--text-soft); margin-left: 6px; }
.conj-romaji { font-size: .88rem; color: var(--text-soft); }
.conj-note { margin-top: 10px; font-size: .88rem; color: var(--text-soft); }

/* ---- Grammar comparison pages (/grammar/compare/) ----------------------- */

/* CTA banner on the grammar index linking to the comparisons hub. */
.grammar-compare-cta {
  display: flex; align-items: center; gap: 14px; margin-top: 22px; padding: 16px 18px;
  border-radius: var(--radius); background: var(--accent-soft);
  border: 1px solid var(--border); color: var(--text);
  transition: box-shadow .3s cubic-bezier(.4, 0, .2, 1), border-color .3s cubic-bezier(.4, 0, .2, 1);
}
.grammar-compare-cta:hover { text-decoration: none; box-shadow: var(--shadow-md); border-color: var(--border-strong); }
.grammar-compare-cta-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
.grammar-compare-cta-body strong { font-size: 1.02rem; }
.grammar-compare-cta-meta { font-size: .82rem; color: var(--text-soft); font-weight: 500; }
.grammar-compare-cta-go { flex: 0 0 auto; color: var(--accent-strong); font-weight: 600; font-size: 1.15rem; }

/* Hub: comparisons grouped, each a compact card. */
.compare-group { margin-top: 26px; }
.compare-group-title { margin: 0 0 12px; font-family: var(--sans); font-size: .72rem; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--text-faint); }
.compare-card-grid { display: grid; gap: 10px; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); }
.compare-card {
  display: flex; flex-direction: column; gap: 3px; padding: 12px 14px; border-radius: var(--radius-sm);
  background: var(--surface); border: 1px solid var(--border); box-shadow: var(--shadow-sm); color: var(--text);
  transition: box-shadow .3s cubic-bezier(.4, 0, .2, 1), border-color .3s cubic-bezier(.4, 0, .2, 1);
}
.compare-card:hover { text-decoration: none; background: var(--accent-soft); box-shadow: var(--shadow-md); border-color: var(--border-strong); }
.compare-card-title { font-family: var(--ja); font-weight: 700; font-size: 1rem; }
.compare-card-meta { font-size: .72rem; font-weight: 600; letter-spacing: .03em; color: var(--text-soft); }

/* Contrast table — the at-a-glance side-by-side. */
.compare-table-wrap { overflow-x: auto; margin-top: 24px; }
.compare-table { border-collapse: collapse; width: 100%; min-width: 420px; }
.compare-table th, .compare-table td { border: 0; border-bottom: 1px solid var(--border); padding: 12px 14px; text-align: left; vertical-align: top; }
.compare-table thead th {
  background: none; border-bottom: 1px solid var(--border-strong);
  color: var(--text-faint); font-size: .72rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: .06em;
}
.compare-table tbody th { color: var(--text-soft); font-weight: 600; white-space: nowrap; }
.compare-table tbody tr:last-child td, .compare-table tbody tr:last-child th { border-bottom: 0; }
.compare-table tbody tr:hover td { background: var(--surface-2); }
.compare-table tbody th a { color: var(--text); }
.compare-table tbody th a:hover { color: var(--accent-strong); }
.compare-table tbody th span[lang="ja"] { font-family: var(--ja); font-size: 1.05rem; }
.compare-romaji { font-size: .82rem; font-weight: 500; color: var(--text-soft); font-family: var(--sans); }
.compare-level-tag { display: inline-block; font-size: .72rem; font-weight: 600; letter-spacing: .03em; color: var(--accent-strong); }

/* Per-member detail cards. */
.compare-points { display: grid; gap: 16px; margin-top: 24px; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.compare-point {
  padding: 16px 18px; border-radius: var(--radius); background: var(--surface);
  border: 1px solid var(--border); box-shadow: var(--shadow-sm);
}
.compare-point-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; flex-wrap: wrap; margin-bottom: 8px; }
.compare-point-head h2 { margin: 0; font-size: 1.35rem; }
.compare-point-head h2 span[lang="ja"] { font-family: var(--ja); }
.compare-point-tags { display: flex; gap: 6px; flex-wrap: wrap; }
.compare-point-gloss { font-weight: 600; color: var(--text); margin: 0 0 8px; }
.compare-point .example-list { margin-top: 12px; }
.compare-point-link { display: inline-block; margin-top: 12px; font-size: .88rem; font-weight: 600; color: var(--accent-strong); }
.compare-point-link span[lang="ja"] { font-family: var(--ja); }
.compare-notes p { color: var(--text); }
.compare-outro { margin-top: 28px; color: var(--text-soft); max-width: 70ch; }
.compare-when-list { list-style: none; margin: 12px 0 0; padding: 0; display: grid; gap: 8px; }
.compare-when-list li { display: flex; align-items: baseline; gap: 10px; color: var(--text); }
.compare-when-form {
  flex-shrink: 0; font-family: var(--ja); font-weight: 700; font-size: 1.02rem;
  padding: 2px 10px; border-radius: 999px; background: var(--surface-2); border: 1px solid var(--border);
}
.compare-related-list a span[lang="ja"] { font-family: var(--ja); font-weight: 700; }

/* "Compare with…" cross-link block on grammar point pages. */
.compare-links-list { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 8px; }
.compare-links-list a {
  display: inline-block; padding: 6px 14px; border-radius: var(--radius-sm); background: var(--surface-2);
  border: 1px solid var(--border); color: var(--text); font-size: .88rem;
}
.compare-links-list a:hover { border-color: var(--accent); color: var(--accent-strong); text-decoration: none; }
.compare-links-list a span[lang="ja"] { font-family: var(--ja); font-weight: 600; }

/* One practice surface with quiet controls and inline results. */
.kp-app { max-width: 560px; margin: 0 auto; }
.kp-app [hidden] { display: none; }
.kp-app button:disabled { cursor: default; opacity: .5; }
.kp-options { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
.kp-options label, .kp-extra-options > label:not(.kana-toggle) { display: flex; flex-direction: column; gap: 4px; min-width: 0; font-size: .72rem; color: var(--text-faint); }
.kp-settings select { width: 100%; min-height: 40px; padding: 8px; border: 1px solid var(--border-strong); border-radius: var(--radius-sm); background: var(--surface); color: var(--text); font: inherit; font-size: .82rem; }
.kp-more { margin: 8px 0 16px; }
.kp-more summary { width: fit-content; min-height: 36px; padding: 6px 0; font-size: .72rem; color: var(--text-soft); cursor: pointer; }
#kp-filter-summary { color: var(--text-faint); }
.kp-extra-options { display: flex; flex-wrap: wrap; align-items: end; gap: 12px; padding-top: 8px; }
.kp-extra-options > label:not(.kana-toggle) { flex: 1 1 120px; }
.kp-extra-options .kana-toggle { flex: 1 1 100%; }
.kp-more .kana-footnote { margin-bottom: 16px; }
.kp-session-top { display: flex; align-items: center; justify-content: space-between; gap: 16px; font-size: .82rem; color: var(--text-soft); font-variant-numeric: tabular-nums; }
.kp-text-button { min-height: 40px; padding: 8px 4px; border: 0; background: transparent; color: var(--text-soft); font-size: .82rem; text-decoration: underline; text-underline-offset: 4px; }
.kp-text-button:hover { color: var(--accent-strong); }
.kp-progress { display: block; width: 100%; height: 3px; border: 0; overflow: hidden; margin: 4px 0 16px; accent-color: var(--st-done); background: var(--bg-sunken); }
.kp-progress::-webkit-progress-bar { background: var(--bg-sunken); }
.kp-progress::-webkit-progress-value { background: var(--st-done); }
.kp-progress::-moz-progress-bar { background: var(--st-done); }
.kp-card { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 16px; min-height: 240px; padding: 24px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); }
.kp-script { font-size: .72rem; color: var(--text-faint); }
.kp-glyph { font-size: clamp(4.5rem, 18vw, 6.5rem); font-weight: 700; line-height: 1.3; }
.kp-form { display: flex; gap: 8px; margin-top: 16px; }
.kp-input { flex: 1; min-width: 0; width: 100%; padding: 12px 16px; border: 1px solid var(--border-strong); border-radius: var(--radius-sm); background: var(--surface); color: var(--text); font: inherit; text-align: center; }
.kp-input:disabled { opacity: .6; }
.kp-submit { min-height: 44px; padding: 12px 20px; border: 0; border-radius: var(--radius-sm); background: var(--accent); color: var(--accent-contrast); font-size: .88rem; }
.kp-submit:hover:not(:disabled) { background: color-mix(in srgb, var(--accent) 86%, var(--surface)); }
.kp-answer-actions { display: flex; align-items: center; gap: 12px; min-height: 52px; }
.kp-feedback { flex: 1; margin: 0; font-size: .88rem; }
.kp-feedback.is-correct { color: var(--st-done); }
.kp-feedback.is-wrong { color: var(--n1); }
.kp-next { width: 100%; }
.kana-draw-btn.kp-study-draw { position: static; display: inline-flex; width: auto; height: auto; min-height: 40px; padding: 8px; gap: 6px; opacity: 1; color: var(--text-soft); font-size: .82rem; }
.kp-score { margin: 16px 0 0; text-align: center; font-size: .72rem; color: var(--text-faint); font-variant-numeric: tabular-nums; }
.kp-choices { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; margin-top: 16px; }
.kp-choices button { display: flex; align-items: center; gap: 20px; min-height: 48px; padding: 10px 16px; border: 1px solid var(--border-strong); border-radius: var(--radius-sm); background: var(--surface); color: var(--text); font-size: 1rem; }
.kp-choices button > span { font-size: .72rem; color: var(--text-faint); }
.kp-choices button:hover:not(:disabled) { background: var(--accent-soft); }
.kp-app .kp-choices button.is-correct { color: var(--st-done); opacity: 1; background: color-mix(in srgb, var(--st-done) 10%, var(--surface)); }
.kp-app .kp-choices button.is-wrong { color: var(--n1); opacity: 1; }
.kp-results { padding: 40px 0; text-align: center; }
.kp-results h2 { margin: 0 0 12px; font-size: 2rem; }
.kp-result-score { margin: 0; font-variant-numeric: tabular-nums; }
.kp-missed-list { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; margin-top: 20px; }
.kp-missed-list a { display: flex; flex-direction: column; gap: 4px; min-width: 56px; padding: 8px; border: 1px solid var(--border); border-radius: var(--radius-sm); text-align: center; color: var(--text); }
.kp-missed-list a:hover { background: var(--surface); text-decoration: none; }
.kp-missed-list span:lang(ja) { font-size: 1.35rem; }
.kp-missed-list span:last-child { font-size: .72rem; color: var(--text-soft); }
.kp-result-actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 16px; margin-top: 24px; }
.kp-empty { padding: 40px 16px; text-align: center; color: var(--text-soft); font-size: .88rem; }
@media (max-width: 420px) {
  .kp-options { gap: 8px; }
  .kp-settings select { padding-inline: 4px; }
  .kp-card { min-height: 200px; padding: 20px; }
}
/* ---- The draw pencil off the kana chart ---------------------------------- */
/* .kana-draw-btn (40-kana.css) pins the pencil to a kana cell's corner and
   keeps it faint until the cell is hovered. On a kanji row or card head it is
   one control among others, so it sits in flow at full strength. Numbered
   after 40 because that is what makes these win. */
.day-item-draw, .ak-back-side .kana-draw-btn {
  position: static; width: 30px; height: 30px; opacity: 1;
  border: 1px solid var(--border); border-radius: 999px; background: var(--surface);
}
.day-item-draw svg, .ak-back-side .kana-draw-btn svg { width: 15px; height: 15px; }
.day-item-draw:hover, .ak-back-side .kana-draw-btn:hover {
  border-color: var(--accent); color: var(--accent-strong); background: var(--accent-soft);
}
.day-item-draw:active, .ak-back-side .kana-draw-btn:active { transform: scale(.94); }
/* ---- Progress control (grammar / vocab / kanji item pages) --------------- */
.progress-control {
  display: flex; margin: 12px 0 4px; padding: 7px 10px;
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
/* One centred row: prev/next arrows pinned at the edges, the grade/Done
   buttons and view-mode toggles clustered in the middle (mirrors the app's
   detail screens). The middle wraps on narrow screens; the arrows stay put. */
.pc-row { display: flex; align-items: center; gap: 8px; width: 100%; }
.pc-main { display: flex; flex: 1 1 auto; align-items: center; flex-wrap: wrap; justify-content: center; gap: 6px 14px; min-width: 0; }
.pc-group { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 4px; }
.pc-view { gap: 6px; }
/* Statuses + deck link. Ships hidden and is unhidden by app.js for a signed-in
   reader; signed out, .pc-signin takes the same slot. */
.pc-gated { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 6px 14px; }
.pc-gated[hidden] { display: none; }
/* The one outlined button in the row: what you get, then the action in the
   only coloured text the chrome has. Fills on hover. */
.pc-signin {
  display: inline-flex; align-items: center; flex-wrap: wrap; justify-content: center; gap: 2px 8px;
  padding: 6px 14px; border: 1.5px solid var(--accent); border-radius: 999px;
  background: var(--bg-elev); color: var(--text);
  font-size: .8rem; font-weight: 500; line-height: 1.2;
  transition: background-color .18s cubic-bezier(.4, 0, .2, 1), color .18s cubic-bezier(.4, 0, .2, 1);
}
.pc-signin b { font-weight: 700; color: var(--accent-strong); white-space: nowrap; }
.pc-signin b::after { content: " \2192"; }
.pc-signin:hover, .pc-signin:focus-visible { background: var(--accent); color: var(--accent-contrast); text-decoration: none; }
.pc-signin:hover b, .pc-signin:focus-visible b { color: inherit; }
.pc-signin[hidden] { display: none; }
.pc-btn {
  border: 1px solid var(--border-strong); background: var(--bg-elev);
  color: var(--text-soft); border-radius: 999px; padding: 3px 9px;
  font-weight: 600; font-size: .72rem; line-height: 1; white-space: nowrap;
}
.pc-btn:hover { border-color: var(--accent); color: var(--accent-strong); }
.pc-btn.is-active { border-color: transparent; color: var(--accent-contrast); }
.pc-btn[data-status="done"].is-active { background: var(--st-done); }
/* The flashcard deck's four answers at pill size: same order and the same
   colours as .ak-grade in 31-srs-deck.css, so a grade reads the same here as
   under a card. */
.pc-grade { padding: 3px 7px; border-color: color-mix(in srgb, var(--pc-g) 55%, var(--border)); }
.pc-grade:hover, .pc-grade:focus-visible { background: color-mix(in srgb, var(--pc-g) 12%, var(--surface)); border-color: var(--pc-g); color: var(--pc-g); }
.pc-grade[data-pc-grade="0"] { --pc-g: var(--n1); }
.pc-grade[data-pc-grade="1"] { --pc-g: var(--n2); }
.pc-grade[data-pc-grade="2"] { --pc-g: var(--n5); }
.pc-grade[data-pc-grade="3"] { --pc-g: var(--n4); }
.pc-chip { flex: none; font-size: .72rem; font-weight: 600; color: var(--text-soft); background: var(--surface-2); border-radius: 999px; padding: 3px 8px; white-space: nowrap; }
.pc-chip[data-due] { background: color-mix(in srgb, var(--st-due) 12%, transparent); color: var(--st-due); }
.pc-chip[hidden] { display: none; }

/* Compact prev/next arrows around the buttons (detail pages). Also driven by
   ArrowLeft / ArrowRight via app.js. */
.pc-pager-btn { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; width: 30px; height: 30px; border-radius: var(--radius-sm); border: 1px solid var(--border); background: var(--surface); color: var(--text-soft); }
.pc-pager-btn:hover { border-color: var(--accent); color: var(--text); text-decoration: none; }
.pc-pager-btn.is-disabled { opacity: .35; pointer-events: none; }

/* List/grid card state colours — each card shows exactly one state (priority
   due > done > scheduled > learning > viewed), set by app.js. Every card kind
   (grammar rows, vocab cards, kanji cells — all .recall-cell) uses the same
   --st-* palette as the level bar, so a bar segment and the cards it counts
   are literally the same hue.

   The card's hairline stays NEUTRAL. With five level hues and five study
   states in play, tinting the outline of every card turns a grid into a set of
   warning boxes — so the state lives in a named chip instead, and the colour
   itself stays contained to about 60 square pixels. */
.recall-cell.is-learned { --state-c: var(--st-done); }
.recall-cell.is-scheduled { --state-c: var(--st-scheduled); }
.recall-cell.is-learning { --state-c: var(--st-learning); }
.recall-cell.is-due { --state-c: var(--st-due); }
/* The 6% wash is the only place the state colour touches the card body, and it
   is optional: drop it and the chip alone still reads. */
.recall-cell.is-learned, .recall-cell.is-scheduled, .recall-cell.is-learning, .recall-cell.is-due {
  background: color-mix(in srgb, var(--state-c) 6%, var(--surface));
}
/* The chip names the state in words, so state is legible without colour. */
.state-chip {
  flex: none; align-self: center; display: inline-flex; align-items: center;
  border-radius: 999px; padding: 3px 9px;
  font-size: .72rem; font-weight: 600; line-height: 1; white-space: nowrap;
  background: color-mix(in srgb, var(--state-c, var(--text-faint)) 12%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--state-c, var(--text-faint)) 40%, transparent);
  color: var(--state-c, var(--text-faint));
}
/* A row that carries a chip does not also need the tick — one signal each. */
.grammar-row.is-learned:has(.state-chip) .grammar-row-learned { display: none; }
/* Kanji cells are too small for a chip, so they carry a 6px dot in the state
   colour in the top-right corner and repeat the state in the cell's title —
   a shape change as well as a hue. */
.kanji-cell { position: relative; }
.kanji-cell .state-chip {
  position: absolute; top: 5px; right: 5px; width: 6px; height: 6px;
  padding: 0; border: 0; border-radius: 50%;
  background: var(--state-c, var(--text-faint));
  overflow: hidden; text-indent: 100%; white-space: nowrap;
}
/* Viewed: a dashed neutral outline — clearly "just seen", not a study status. */
.recall-cell.is-visited {
  border: 1px dashed var(--border-strong);
  background: color-mix(in srgb, var(--st-viewed) 6%, var(--surface));
}
.grammar-nav-link.is-learning span[lang="ja"] { color: var(--st-learning); }
.grammar-nav-link.is-scheduled span[lang="ja"] { color: var(--st-scheduled); }
.grammar-nav-link.is-due span[lang="ja"] { color: var(--st-due); }

/* Level progress overview bar (top of grammar / vocab / kanji level pages) */
.level-progress { margin: 0 0 22px; }
.level-progress[hidden] { display: none; }
.lp-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px 14px; flex-wrap: wrap; margin: 0 0 8px; }
.lp-title { font-size: .88rem; color: var(--text-soft); margin: 0; }
.lp-title b { color: var(--text); font-weight: 600; }
.lp-due-link { font-size: .82rem; font-weight: 600; color: var(--n2); white-space: nowrap; }
/* Same look when the due link is a button (opens the flashcard deck in place) */
button.lp-due-link { background: none; border: 0; padding: 0; font-family: inherit; cursor: pointer; }
button.lp-due-link:hover { text-decoration: underline; }
.lp-bar { display: flex; height: 10px; border-radius: 999px; overflow: hidden; background: var(--bg-sunken); border: 1px solid var(--border); }
.lp-seg { height: 100%; }
.lp-seg.lp-done { background: var(--st-done); }
.lp-seg.lp-scheduled { background: var(--st-scheduled); }
.lp-seg.lp-learning { background: var(--st-learning); }
.lp-seg.lp-due { background: var(--st-due); }
.lp-seg.lp-viewed { background: var(--st-viewed); }
.lp-legend { display: flex; flex-wrap: wrap; gap: 6px 16px; margin: 10px 0 0; }
.lp-key { display: inline-flex; align-items: center; gap: 6px; font-size: .82rem; color: var(--text-soft); }
.lp-key::before { content: ""; width: 10px; height: 10px; border-radius: var(--radius-xs); background: var(--border-strong); }
.lp-key.lp-done::before { background: var(--st-done); }
.lp-key.lp-scheduled::before { background: var(--st-scheduled); }
.lp-key.lp-learning::before { background: var(--st-learning); }
.lp-key.lp-due::before { background: var(--st-due); }
.lp-key.lp-viewed::before { background: var(--st-viewed); }
.lp-key b { color: var(--text); font-weight: 600; }

/* ---- Progress dashboard (/progress/) ------------------------------------- */
.progress-loading { color: var(--text-faint); }

/* Account boot screen. It is in the first HTML response, so it covers the
   dashboard before either deferred script can paint controls. app.js also
   makes every background surface inert until sync.js confirms the account. */
body.page-progress.is-account-loading { overflow: hidden; }
.account-loading-screen {
  position: fixed; inset: 0; z-index: 1000;
  overflow: hidden; padding: clamp(28px, 7vh, 72px) 20px 24px;
  background: var(--bg);
  color: var(--text); cursor: wait; touch-action: none; overscroll-behavior: contain;
  opacity: 1; visibility: visible;
  transition: opacity .18s ease, visibility 0s linear 0s;
}
.account-loading-screen.is-ready {
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity .18s ease, visibility 0s linear .18s;
}
.account-loading-card { width: min(100%, var(--shell)); margin: 0 auto; }
/* The visual title occupies the skeleton's real page-heading slot. Loading
   updates remain available to assistive technology without moving that title. */
.account-loading-copy {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.account-loading-skeleton { display: grid; padding-top: 38px; }
.account-skeleton {
  display: block; border-radius: var(--radius-xs);
  background: linear-gradient(100deg, var(--surface-2) 22%, var(--surface) 40%, var(--surface-2) 58%);
  background-size: 240% 100%; animation: account-skeleton-shimmer 1.35s ease-in-out infinite;
}
.account-skeleton-crumb { width: 7rem; height: 11px; margin-bottom: 18px; }
.account-loading-title { margin: 0 0 24px; }
.account-skeleton-panel {
  margin-bottom: 14px; padding: 20px 22px; border: 1px solid var(--border);
  border-radius: var(--radius); background: var(--surface); box-shadow: var(--shadow-sm);
}
.account-skeleton-panel-head { display: flex; align-items: center; gap: 14px; }
.account-skeleton-avatar { flex: none; width: 42px; height: 42px; border-radius: 50%; }
.account-skeleton-copy { display: grid; flex: 1; gap: 8px; }
.account-skeleton-line { height: 11px; }
.account-skeleton-line-lg { width: min(55%, 19rem); }
.account-skeleton-line-sm { width: min(38%, 13rem); }
.account-skeleton-meter { width: 100%; height: 8px; margin-top: 22px; border-radius: 999px; }
.account-skeleton-actions { display: flex; gap: 10px; margin-top: 20px; }
.account-skeleton-button { width: 9rem; height: 38px; border-radius: var(--radius-sm); }
.account-skeleton-button-short { width: 6.5rem; }
.account-skeleton-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 28px; }
.account-skeleton-stat { height: 78px; border: 1px solid var(--border); border-radius: var(--radius); }
.account-skeleton-section { display: grid; gap: 7px; }
.account-skeleton-subtitle { width: 9rem; height: 22px; margin-bottom: 3px; }
.account-skeleton-row { width: 100%; height: 46px; border: 1px solid var(--border); border-radius: var(--radius-sm); }
.account-loading-retry {
  margin: 18px auto 0; padding: 9px 18px; border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm); background: var(--surface); color: var(--text);
  font: 600 .88rem/1.2 var(--sans); cursor: pointer;
}
.account-loading-retry:hover { border-color: var(--accent); color: var(--accent-strong); }
.account-loading-screen.has-error { cursor: default; }
.account-loading-screen.has-error { display: grid; place-items: center; }
.account-loading-screen.has-error .account-loading-card { width: min(100%, 31rem); text-align: center; }
.account-loading-screen.has-error .account-loading-copy {
  position: static; width: auto; height: auto; padding: 0; margin: 0;
  overflow: visible; clip: auto; white-space: normal; border: 0;
  color: var(--text-soft); font-size: 1rem; font-weight: 400;
  letter-spacing: 0; line-height: 1.55; text-transform: none;
}
.account-loading-screen.has-error .account-loading-skeleton { display: none; }

@keyframes account-skeleton-shimmer {
  from { background-position: 100% 0; }
  to { background-position: -100% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .account-loading-screen, .account-loading-screen.is-ready { transition: none; }
  .account-skeleton { animation: none; background: var(--surface-2); }
}
@media (max-width: 680px) {
  .account-loading-screen { padding: 24px 14px; }
  .account-skeleton-panel { padding: 17px; }
  .account-skeleton-stats { grid-template-columns: repeat(2, 1fr); gap: 8px; margin-bottom: 22px; }
  .account-skeleton-stat { height: 64px; }
  .account-skeleton-section .account-skeleton-row:last-child { display: none; }
}

.progress-stats { display: flex; flex-wrap: wrap; gap: 12px; margin: 4px 0 30px; }
.progress-stat {
  flex: 1 1 130px; background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 14px 16px; box-shadow: var(--shadow-sm);
}
.progress-stat b { display: block; font-size: 1.7rem; font-weight: 600; letter-spacing: -.02em; line-height: 1.1; }
.progress-stat span { color: var(--text-soft); font-size: .88rem; }
.progress-section { margin: 0 0 30px; }
.progress-section > h2 { margin: 0 0 .5em; }
/* "Continue studying": the last five pages read, as one line of small text.
   Going back to a page already read is the smallest offer on the dashboard,
   so it is plain links under the three lists, not chips or cards. */
.dash-recent {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 10px;
  margin: 0 0 30px; font-size: .8rem; line-height: 1.5; color: var(--text-soft);
}
.dash-plan .dash-recent { margin: 10px 0 0; }
.dash-recent-h { margin: 0; font: inherit; font-weight: 600; letter-spacing: 0; color: inherit; }
.dash-recent-list { display: flex; flex-wrap: wrap; gap: 2px 12px; min-width: 0; margin: 0; padding: 0; list-style: none; }
.dash-recent-list li { min-width: 0; margin: 0; }
.dash-recent-list a {
  display: block; max-width: 24ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: inherit; text-decoration: underline; text-decoration-color: var(--border-strong); text-underline-offset: 3px;
}
.dash-recent-list a:hover { color: var(--accent-strong); text-decoration-color: currentColor; }
.progress-empty {
  color: var(--text-soft); background: var(--surface-2);
  border: 1px dashed var(--border-strong); border-radius: var(--radius-sm);
  margin: 0 0 30px; padding: 10px 14px;
  font-size: .84rem; line-height: 1.45;
}
.progress-empty-inline { padding: 12px 14px; }
.progress-empty p { margin: 0 0 .6em; }
.progress-empty p:last-child { margin: 0; }
.progress-note { color: var(--text-soft); font-size: .88rem; margin: 0 0 8px; }
.progress-actions { display: flex; flex-wrap: wrap; gap: 10px; }
.progress-actions button {
  border: 1px solid var(--border-strong); background: var(--surface);
  color: var(--text-soft); border-radius: var(--radius-sm); padding: 8px 14px; font-weight: 600; font-size: .88rem;
}
.progress-actions button:hover { border-color: var(--accent); color: var(--accent-strong); }

/* Start one level over: a picker and its own button. */
.progress-reset-level { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: 0; }
.progress-reset-level label { font-size: .88rem; font-weight: 600; }
.progress-reset-level select {
  border: 1px solid var(--border-strong); background: var(--surface); color: var(--text);
  border-radius: var(--radius-sm); padding: 7px 10px; font: inherit; font-size: .88rem; font-weight: 600;
}
.progress-reset-level button {
  border: 1px solid color-mix(in srgb, var(--n1) 45%, var(--border-strong)); background: var(--surface);
  color: var(--n1); border-radius: var(--radius-sm); padding: 8px 14px; font-weight: 600; font-size: .88rem; cursor: pointer;
}
.progress-reset-level button:hover { border-color: var(--n1); background: color-mix(in srgb, var(--n1) 8%, var(--surface)); }
.progress-reset-level .progress-note { flex: 1 1 100%; margin: 0; font-size: .82rem; }

/* Optional cross-device sync (sync.js). Empty until a sign-in control renders. */
.sync-row { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.sync-btn {
  border: 1px solid var(--border-strong); background: var(--surface);
  color: var(--text-soft); border-radius: var(--radius-sm); padding: 8px 14px; font-weight: 600; font-size: .88rem; cursor: pointer;
}
.sync-btn:hover:not([disabled]) { border-color: var(--accent); color: var(--accent-strong); }
.sync-btn[disabled] { opacity: .6; cursor: default; }
.sync-status { display: inline-flex; align-items: center; gap: 8px; font-size: .88rem; color: var(--text-soft); }
.sync-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--st-done); box-shadow: 0 0 0 3px color-mix(in srgb, var(--st-done) 20%, transparent); }
.sync-error { font-size: .82rem; color: var(--n1); }

/* Section × level breakdown rows */
.pl-grid { display: flex; flex-direction: column; gap: 6px; }
.pl-row {
  display: grid; grid-template-columns: 36px 1fr auto auto; align-items: center; gap: 14px;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 10px 14px; color: var(--text);
}
.pl-row:hover { border-color: var(--border-strong); text-decoration: none; }
/* Scoped to one level the first column holds a section name, not "N3". */
.pl-grid.is-sections .pl-row { grid-template-columns: 92px 1fr auto auto; }
.pl-level { font-weight: 600; font-size: .88rem; letter-spacing: -.01em; }
.pl-bar { display: flex; height: 8px; min-width: 64px; border-radius: 999px; overflow: hidden; background: var(--bg-sunken); border: 1px solid var(--border); }
.pl-count { font-size: .82rem; color: var(--text-soft); white-space: nowrap; }
.pl-count b { color: var(--text); font-weight: 600; }
.pl-due { font-size: .72rem; font-weight: 600; color: var(--n2); white-space: nowrap; min-width: 3.2em; text-align: right; }



/* ---- The schedule card --------------------------------------------------- */
/* The top of the dashboard: what to do today, and how far through the level
   that leaves you. Rendered by app.js (scheduleCardHtml); --lvl carries the
   level's colour in from the row it belongs to.

   Zones, top to bottom, on one 24px rhythm: the head (the level), its three
   lists, its tests, its graded reading, the flashcards (with the schedule's
   day, settings and progress panel), then the two quick routes. Tiles inside the card sit on
   --surface-2 with no outline, so the card has one border, not twenty. */
.dash-plan {
  --gap: 24px;
  margin: 4px 0 28px; padding: 28px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-sm);
}
.dash-plan > * + * { margin-top: var(--gap); }

.dash-plan-head { display: flex; align-items: center; gap: 12px 18px; flex-wrap: wrap; }
.dash-plan-mark {
  flex: none; display: grid; place-items: center;
  width: 56px; height: 56px; border-radius: var(--radius);
  background: color-mix(in srgb, var(--lvl, var(--text-soft)) 14%, transparent);
  font-family: var(--display); font-weight: 400; font-size: 1.9rem; line-height: 1;
  color: var(--lvl, var(--text-soft));
}
.dash-plan-title { min-width: 0; flex: 1 1 0; }
@media (min-width: 821px) { .dash-plan-title { flex-basis: auto; } }
.dash-plan-h { margin: 0; font-family: var(--sans); font-size: 1.5rem; font-weight: 650; letter-spacing: -.025em; line-height: 1.15; }
.dash-plan-edits { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; }
/* Settings, not actions: quiet chips on the card's second surface, all 36px
   tall so the buttons and the reminder line up along both edges. */
.dash-plan-edit {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 36px; padding: 0 14px;
  border: 1px solid transparent; background: var(--surface-2);
  color: var(--text-soft); border-radius: 999px;
  font: inherit; font-size: .82rem; font-weight: 600; cursor: pointer;
  transition: border-color .15s, color .15s;
}
.dash-plan-edit:hover { border-color: var(--border-strong); color: var(--text); }
/* The reminder is schedule setup too, so it shares the head row. The tick box
   and its clock are one control, so they share one chip with a hairline
   between them instead of reading as two more buttons. */
.dash-plan-edits .dash-remind {
  display: flex; flex-wrap: wrap; align-items: center; margin: 0; padding: 0;
  border: 0; border-radius: 0; background: transparent;
}
.dash-plan-edits .dash-remind[hidden] { display: none; }
.dash-remind .sp-remind-head {
  flex-wrap: nowrap; gap: 0; min-height: 36px;
  border: 1px solid transparent; border-radius: 999px; background: var(--surface-2);
  transition: border-color .15s;
}
.dash-remind .sp-remind-head:hover { border-color: var(--border-strong); }
.dash-remind .sp-remind-toggle { gap: 8px; min-height: 34px; padding: 0 14px; color: var(--text-soft); }
.dash-remind .sp-remind-toggle:hover { color: var(--text); }
.dash-remind .sp-remind-toggle input { width: 15px; height: 15px; }
.dash-remind .sp-remind-text { display: block; }
.dash-remind .sp-remind-text b { font-size: .82rem; font-weight: 600; white-space: nowrap; }
.dash-remind .sp-remind-time {
  align-self: stretch; padding: 0 14px 0 12px; font-size: .82rem;
  border: 0; border-inline-start: 1px solid var(--border-strong);
  border-radius: 0 999px 999px 0; background: transparent; color: var(--text);
}
.dash-remind .sp-remind-note, .dash-remind .sp-remind-error { flex-basis: 100%; margin-top: 4px; }

/* Progress: the bar with its two numbers, then the facts under a hairline. */
.dash-progress {
  padding: 18px 20px; border-radius: var(--radius);
  background: var(--surface-2);
}
.dash-progress-top {
  display: flex; align-items: baseline; justify-content: space-between; gap: 8px 16px; flex-wrap: wrap;
  margin: 0 0 10px; font-size: .86rem; color: var(--text-soft);
}
.dash-progress-pct b { font-size: 1.05rem; font-weight: 650; color: var(--text); letter-spacing: -.01em; }
.dash-meter { height: 8px; border-radius: 999px; background: var(--bg-sunken); overflow: hidden; }
.dash-meter span { display: block; height: 100%; min-width: 0; border-radius: inherit; background: var(--lvl, var(--accent-strong)); }
.dash-facts {
  display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: 12px;
}
.dash-progress-top + .dash-meter + .dash-facts { margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--border-strong); }
.dash-fact { min-width: 0; }
.dash-fact + .dash-fact { padding-inline-start: 16px; border-inline-start: 1px solid var(--border-strong); }
.dash-fact b { display: block; font-size: 1.45rem; font-weight: 650; letter-spacing: -.025em; line-height: 1.15; font-variant-numeric: tabular-nums; }
.dash-fact span { display: block; margin-top: 2px; font-size: .8rem; color: var(--text-soft); }

/* Today: the yellow button is the day; the decks beside it are the same level
   cut one section at a time, and share one row with it on a wide screen.
   Under the tests and passages, behind a hairline, with everything the
   schedule counts: its day, its pace and reminder, and the progress panel. */
.dash-today { padding-top: var(--gap); border-top: 1px solid var(--border); }
.dash-plan .dash-today > * + * { margin-top: 16px; }
.dash-today-head { display: flex; align-items: center; flex-wrap: wrap; gap: 12px 18px; }
.dash-today-head .dash-prem-sub { margin-bottom: 0; }
.dash-plan-actions { display: flex; flex-wrap: wrap; align-items: stretch; gap: 10px; margin: 0; }
.dash-plan-go { flex: none; margin: 0; min-height: 64px; padding: 10px 24px; border-radius: var(--radius); justify-content: space-between; gap: 18px; }
.dash-plan-go-txt { display: flex; flex-direction: column; align-items: flex-start; gap: 1px; line-height: 1.25; font-size: 1rem; }
.dash-plan-go-sub { font-size: .76rem; font-weight: 500; opacity: .72; }
.dash-decks {
  flex: 1 1 320px; display: grid; gap: 10px;
  grid-template-columns: repeat(var(--n, 3), minmax(0, 1fr));
}
.dash-decks[data-n="1"] { --n: 1; }
.dash-decks[data-n="2"] { --n: 2; }
.dash-decks[data-n="4"] { --n: 4; }
.dash-deck {
  display: flex; align-items: center; justify-content: center; min-height: 64px; padding: 10px 12px;
  border: 1px solid var(--border-strong); border-radius: var(--radius);
  background: transparent; color: var(--text);
  font-size: .9rem; font-weight: 600; white-space: nowrap;
  transition: border-color .15s, background .15s, color .15s;
}
.dash-deck:hover { border-color: var(--accent); background: var(--surface-2); color: var(--text); text-decoration: none; }

/* The quick test and the chat: two routes out, side by side, last in the card. */
.dash-plan .dash-known-row { margin: var(--gap) 0 0; gap: 10px; }
.dash-plan .dash-known { border-color: var(--border-strong); padding: 16px 48px 16px 20px; }
/* The chat's row leads with the two faces, Hoshizaki tucked behind Sasaki. */
.dash-plan .dash-chat { flex-direction: row; align-items: center; gap: 14px; }
.dash-chat .chat-avatar + .chat-avatar { margin-inline-start: -14px; box-shadow: 0 0 0 2px var(--surface); }
.dash-chat-txt { display: flex; flex-direction: column; gap: 3px; min-width: 0; }

.dash-plan .dash-browse-tile {
  position: relative; display: flex; flex-direction: column; gap: 4px;
  padding: 14px 16px; min-width: 0;
  background: var(--surface-2); border: 1px solid transparent; border-radius: var(--radius-sm);
  color: var(--text); transition: border-color .15s;
}
.dash-plan .dash-browse-tile:hover { border-color: var(--border-strong); background: var(--surface-2); text-decoration: none; }
.dash-plan .dash-browse-label { font-weight: 600; font-size: .92rem; letter-spacing: -.01em; }
.dash-plan .dash-browse-n { font-size: .8rem; color: var(--text-soft); }

/* The three lists, first under the head: a route into kanji, grammar
   and vocabulary that does not wait for something to have been marked in them. */
.dash-plan .dash-browse {
  margin: var(--gap) 0 0; gap: 10px;
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

/* Nothing running: the invitation, and the button that opens the setup modal. */
.dash-plan.is-empty > * + * { margin-top: 0; }
.dash-plan-lede { margin: 8px 0 20px; color: var(--text-soft); }

/* Tablet: the day's button takes its own row, the decks the row under it. */
@media (max-width: 820px) {
  .dash-plan-go { flex: 1 1 100%; }
  .dash-decks { flex-basis: 100%; }
}

@media (max-width: 560px) {
  .dash-plan { --gap: 20px; padding: 18px 16px 20px; border-radius: var(--radius); }
  .dash-plan-mark { width: 48px; height: 48px; font-size: 1.6rem; border-radius: var(--radius-sm); }
  .dash-plan-h { font-size: 1.3rem; }
  /* The head keeps Change level beside the level; the schedule's settings
     take a row each under the flashcards heading. */
  .dash-today-head .dash-plan-edits { display: grid; grid-template-columns: minmax(0, 1fr); flex: 1 1 100%; }
  .dash-today-head .dash-plan-edit { width: 100%; }
  .dash-today-head .dash-remind { width: 100%; }
  .dash-remind .sp-remind-head { display: grid; grid-template-columns: minmax(0, 1fr) auto; width: 100%; }
  .dash-remind .sp-remind-toggle { justify-content: center; }
  .dash-progress { padding: 16px; }
  .dash-fact + .dash-fact { padding-inline-start: 12px; }
  .dash-fact b { font-size: 1.2rem; }
  .dash-fact span { font-size: .76rem; }
  .dash-plan-go { min-height: 60px; }
  .dash-decks[data-n="4"] { --n: 2; }
  .dash-deck { min-height: 48px; font-size: .86rem; }
  /* Three "Browse ..." labels do not fit three across a phone, so they
     stack as rows with the count on the right. */
  .dash-plan .dash-browse { grid-template-columns: 1fr; gap: 8px; }
  .dash-plan .dash-browse-tile { flex-direction: row; align-items: baseline; justify-content: space-between; }
}

/* ---- Tests and reading ---------------------------------------------------
   The past-paper tests and the graded passages (premiumHtml in app.js): the
   test tiles and one short reading tile under them, inside the schedule card,
   under the three lists and a hairline.
   Filled tiles with a numeral, so they read louder than the outlined section
   decks below them. --lvl comes in from the schedule card. */
.dash-prem {
  display: grid; grid-template-columns: minmax(0, 1fr); gap: 10px;
  padding-top: var(--gap); border-top: 1px solid var(--border);
}
.dash-prem-panel { min-width: 0; }
.dash-prem-h { margin: 0; font-family: var(--sans); font-size: 1.2rem; font-weight: 650; letter-spacing: -.02em; line-height: 1.2; }
.dash-prem-sub { margin: 4px 0 14px; font-size: .86rem; color: var(--text-soft); }
.dash-prem-tiles { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; }
.dash-prem-tile {
  display: flex; flex-direction: column; justify-content: flex-end; gap: 4px;
  min-width: 0; min-height: 104px; padding: 16px 18px;
  background: var(--surface-2); border: 1px solid transparent; border-radius: var(--radius);
  color: var(--text); transition: border-color .15s;
}
.dash-prem-tile:hover { border-color: var(--lvl, var(--border-strong)); color: var(--text); text-decoration: none; }
.dash-prem-tile b {
  font-family: var(--display); font-weight: 400; font-size: 2.3rem; line-height: 1;
  color: var(--lvl, var(--text)); font-variant-numeric: tabular-nums;
}
.dash-prem-tile > span { font-size: .88rem; font-weight: 600; letter-spacing: -.01em; }
/* Graded reading: one short tile, the count and its label side by side. */
.dash-prem-tile.is-row {
  flex-direction: row; align-items: baseline; justify-content: flex-start; gap: 12px;
  min-height: 0; padding: 12px 18px;
}
.dash-prem-tile.is-row b { font-size: 1.6rem; }
@media (max-width: 560px) {
  .dash-prem { gap: 8px; }
  .dash-prem-h { font-size: 1.1rem; }
  .dash-prem-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
  .dash-prem-tile { min-height: 88px; padding: 13px 14px; border-radius: var(--radius-sm); }
  .dash-prem-tile b { font-size: 1.9rem; }
  .dash-prem-tile.is-row { min-height: 0; padding: 10px 14px; }
  .dash-prem-tile.is-row b { font-size: 1.4rem; }
}

/* ---- Foot cards ----------------------------------------------------------
   The exam, the running level's reference pages, and the parts of the site the
   schedule never links to. Ruled off, because everything above them is this
   reader's own record and these are not. Same cards as the home page; the
   countdown is the one line here worth reading from across the room. */
.dash-explore { margin-top: 40px; padding-top: 28px; border-top: 1px solid var(--border); }
/* A wider minimum than the home page's, so the cards land three to a row
   rather than four and a gap. */
.dash-explore .section-card-grid { grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr)); }
/* The three lists, above the cards on a dashboard with no schedule on it (with
   one they ride in the schedule card instead). Tiles rather than cards, so the
   cards under them stay the loud thing. */
.dash-browse {
  display: grid; gap: 10px; margin: 0 0 18px;
  grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr));
}
.dash-browse-tile {
  display: flex; flex-direction: column; gap: 3px; padding: 13px 16px;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface); color: var(--text);
}
.dash-browse-tile:hover { border-color: var(--border-strong); background: var(--accent-soft); text-decoration: none; }
.dash-browse-label { font-weight: 600; font-size: .92rem; letter-spacing: -.01em; }
.dash-browse-n { font-size: .8rem; color: var(--text-soft); }
.dash-explore .section-card-desc { line-height: 1.45; }
.dash-explore .is-exam .section-card-count {
  font-size: 1.18rem; font-weight: 600; letter-spacing: -.02em; margin: 0 0 6px;
}
.dash-explore .is-exam .feature-icon { color: var(--accent-strong); }
/* JLPT guide pages (/guide/): plain-English quick-answer box */
.guide-quick {
  border: 1px solid var(--border); border-left: 2px solid var(--border-strong);
  border-radius: var(--radius-sm); background: var(--surface-2);
  padding: 14px 18px; margin: 4px 0 10px; max-width: 72ch;
}
.guide-quick > p { margin: 0 0 8px; font-size: .72rem; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--accent-strong); }
.guide-quick ul { margin: 0; padding-inline-start: 1.2em; }
.guide-quick li { margin: .3em 0; color: var(--text); line-height: 1.55; }

/* Study-hours table: the "from zero" / "on top of N5" baseline qualifier,
   de-emphasised so the hour figure itself reads first. */
.step-base { display: block; color: var(--text-soft); font-size: .85em; }

/* Study plan (/study-plan/): static schedule tables + the client-rendered
   personal plan app (#plan-app, painted by app.js initStudyPlan). */
.sp-prose { max-width: 76ch; }
.sp-prose h2 { margin: 1.6em 0 .5em; }
/* position:relative makes this the containing block for the visually-hidden
   <span>s in the header cells, so its overflow:auto clips them — without it
   those absolute spans escape to the html box and force page-wide scroll. */
.sp-table-wrap { position: relative; overflow-x: auto; margin: 14px 0 18px; }
.sp-table { border-collapse: collapse; width: 100%; min-width: 460px; }
.sp-table th, .sp-table td { border: 0; border-bottom: 1px solid var(--border); padding: 12px 14px; text-align: left; }
.sp-table thead th { background: none; border-bottom: 1px solid var(--border-strong); font-size: .72rem; font-weight: 600; text-transform: uppercase; letter-spacing: .06em; color: var(--text-faint); }
.sp-table tbody th { font-weight: 600; white-space: nowrap; color: var(--text-soft); }
.sp-table tbody tr:last-child td, .sp-table tbody tr:last-child th { border-bottom: 0; }
.sp-table tbody tr:hover td { background: var(--surface-2); }
.sp-table tbody th a { color: var(--text); }
.sp-table tbody th a:hover { color: var(--accent-strong); }

/* The one loud action on each plan page: into the setup modal on the dashboard.
   Uses --cta, so there is exactly one of these per page and nothing else on the
   page is allowed to compete with it. */
.sp-hero { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 16px; margin: 20px 0 0; }
.sp-hero-fine { font-size: .82rem; color: var(--text-faint); }
/* Used on <a> and, on the dashboard's empty schedule card, on a <button>, so
   it carries the font and cursor a button does not inherit. */
.sp-cta { display: inline-flex; align-items: center; gap: 8px; padding: 13px 22px; border: 0; border-radius: var(--radius); background: var(--cta); color: var(--cta-contrast); font-family: inherit; font-size: .95rem; font-weight: 600; text-decoration: none; cursor: pointer; transition: filter .18s cubic-bezier(.4, 0, .2, 1); }
.sp-cta::after { content: "→"; }
.sp-cta:hover { filter: brightness(.94); text-decoration: none; color: var(--cta-contrast); }

/* Level page, before a schedule exists. app.js hides it once one does, so the
   display below has to be beatable by [hidden]. */
.sp-start { margin: 0 0 30px; }
.sp-start[hidden] { display: none; }
.sp-start-line { margin: 0 0 14px; font-size: var(--step); color: var(--text-soft); }
.sp-start-fine { margin: 12px 0 0; font-size: .82rem; color: var(--text-faint); }

.sp-h2 { margin: 0 0 14px; font-size: clamp(1.3rem, 2.6vw, 1.6rem); line-height: 1.15; letter-spacing: -.02em; }

/* Level chooser on /study-plan/ — replaces a five-row table of numbers. */
/* Three across: five levels land 3 + 2 rather than orphaning N1 on its own. */
.sp-levelcards { display: grid; grid-template-columns: repeat(auto-fit, minmax(272px, 1fr)); gap: 14px; margin: 0 0 30px; }
.sp-levelcard { display: flex; flex-direction: column; gap: 8px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); padding: 16px 18px; text-decoration: none; color: inherit; transition: background-color .3s cubic-bezier(.4, 0, .2, 1), border-color .3s cubic-bezier(.4, 0, .2, 1), box-shadow .3s cubic-bezier(.4, 0, .2, 1); }
.sp-levelcard:hover { background: var(--accent-soft); border-color: var(--border-strong); box-shadow: var(--shadow-md); text-decoration: none; }
.sp-lc-top { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.sp-lc-level { font-family: var(--display); font-size: 2.1rem; font-weight: 400; line-height: .8; letter-spacing: -.045em; color: var(--lvl, var(--text)); font-variant-numeric: tabular-nums; }
.sp-lc-time { font-size: .82rem; font-weight: 500; color: var(--text-faint); text-align: right; font-variant-numeric: tabular-nums; }
.sp-lc-counts { font-size: .88rem; color: var(--text-soft); }
.sp-lc-go { font-size: .82rem; font-weight: 500; color: var(--accent-strong); }
.sp-lc-go::after { content: " →"; }

/* The plan's how-it-works answers carry markup, not just one line of text:
   several paragraphs, and the week schedule opens a full table. */
.sp-prose + .faq-section { margin-top: 28px; }
.faq-item .sp-table-wrap { margin-top: 10px; }
.faq-item .sp-table { font-size: .88rem; }

/* Daily-plan prompts carried by the study pages (lib/plan.js planBar) and the
   band at the top of the level hubs. app.js rewrites both into resume links
   once that level's plan is running. */
.plan-bar { display: flex; align-items: center; gap: 10px 16px; flex-wrap: wrap; margin: 0 0 18px; padding: 12px 16px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface-2); color: var(--text); text-decoration: none; }
.plan-bar:hover { background: var(--accent-soft); border-color: var(--border-strong); text-decoration: none; }
.plan-bar-text { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; }
.plan-bar-text b { font-size: var(--step); }
.plan-bar-text span { font-size: .88rem; color: var(--text-soft); }
.plan-bar-go { margin-left: auto; font-weight: 600; font-size: .88rem; color: var(--accent-strong); white-space: nowrap; }

.plan-pitch { display: flex; align-items: center; justify-content: space-between; gap: 14px 24px; flex-wrap: wrap; margin: 0 0 26px; padding: 20px 22px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface-2); }
.plan-pitch-eyebrow { margin: 0 0 4px; font-size: .72rem; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; color: var(--accent-strong); }
.plan-pitch h2 { margin: 0; font-size: clamp(1.3rem, 2.6vw, 1.6rem); line-height: 1.15; letter-spacing: -.02em; }
.plan-pitch-body p:last-child { margin: 5px 0 0; font-size: .88rem; color: var(--text-soft); }

.sp-app { margin: 6px 0 26px; }
.sp-app-today { margin-top: 0; }
/* The deck pages are the session itself. On desktop main fills the window
   under the header and the session sits in the middle of it, the same gap
   above and below, with the footer below the fold. The header wraps to more
   rows on a narrower window, so app.js (sizeDayFrame) measures it into
   --study-top; the fallback is the one-row header. The 32px padding is the
   smallest gap, and FRAME_GAP in app.js matches it. */
@media (min-width: 561px) {
  .page-study-today main.shell {
    display: flex; flex-direction: column; justify-content: center; gap: 18px;
    padding-block: 32px;
    min-height: calc(100vh - var(--study-top, calc(var(--header-h) + 1px)));
  }
  .page-study-today .sp-app-today,
  .page-study-today main.shell > .plan-bar { margin-block: 0; }
}
.sp-card { border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); box-shadow: var(--shadow-sm); padding: 20px 22px; }
.sp-title { margin: 0 0 .25em; font-size: clamp(1.3rem, 2.6vw, 1.6rem); line-height: 1.15; letter-spacing: -.02em; }
.sp-sub { margin: 0; font-size: .88rem; color: var(--text-soft); }
.sp-note { font-size: .88rem; color: var(--text-soft); line-height: 1.55; }
.sp-head { margin: 0 0 14px; }

/* Picking a finish time moved to the setup modal on the dashboard (see
   55-onboarding.css). What is left of .sp-join is the small card the /today/
   page shows when its schedule has been stopped. */
.sp-join-fine { margin: 14px 0 0; font-size: .82rem; }

.sp-block { margin: 18px 0 0; padding-top: 16px; border-top: 1px solid var(--border); }
.sp-block > h3, .sp-block-head > h3 { margin: 0 0 10px; font-family: var(--sans); font-size: 1.02rem; font-weight: 600; line-height: 1.3; letter-spacing: -.01em; display: inline-flex; align-items: baseline; gap: 8px; }
/* One heading row for every list — today's, a past day's, a due review pile —
   with the section's own action (e.g. "done reviewing") on the right. */
.sp-block-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px 14px; flex-wrap: wrap; }
.sp-block-head > h3 { margin-bottom: 10px; }

/* ── The day, as one shell ───────────────────────────────────
   Day bar, body, action bar: the same three parts, in the same order, on
   every day and in every state. Nothing in here appears on some days and not
   others — a day you can learn the shape of is the whole point, and the shape
   used to change with what the day happened to hold.

   The card is never taller than the screen and the body is the only part
   that scrolls, so the bar stays at the top, the buttons stay at the bottom,
   and stepping from one item to the next moves nothing but the item. */
.sp-day {
  display: flex; flex-direction: column; padding: 0;
  /* A flashcard's height, and the skeleton's, so the deck lands in the box
     that was already there. */
  --deck-stage: clamp(400px, 42vh, 400px);
}
/* Daily and free study use this same section picker, frame and footer. The
   picker sits under the day, above the action bar: the day is what the screen
   opens on, and switching section is what you do once you are done with it. */
.study-toolbar {
  display: flex; flex: none; align-items: center; gap: 8px 14px;
  padding: 8px 14px; border-top: 1px solid var(--border);
}
.study-sections { display: flex; flex: 1 1 auto; min-width: 0; gap: 5px; overflow-x: auto; }
.study-practice-btn {
  display: inline-flex; flex: none; align-items: center; gap: 5px; margin: 0;
  min-height: 30px; padding: 5px 9px; border: 1px solid var(--border-strong);
  border-radius: 999px; font-size: .76rem; line-height: 1.2; white-space: nowrap;
}
.study-practice-btn svg { flex: none; }
.study-practice-btn[hidden] { display: none; }
.study-section {
  display: inline-flex; flex: none; align-items: center; justify-content: center; white-space: nowrap;
  min-height: 32px; padding: 5px 13px; border-radius: 999px;
  color: var(--text-soft); font-size: .85rem; font-weight: 600;
  text-decoration: none;
}
.study-section:hover { background: var(--surface-2); color: var(--text); text-decoration: none; }
.study-section.is-active { background: var(--accent-soft); color: var(--accent-strong); }
.study-section:focus-visible, .study-practice-btn:focus-visible, .view-opt:focus-visible {
  outline: 2px solid var(--accent-strong); outline-offset: 2px;
}
.study-level { font-weight: 700; color: var(--text); font-size: .88rem; }
.study-session-label { color: var(--text-soft); font-size: .85rem; }
.study-session .day-bar.study-bar {
  position: relative; top: auto; display: flex; flex-wrap: wrap;
  border-radius: var(--radius) var(--radius) 0 0; gap: 8px 10px;
}
.study-session .study-bar .study-level { margin-inline-end: 12px; }
.study-session .study-bar .day-bar-right { display: flex; margin-left: auto; }
.study-session .study-bar .day-bar-tools { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.study-session .study-bar .day-bar-more { display: none; }
.study-session .sp-deckhost .ak-top { display: none; }
.study-session [data-study-tools]:empty { display: none; }
.study-session .ak-newlimit-label[hidden] { display: none; }
.study-session .ak-newlimit-label { display: inline-flex; }
.study-session .sp-deckhost.ak-listing { height: auto; }
.day-deck-action:has(.ak-answer[hidden]) { display: none; }
@media (max-width: 560px) {
  .study-toolbar { padding: 9px 0 6px; gap: 6px; }
  .study-sections { gap: 2px; }
  .study-practice-btn { padding: 5px 7px; font-size: .7rem; }
  .study-section { padding: 6px; min-height: 34px; font-size: .7rem; }
  /* Keep the level and day together on the left, with the view and settings
     controls at the right edge. The day column holds the space between them. */
  .study-session .day-bar.study-bar {
    display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas: "level day tools";
    flex-wrap: nowrap; min-height: 40px; padding: 4px 0 6px; gap: 8px;
  }
  .study-session .study-bar .study-level {
    grid-area: level; justify-self: start; color: var(--text-soft); font-size: .7rem; letter-spacing: .035em;
  }
  .study-session .study-bar .study-session-label { grid-area: day; justify-self: start; font-size: .72rem; }
  .study-session .study-bar .day-pick-group {
    grid-area: day; order: 0; justify-self: start; min-width: 0; justify-content: flex-start; gap: 0;
  }
  .study-session .study-bar .sp-day-now { padding-inline: 3px; font-size: .74rem; }
  /* Keep the view controls grouped in their right-hand grid cell. */
  .study-session .day-bar.study-bar .day-bar-right {
    grid-area: tools; display: flex; order: 0; justify-self: end; margin-left: 0;
  }
  .study-session .study-bar .day-rail-gap { display: none; }
  .study-session .study-bar > .day-bar-tools { grid-area: tools; justify-self: end; margin-left: 0; }
  .study-session .study-bar .day-bar-tools { gap: 2px; }
  .study-session .study-bar .day-arrow { width: 24px; height: 28px; }
  .study-session .study-bar .view-switch {
    gap: 0; padding: 1px; border-color: transparent; background: transparent;
  }
  .study-session .study-bar .view-opt {
    min-height: 26px; padding: 2px 7px; font-size: .66rem;
  }
  .study-session .study-bar .view-opt.is-active {
    background: var(--surface-2); color: var(--text); box-shadow: none;
  }
  .study-session .study-bar .sp-gear {
    width: 28px; height: 28px; border-color: transparent;
    border-radius: 999px; background: transparent;
  }
  .study-session .study-bar .sp-gear svg { width: 15px; height: 15px; }
  .study-session .study-bar .day-prog { bottom: 0; height: 1px; background: var(--border); }
  .study-session .study-bar .day-prog-fill { opacity: .72; }
  .study-session .ak-newlimit-label { font-size: .72rem; }
}
@media (max-width: 360px) {
  .study-practice-btn { width: 30px; justify-content: center; padding: 5px; }
  .study-practice-btn span { display: none; }
}
/* The claim prompt rides above the bar, so it carries the padding the card
   itself no longer has. */
.sp-day > .sp-claim { margin: 16px 18px 0; }

/* The frame. When the day is taller than the screen, app.js (sizeDayFrame)
   measures what is above the card and sets the height, so the session is one
   screen with no page scroll behind it; all that belongs here is the ceiling
   and the fact that the body is the part that scrolls. A day that fits keeps
   its own height. */
@media (min-width: 561px) {
  .sp-day { max-height: 900px; }
  .day-body { overflow-y: auto; overscroll-behavior: contain; }
}

/* A. Day bar. One row and the whole of the day's chrome: which day this is,
   the way to another one, and how the day is shown. Two full-width rows of
   controls used to sit between the reader and the item they came to study;
   how far through the day is runs as a hairline along the bar's bottom edge
   rather than taking a slot of its own. */
.day-bar {
  position: sticky; top: var(--header-h); z-index: 6;
  display: flex; align-items: center; gap: 6px 12px; min-height: 50px;
  padding: 7px 14px; border-bottom: 1px solid var(--border);
  background: var(--surface); border-radius: var(--radius) var(--radius) 0 0;
}
.day-pick-group { flex: none; display: inline-flex; align-items: center; gap: 2px; }
.day-arrow { width: 30px; height: 30px; padding: 0; justify-content: center; }
.day-when { flex: none; font-size: .82rem; font-weight: 500; color: var(--text-soft); white-space: nowrap; }
/* Holds the right-hand group against the right edge. */
.day-rail-gap { flex: 1 1 auto; }
/* One line for the day: how much of its list carries a mark — not a
   plan-wide total, which read as today's target and always said 0%. It rides
   on the bar's bottom edge: a progress bar is a reading, not a control, and
   it does not need a slot of its own to be one. */
.day-prog { position: absolute; left: 0; right: 0; bottom: -1px; height: 2px; background: var(--surface-2); overflow: hidden; }
.day-prog-fill { display: block; height: 100%; background: var(--accent); transition: width .25s cubic-bezier(.4, 0, .2, 1); }
.day-bar-right { flex: none; display: inline-flex; align-items: center; gap: 6px; }
.day-bar-tools { display: inline-flex; align-items: center; gap: 6px; }
.day-bar-right .recall-toggle { flex: none; padding: 5px 10px; font-size: .82rem; }
.sp-gear { width: 32px; height: 32px; }
/* The one control that is about the bar rather than the day: it unfolds it.
   Phones only — above 620px the bar is one row and already open. */
.day-bar-more { display: none; }

/* The header scrolls away with the page on a phone (02-header.css), so the
   bar has nothing to clear and goes to the top of the viewport itself. */
@media (max-width: 720px) {
  .day-bar { top: 0; }
}

/* B. The view switch: the same day as cards to answer, or as a list to
   read. */
.view-switch {
  flex: none;
  display: inline-flex; align-items: center; gap: 2px; padding: 2px;
  border: 1px solid var(--border); border-radius: 999px; background: var(--surface-2);
}
.view-opt {
  padding: 4px 12px; border: 0; border-radius: 999px; background: none;
  font: inherit; font-size: .78rem; font-weight: 600; color: var(--text-faint);
  white-space: nowrap; cursor: pointer;
}
.view-opt:hover { color: var(--text); }
.view-opt.is-active { background: var(--accent); color: var(--accent-contrast); }

/* C. Day body. It takes every pixel the bar and the buttons do not, and it is
   the only part of the card whose contents change. */
.day-body { flex: 1 1 auto; min-height: 0; padding: 14px 18px; }
.day-body > .sp-block:first-child, .day-body > .recall-hint:first-child + .sp-block { margin-top: 0; padding-top: 0; border-top: 0; }
.day-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 100%; padding: 34px 0; text-align: center; }
.day-empty h3 { margin: 0 0 6px; font-family: var(--sans); font-size: 1.02rem; font-weight: 600; letter-spacing: -.01em; }
.day-empty .sp-note { margin: 0; }

/* C2. The questions over what today was spent on. One section, shown in two
   places: under a finished day and under the list. Five at a time, stacked
   down the page rather than one card at a time, because reading the next
   question is how you decide whether you want another five. */
/* A finished day: the one line that says so, and its questions under it, on
   one column so the line and the heading share a left edge. Not .day-done:
   that name is taken by the Done pill on a list row, which is a 999px-radius
   bordered button, and it drew itself around this whole block. */
.day-finish { max-width: 46rem; margin: 0 auto; }
.day-clear-line { margin: 2px 0 22px; font-family: var(--sans); font-size: 1.02rem; font-weight: 600; }
.day-practice { max-width: 46rem; margin: 0 auto; padding: 4px 0 8px; scroll-margin-top: 16px; }
.day-finish .day-practice { max-width: none; padding-top: 0; }
.sp-block + .day-practice { margin-top: 26px; padding-top: 22px; border-top: 1px solid var(--border); }
.dp-head {
  margin: 0 0 18px; font-family: var(--sans); font-size: 1.02rem; font-weight: 600;
  letter-spacing: -.01em;
}
/* How many there are, under the heading, and how it went, under the last one. */
.dp-count { margin: -12px 0 18px; font-size: .82rem; color: var(--text-soft); }
.practice-modal .dp-count { margin-top: 0; }
.dp-score { margin: 26px 0 0; font-size: .95rem; font-weight: 600; color: var(--text); }
.dp-score[hidden] { display: none; }
.dp-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 30px; }
/* The one thing to press in the section, and the only one: an outlined button
   rather than a filled one, because the day's own primary is in the bar under
   it. Full width, so it reads as the end of the five above it. */
.dp-more { margin-top: 30px; }
.dp-more .btn-ghost { width: 100%; justify-content: center; }

/* One question: the stem with its number, four options that are themselves
   the buttons, and the reason underneath once one is pressed. Answered
   options keep their place and take a colour; the paper does not move under
   the answer. */
/* What the question wants, in the words the exam paper uses, over the stem
   and on its left edge. */
.dq-ask { margin: 0 0 2px calc(1.6em + 10px); font-size: .8rem; color: var(--text-soft); }
.dq-stem {
  display: flex; gap: 10px; margin: 0 0 14px;
  font-family: var(--ja); font-size: 1.1rem; line-height: 1.95; color: var(--text);
}
.dq-n {
  flex: none; min-width: 1.6em; font-family: var(--sans); font-size: .9rem; font-weight: 600;
  line-height: 1.95; color: var(--text-faint);
}
.dq-stem ruby rt { font-size: .5em; color: var(--text-soft); }
.dq-opts { list-style: none; margin: 0; padding: 0 0 0 calc(1.6em + 10px); display: flex; flex-direction: column; gap: 8px; }
.dq-opt {
  display: flex; width: 100%; gap: 10px; align-items: baseline; text-align: left;
  padding: 10px 14px; border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--text); font: inherit; cursor: pointer;
  transition: border-color .12s ease, background .12s ease;
}
.dq-opt:hover:not(:disabled) { border-color: var(--accent); background: var(--surface-2); }
.dq-opt:disabled { cursor: default; }
.dq-num { flex: none; font-size: .82rem; font-weight: 600; color: var(--text-faint); }
.dq-text { font-family: var(--ja); font-size: 1.02rem; }
.dq-opt.is-right { border-color: var(--st-done); background: color-mix(in srgb, var(--st-done) 9%, var(--surface)); }
.dq-opt.is-right .dq-num { color: var(--st-done); }
.dq-opt.is-wrong { border-color: var(--n1); background: color-mix(in srgb, var(--n1) 8%, var(--surface)); }
.dq-opt.is-wrong .dq-num { color: var(--n1); }
.dq-explain {
  margin: 14px 0 0 calc(1.6em + 10px); padding-left: 14px;
  border-left: 2px solid var(--border-strong);
  font-size: .9rem; line-height: 1.65; color: var(--text-soft);
}
.dq-explain p { margin: 0; }
.dq-explain p + p { margin-top: 6px; }
.dq-link {
  display: inline-flex; align-items: center; gap: 5px; margin-top: 8px;
  font-size: .9rem; font-weight: 600; color: var(--accent-strong);
}
.dq-link [lang="ja"] { font-family: var(--ja); }
.dq-link svg { flex: none; }

@media (max-width: 560px) {
  .dq-stem { font-size: 1.04rem; line-height: 1.85; gap: 8px; }
  .dq-n { min-width: 1.4em; line-height: 1.85; }
  .dq-opts { padding-left: calc(1.4em + 8px); }
  .dq-ask { margin-left: calc(1.4em + 8px); }
  .dq-explain { margin-left: calc(1.4em + 8px); }
  .dq-opt { padding: 10px 12px; }
}

/* D. Action bar. The deck's own answer controls mount in it, so the card and
   the buttons under it share one bottom edge and flipping a card moves
   neither. Sticky at the foot of the card. */
.day-action {
  position: sticky; bottom: 0; z-index: 5; flex: none;
  display: flex; align-items: center; gap: 10px 14px; flex-wrap: wrap;
  padding: 9px 16px; border-top: 1px solid var(--border); background: var(--surface);
  border-radius: 0 0 var(--radius) var(--radius);
}
.day-action-left { font-size: .82rem; font-weight: 600; color: var(--text-soft); }
.day-action-right { display: inline-flex; align-items: center; gap: 8px; margin-left: auto; }

/* A section page's day is one section's, so where the combined day offers
   Study ahead this offers that section's new cards a day. It rides in the
   action bar, and in the deck's finish screen when the section's cards run
   out, so it sizes itself against both. */
.day-newset { display: inline-flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: center; }
.day-newset-label {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: .82rem; font-weight: 600; color: var(--text-soft);
}
.day-newset-input {
  width: 4.4em; padding: 7px 8px; border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm); background: var(--surface); color: var(--text);
  font: inherit; font-weight: 600; text-align: center;
}
.day-newset-input:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: 1px; }
.day-newset-save { flex: none; padding: 9px 18px; font-size: .86rem; }
.ak-finish .day-newset { margin-top: 8px; }

/* E. The same card, before there is a day in it. The page ships this markup
   (planToday, scripts/build/pages/study-plan.js) so the shell is on screen
   before app.js runs, and every held state after it — auth, the schedule
   coming down, the level lists — keeps the same blocks rather than swapping
   in a spinner of a different shape. Nothing here says it is loading: the
   card is already the size and shape of the day, and it fills in.  */
.sp-skel .day-bar, .sp-skel .day-action { pointer-events: none; }
.skel {
  display: block; border-radius: 999px; background: var(--surface-2);
  animation: skel-pulse 1.3s ease-in-out infinite;
}
.skel-arrow { width: 30px; height: 30px; border-radius: 50%; }
.skel-daynum { width: 54px; height: 14px; margin: 0 6px; }
.skel-switch { width: 102px; height: 26px; }
.skel-gear { width: 32px; height: 32px; border-radius: 50%; }
/* The hairline runs as an indeterminate sweep instead of a reading, because
   there is nothing to read yet. */
.sp-skel .day-prog-fill { width: 34%; background: var(--border-strong); transition: none;
  animation: skel-sweep 1.5s cubic-bezier(.4, 0, .2, 1) infinite; }
.sp-skel .day-body { display: flex; }
/* The study pass's column and its minimum height, so the card the lists land
   in is the card that was already there. */
.skel-pass {
  display: flex; flex-direction: column; gap: 18px;
  width: 100%; max-width: 760px; min-height: var(--deck-stage); margin: 0 auto;
}
.skel-pass-head { display: flex; align-items: center; gap: 10px; }
.skel-chip { width: 62px; height: 20px; }
.skel-meta { width: 94px; height: 12px; }
.skel-pass-body { flex: 1 1 auto; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 16px; }
.skel-term { width: min(320px, 62%); height: 46px; border-radius: var(--radius-sm); }
.skel-reading { width: min(180px, 38%); height: 16px; }
.skel-mean { width: min(260px, 52%); height: 20px; }
.skel-status { width: 90px; height: 12px; }
.skel-btn { width: 118px; height: 34px; }
@keyframes skel-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }
@keyframes skel-sweep { 0% { transform: translateX(-110%); } 100% { transform: translateX(320%); } }
@media (prefers-reduced-motion: reduce) {
  .skel { animation: none; }
  .sp-skel .day-prog-fill { animation-duration: 3.6s; }
}

/* The deck, and it is the day. Its card stays in the body while its answer
   controls are moved into .day-deck-action, the card's own footer. */
.sp-deckhost { position: relative; margin: 0; max-width: none; }
.sp-deckhost.ak { gap: 0; margin-bottom: 0; }
.sp-deckhost .ak-top { position: absolute; top: 0; right: 0; width: auto; z-index: 2; }
/* The schedule sets the day's new cards, so how many of them to draw is not a
   question this screen asks. It is still on the flashcard pages' own decks. */
.sp-deckhost .ak-newlimit-label { display: none; }
/* The card is one box on both faces, well short of the screen, so the buttons
   sit right under it and turning a card moves nothing. A back taller than the
   box scrolls inside it. */
.sp-deckhost .ak-stage:not([hidden]) {
  height: var(--deck-stage); overflow-y: auto; overscroll-behavior: contain;
  display: flex; flex-direction: column;
  /* `safe` so an answer taller than the box starts at its term rather than
     centring the overflow and cutting the top off. */
  justify-content: center; justify-content: safe center;
}
.sp-deckhost .fc-front { min-height: 0; }

/* One anchored footer for both sides of the deck: the queue counts, then the
   control, which is Show answer before the flip and the grade strip after it.
   Both faces fill the same box (.ak-choice, 31-srs-deck.css), so this row is
   laid out once and turning a card moves nothing in it. */
.day-deck-action { flex-wrap: nowrap; }
.day-deck-action .ak-answer {
  position: static; z-index: auto;
  /* The counts keep their line and never shrink; the control takes what is
     left of the row up to its full width, and drops to a row of its own when
     that is no longer enough for both. */
  display: flex; flex-direction: row; flex-wrap: wrap; align-items: center;
  width: 100%; min-height: 0; gap: 14px 16px;
  padding: 0; background: none;
}
.day-deck-action .ak-answer[hidden] { display: none; }
.day-deck-action .ak-counts {
  position: static; flex: 0 0 auto; min-width: 0; margin: 0;
  flex-wrap: wrap; gap: 2px 8px; line-height: 1.25;
}
.day-deck-action .ak-choice { flex: 1 1 380px; margin-left: auto; }
.day-deck-action .ak-show { min-width: 0; padding: 10px 22px; font-size: var(--step); }

@media (max-width: 560px) {
  .sp-day { --deck-stage: clamp(260px, 48vh, 350px); }
  /* Framed only when the day is taller than the screen: then the card gives
     up height rather than pushing the buttons off it. */
  .sp-day.is-framed .sp-deckhost { height: 100%; }
  .sp-day.is-framed .sp-deckhost.ak { display: flex; flex-direction: column; }
  .sp-day.is-framed .sp-deckhost .ak-stage { flex: 1 1 auto; min-height: 0; }
  /* No room for the counts beside a full-width grade strip, so the control
     always takes the row below them. */
  .day-deck-action .ak-answer { gap: 14px; }
  .day-deck-action .ak-choice { flex-basis: 100%; }
  .day-deck-action .ak-counts { font-size: .68rem; }
  .day-deck-action .ak-choice { max-width: none; }
  .day-deck-action .ak-show { padding: 10px 20px; }
  /* All five outcomes fit one fixed-height row here. Done no longer drops
     underneath and pushes the grade buttons away from the footer edge. */
  .day-deck-action .ak-grades {
    grid-template-columns: repeat(5, minmax(0, 1fr));
    width: 100%; gap: 4px;
  }
  .day-deck-action .ak-grade { padding: 6px 2px 7px; }
  .day-deck-action .ak-grade-name { font-size: .74rem; }
  .day-deck-action .ak-grade-ivl { font-size: .66rem; }
  .day-deck-action .ak-done { grid-column: auto; min-width: 0; padding: 5px 3px; }
}
/* The deck is a column of its own: in a frame the card shrinks to the room
   the day card has left. The controls are outside this body, in the day's
   footer. */
@media (min-width: 561px) {
  .sp-deckhost { height: 100%; }
  .sp-deckhost.ak { display: flex; flex-direction: column; }
  .sp-deckhost .ak-stage { flex: 1 1 auto; min-height: 0; }
  /* The deck pages' sizes (31-srs-deck.css) were set for a card the height of
     the window. In this smaller box both faces come down to reading size. */
  .sp-deckhost .fc-front { gap: 14px; font-size: clamp(4.2rem, 6.5vw, 5.6rem); }
  .sp-deckhost.ak-words .fc-front { font-size: clamp(2rem, 4vw, 3.2rem); }
  .sp-deckhost[data-ak-kind="g"] .fc-front { font-size: clamp(1.5rem, 2.8vw, 2.2rem); }
  .sp-deckhost .fc-card.is-flipped .fc-back { gap: 12px; padding-top: 4px; }
  .sp-deckhost .fc-back-header { gap: 7px; padding-bottom: 12px; }
  .sp-deckhost .fc-back-glyph { font-size: 3.2rem; }
  .sp-deckhost.ak-words .fc-back-glyph { font-size: 2.2rem; }
  .sp-deckhost[data-ak-kind="g"] .fc-back-glyph { font-size: 1.7rem; }
  .sp-deckhost .fc-back-meanings { font-size: 1.2rem; }
  .sp-deckhost .ak-back-reading { font-size: 1.05rem; }
  .sp-deckhost .fc-back-readings { gap: 8px 30px; }
  .sp-deckhost .fc-back-reading-val { font-size: 1.05rem; }
  .sp-deckhost .fc-back-examples { margin-top: 0; padding-top: 10px; }
  .day-deck-action .ak-grade { padding: 6px 8px 7px; }
}

/* Previous/next days keep the item and meaning prominent. The saved schedule
   is a small note beneath the meaning, outside its blur/unblur control. */
.day-schedule-table { width: 100%; table-layout: fixed; border-collapse: collapse; font-size: .88rem; }
.day-schedule-table th, .day-schedule-table td {
  padding: 11px 12px; border-bottom: 1px solid var(--border);
  text-align: left; vertical-align: top; overflow-wrap: anywhere;
}
.day-schedule-table thead th { font-size: .74rem; font-weight: 600; color: var(--text-faint); }
.day-schedule-table th:first-child { width: 36%; padding-left: 0; }
.day-schedule-table td:last-child, .day-schedule-table th:last-child { padding-right: 0; }
.day-schedule-table tbody th { font-weight: 500; }
.day-schedule-table td { color: var(--text-soft); }
.day-schedule-term { display: block; font-family: var(--ja); font-size: 1.05rem; color: var(--text); }
.day-schedule-reading { display: block; margin-top: 3px; font-family: var(--ja); font-size: .82rem; color: var(--text-soft); }
.day-schedule-date {
  display: block; margin-top: 5px; font-size: .65rem; line-height: 1.4;
  font-weight: 400; color: var(--text-faint); font-variant-numeric: tabular-nums;
}
@media (max-width: 560px) {
  .day-schedule-table { font-size: .8rem; }
  .day-schedule-table th, .day-schedule-table td { padding: 10px 6px; }
  .day-schedule-table thead th { font-size: .7rem; }
  .day-schedule-term { font-size: .95rem; }
  .day-schedule-reading { font-size: .76rem; }
}

/* ── One card for every list item ─────────────────────────────
   A list entry has two jobs: make the material easy to scan, then make its
   review actions easy to hit. Keep those jobs on separate rows so the term,
   reading and meaning never have to surrender their space to five buttons. */
.day-list { display: grid; gap: 12px; list-style: none; margin: 0; padding: 0; }
.day-item {
  display: grid; align-items: start; gap: 5px 20px;
  grid-template-columns: minmax(9rem, .9fr) minmax(0, 1.6fr) auto;
  grid-template-areas:
    "term meaning tools"
    "reading meaning tools"
    "example example example"
    "controls controls controls";
  min-width: 0; padding: 16px 18px;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface);
  transition: background-color .18s cubic-bezier(.4, 0, .2, 1), border-color .18s cubic-bezier(.4, 0, .2, 1), box-shadow .18s cubic-bezier(.4, 0, .2, 1);
}
.day-item:hover { border-color: var(--border-strong); box-shadow: var(--shadow-sm); }
.day-item-term {
  grid-area: term; min-width: 0; align-self: start;
  font-family: var(--ja); font-weight: 700; font-size: var(--term-size, 1.16rem); line-height: 1.35;
  color: var(--text); text-decoration: none; overflow-wrap: anywhere;
}
.day-item-term:hover { color: var(--accent-strong); text-decoration: underline; }
.day-item-read {
  grid-area: reading; min-width: 0; display: flex; flex-direction: column; gap: 3px;
  font-family: var(--ja); font-size: .98rem; line-height: 1.45; color: var(--text-soft);
}
.day-item-read:empty { display: none; }
.day-item-mean {
  grid-area: meaning; min-width: 0; display: flex; flex-direction: column; gap: 4px;
  font-size: .95rem; line-height: 1.5; color: var(--text-soft);
}
.day-item-label {
  font-family: var(--sans); font-size: .66rem; font-weight: 700; line-height: 1.2;
  letter-spacing: .07em; text-transform: uppercase; color: var(--text-faint);
}
.day-item-meta { display: flex; flex-wrap: wrap; gap: 0 10px; font-style: normal; font-family: var(--sans); font-size: .72rem; color: var(--text-faint); }
.day-item-tools { grid-area: tools; display: inline-flex; align-items: center; justify-content: flex-end; gap: 6px; }
/* Two things to do with a row besides answer it, each said in a word: an icon
   alone left both to be guessed at. */
.day-item-tools .day-item-draw, .day-item-go {
  display: inline-flex; align-items: center; justify-content: center; gap: 5px; flex: none;
  width: auto; height: 32px; padding: 0 11px; border-radius: 999px;
  font-family: var(--sans); font-size: .76rem; font-weight: 600; line-height: 1;
}
.day-item-go {
  border: 1px solid var(--border);
  color: var(--text-soft); text-decoration: none;
  transition: background .18s cubic-bezier(.4, 0, .2, 1), color .18s cubic-bezier(.4, 0, .2, 1);
}
.day-item-go:hover { border-color: var(--accent); background: var(--accent-soft); color: var(--accent-strong); text-decoration: none; }
/* Grammar examples get their own calm reading line instead of making the
   meaning column taller and narrower. */
.day-item-ex {
  grid-area: example; margin: 9px 0 0; padding-top: 11px; border-top: 1px dashed var(--border);
  display: flex; flex-direction: column; gap: 3px; font-size: .9rem; line-height: 1.5;
}
.day-item-ex [lang="ja"] { font-family: var(--ja); }
.day-item-ex .recall-hide { color: var(--text-soft); font-size: .84rem; }
/* A row that has been answered today carries a mark on its leading edge, so a
   list half worked shows which half. */
.day-item.is-studied { box-shadow: inset 3px 0 0 var(--st-learning); }
/* Shown only while the answers are hidden. */
.recall-hint { display: none; margin: 0 0 12px; font-size: .82rem; color: var(--text-soft); }
.recall-scope.is-recall .recall-hint { display: block; }
.ak-list-top .recall-hint { margin: 0 auto 0 0; align-self: center; }
/* The glyph is the item, so it leads at the size it is read at. */
.day-item[data-kind="k"] { --term-size: 2.1rem; }
.day-item[data-kind="a"] { --term-size: 2.25rem; }
.day-item[data-kind="g"] { --term-size: 1.1rem; }
/* A kana row has no reading of its own: the syllable it spells is the answer
   and sits in the meaning column, so the reading column gives up its width. */
.day-item.is-kana .day-item-read { display: none; }
/* A kanji is one glyph wide, so its readings sit beside it rather than under
   it and the row is as tall as the glyph. */
@media (min-width: 621px) {
  .day-item[data-kind="k"] {
    grid-template-columns: 3.2rem minmax(8rem, .8fr) minmax(0, 1.5fr) auto;
    grid-template-areas:
      "term reading meaning tools"
      "example example example example"
      "controls controls controls controls";
    align-items: center;
  }
  .day-item[data-kind="k"] .day-item-term { line-height: 1.1; align-self: center; }
  .day-item[data-kind="k"] .day-item-read, .day-item[data-kind="k"] .day-item-mean { align-self: start; }
}
.day-item.is-kana .day-item-mean { font-size: 1.05rem; font-weight: 600; }

/* Grammar has no separate reading; its meaning gets the full answer column. */
.day-item[data-kind="g"] .day-item-read { display: none; }
/* Done is the tinted row and the pressed button, not a fade past the point of
   being readable. */
.day-item.is-done { background: var(--surface-2); }
.day-item.is-done .day-item-term, .day-item.is-done .day-item-read, .day-item.is-done .day-item-mean { opacity: .85; }

/* Review controls live below the material. The label and status stay compact;
   the buttons share the rest of the card instead of squeezing its content. */
.day-item-ctl {
  grid-area: controls; min-width: 0; display: grid;
  grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: 10px 16px;
  margin-top: 9px; padding-top: 12px; border-top: 1px solid var(--border);
}
.day-item-review-head { display: inline-flex; align-items: center; gap: 7px; }
.day-item-review-label {
  font-size: .68rem; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--text-faint); white-space: nowrap;
}
.day-grades {
  display: grid; grid-template-columns: repeat(4, minmax(5.5rem, 1fr)) auto;
  align-items: stretch; gap: 7px; width: 100%; min-width: 0;
}
/* A concealed recall card has no actionable controls yet. Removing the whole
   footer leaves more room to read; revealing either answer restores it. */
.recall-scope.is-recall .day-item:not(.is-open):not(.is-done) .day-item-ctl { display: none; }
.day-grades .ak-grade {
  min-width: 0; min-height: 40px; flex-direction: row; align-items: baseline; justify-content: center; gap: 5px;
  padding: 7px 9px; border-radius: var(--radius-sm); cursor: pointer;
}
.day-grades .ak-grade-name { font-size: .78rem; }
/* The interval is the only part of a grade that changes width, so it is given
   a width of its own: the strip is then the same size on every row and the
   meanings beside it line up down the list. */
.day-grades .ak-grade-ivl { font-size: .7rem; min-width: 3.2em; text-align: center; }
.day-grades .ak-grade:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: 2px; }
/* The fifth button, and the only one that is not a grade. Pressed while it
   holds, and a settled row has nothing left to answer, so the four stand down
   and the row keeps showing the one mark it carries even in recall mode. */
.day-done {
  min-height: 40px; border: 1px solid var(--border-strong); background: var(--surface); color: var(--text-soft);
  border-radius: var(--radius-sm); padding: 7px 13px; font: inherit; font-size: .76rem; font-weight: 600;
  line-height: 1.5; white-space: nowrap; cursor: pointer;
  transition: background-color .18s cubic-bezier(.4, 0, .2, 1), border-color .18s cubic-bezier(.4, 0, .2, 1), color .18s cubic-bezier(.4, 0, .2, 1);
}
.day-done:hover { border-color: var(--st-done); color: var(--st-done); }
.day-done[aria-pressed="true"] { background: var(--st-done); border-color: transparent; color: var(--accent-contrast); }
.day-done:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: 2px; }
.day-item.is-done .day-grades { grid-template-columns: auto; justify-content: end; }
.day-item.is-done .day-grades .ak-grade { display: none; }

/* One control cluster, right-aligned, wherever an item can be marked. */
.item-mark { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.item-mark-pair { display: inline-flex; align-items: center; gap: 6px; }

/* One row is one row only while it fits. Below that the bar drops what it can
   spare, widest first. */
@media (max-width: 1080px) {
  .day-when { display: none; }
}
@media (max-width: 1000px) {
  .day-bar-right .recall-label { display: none; }
  .day-bar-right .recall-toggle { padding: 5px 8px; }
}
@media (max-width: 900px) {
  .day-bar { flex-wrap: wrap; row-gap: 4px; }
  .day-bar-right { margin-left: auto; }
}
/* Phones: content becomes one clear vertical reading order. Review choices
   become a two-column touch grid, with Done on a full-width final row. */
@media (max-width: 620px) {
  .day-list { gap: 14px; }
  .day-item {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "term tools"
      "reading reading"
      "meaning meaning"
      "example example"
      "controls controls";
    gap: 8px 12px; padding: 15px 14px;
  }
  .day-item-term { align-self: center; font-size: var(--term-size, 1.2rem); }
  .day-item-read, .day-item-mean { gap: 4px; }
  .day-item-mean { padding-top: 3px; }
  .day-item-tools { align-self: start; gap: 7px; }
  .day-item-tools .day-item-draw, .day-item-go { height: 36px; }
  .day-item-ex { margin-top: 3px; padding-top: 12px; }
  .day-item-ctl {
    grid-template-columns: minmax(0, 1fr); gap: 9px;
    margin-top: 4px; padding-top: 14px;
  }
  .day-item-review-head { justify-content: space-between; }
  .day-grades { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
  .day-grades .ak-grade {
    min-height: 46px; justify-content: space-between; padding: 9px 12px;
  }
  .day-grades .ak-grade-name { font-size: .78rem; }
  .day-grades .ak-grade-ivl { min-width: 0; font-size: .7rem; }
  .day-done { grid-column: 1 / -1; min-height: 44px; padding: 9px 12px; }
}
/* ── Phones: the day bar folds ─────────────────────────────
   Open, the bar carries the day and its arrows, Cards/List, recall and the
   gear, above the card the reader came for. Folded, it is which day this is,
   the view switch and the chevron that brings the rest back. It stays where
   it was left while the page is up and opens folded on the next one.
   Study sessions have their own level/day/tools layout: exclude .study-bar
   so these legacy grid positions cannot put the arrows over the level. */
@media (max-width: 620px) {
  .day-bar-more { display: inline-flex; width: 30px; height: 30px; padding: 0; justify-content: center; }
  .day-bar-more svg { transition: transform .18s cubic-bezier(.4, 0, .2, 1); }
  .day-bar-more[aria-expanded="true"] svg { transform: rotate(180deg); }
  /* Open controls have two intentional rows: day navigation plus the fold
     button, then the view tools. This avoids the accidental wrapping that
     made the expanded bar look like a dropdown. */
  .day-bar:not(.is-tight, .study-bar) {
    display: grid; grid-template-columns: minmax(0, 1fr) auto;
    align-items: center; gap: 8px 10px;
  }
  .day-bar:not(.is-tight, .study-bar) .day-pick-group { grid-column: 1; grid-row: 1; justify-self: start; }
  .day-bar:not(.is-tight, .study-bar) .day-bar-right { display: contents; }
  .day-bar:not(.is-tight, .study-bar) .day-bar-more { grid-column: 2; grid-row: 1; justify-self: end; }
  .day-bar:not(.is-tight, .study-bar) .day-bar-tools {
    grid-column: 2; grid-row: 2; justify-self: end;
  }
  .day-bar:not(.is-tight, .study-bar) .day-rail-gap { display: none; }
  .day-bar.is-tight { min-height: 0; gap: 4px 8px; }
  .day-bar.is-tight .day-arrow { display: none; }
  .day-bar.is-tight .day-pick-group { order: 0; }
  .day-bar.is-tight .day-bar-right { order: 2; margin-left: auto; }
  /* Folded, the bar keeps one control: Cards/List, or on a day being looked
     back at, the way back to today. */
  .day-bar.is-tight .day-bar-tools > * { display: none; }
  .day-bar.is-tight .day-bar-tools > :first-child { display: inline-flex; }
  /* The skeleton folds with it, so the bar is not one height before the day
     lands and another after: one control, the width of the view switch. */
  .sp-skel .day-bar.is-tight .skel-gear { display: none; }
}
@media (max-width: 520px) {
  /* The view tools share the navigation row rather than taking one of their
     own, so the panel stays one row tall. */
  .day-bar:not(.is-tight, .study-bar) { grid-template-columns: auto minmax(0, 1fr) auto; }
  .day-bar:not(.is-tight, .study-bar) .day-bar-tools {
    grid-column: 2; grid-row: 1; width: auto; justify-self: end;
  }
  .day-bar:not(.is-tight, .study-bar) .day-bar-more { grid-column: 3; }
}
@media (max-width: 360px) {
  /* Only the narrowest screens need a third row for the view tools. */
  .day-bar:not(.is-tight, .study-bar) { grid-template-columns: minmax(0, 1fr) auto; }
  .day-bar:not(.is-tight, .study-bar) .day-bar-more { grid-column: 2; }
  .day-bar:not(.is-tight, .study-bar) .day-bar-tools {
    grid-column: 1 / -1; grid-row: 3; width: 100%; justify-content: flex-end;
  }
}
/* Phones: the same bar, tightened. */
@media (max-width: 560px) {
  /* No frame. The card's border, its fill and its 22px of padding are width
     the day does not have to give and a ring around content that already
     fills the screen, so the day runs to the page's own gutter and the parts
     that were sticky inside the frame sit on the page's ground instead. */
  .sp-card.sp-day { border: 0; border-radius: 0; box-shadow: none; background: none; padding: 0; }
  .sp-card.sp-day > .sp-claim { margin: 12px 0 0; }
  .page-study-today main.shell { padding: 8px 12px 20px; }
  .page-study-today .sp-app-today { margin-bottom: 0; }
  .day-bar { min-height: 44px; padding: 5px 0; background: var(--bg); border-radius: 0; }
  /* The page gutter belongs to the list content, not its session controls.
     Bleed the top rail across it, then restore the inset inside the rail so
     the progress track and separator reach both viewport edges. */
  .page-study-today .study-session .day-bar.study-bar {
    width: calc(100% + 24px); margin-inline: -12px; padding-inline: 12px;
  }
  .day-bar:not(.is-tight, .study-bar) { padding-block: 6px 8px; gap: 6px 8px; }
  .day-body { padding: 12px 0; }
  /* sizeDayFrame can now use the live mobile viewport, and this body is what
     scrolls in it. */
  .sp-day.is-framed .day-body { overflow-y: auto; overscroll-behavior: contain; }
  .day-action { padding: 10px 0; background: var(--bg); border-radius: 0; }
  .day-action-left { font-size: .72rem; }
  .day-bar-more { width: 28px; height: 28px; }
  .day-bar-tools { gap: 4px; }
  .sp-gear { width: 30px; height: 30px; }
  .view-opt { padding: 3px 8px; font-size: .68rem; }
  .item-mark { gap: 8px; }
  .day-pick-group { order: 1; }
  .day-bar-right { order: 2; margin-left: auto; }
}


.sp-chip { flex: none; font-size: .72rem; font-weight: 600; color: var(--text-soft); background: var(--surface-2); border-radius: 999px; padding: 3px 8px; white-space: nowrap; }

.sp-done h3 { margin: 0 0 6px; }


/* The day number is a reading, not a control: the arrows either side of it are
   the way between days. It keeps the title's type so the bar still opens on
   the thing that says where you are. */
.sp-day-now {
  display: inline-flex; align-items: center; padding: 1px 7px;
  font-size: 1rem; font-weight: 600; line-height: 1.15;
  color: var(--text); white-space: nowrap;
}
@media (max-width: 560px) {
  .sp-day-now { padding-inline: 5px; font-size: .82rem; }
}

.sp-editor-sum { margin: 0 0 12px; }

/* Where each section stands: finished / in the review pipeline / untouched.
   A single all-sections total said nothing, so it is gone.
   Three columns, not three sentences: the count is the number people look for,
   so it leads; "left" is the rest of the bar and needs no words; the review
   count only appears when there is something in review. */
.sp-stats { display: flex; align-items: stretch; }
.sp-tiles + .sp-stats { margin-top: 14px; }
.sp-stcol {
  flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: 4px;
  padding: 0 16px; border-left: 1px solid var(--border);
}
.sp-stcol:first-child { padding-left: 0; border-left: 0; }
.sp-stcol:last-child { padding-right: 0; }
.sp-st-label { font-size: .72rem; font-weight: 600; text-transform: uppercase; letter-spacing: .05em; color: var(--text-soft); }
.sp-st-nums { font-size: .82rem; line-height: 1.1; color: var(--text-soft); white-space: nowrap; }
.sp-st-nums b { font-size: 1.2rem; font-weight: 600; letter-spacing: -.01em; color: var(--text); }
.sp-st-bar { display: flex; height: 6px; margin-top: 2px; border-radius: 999px; background: var(--surface-2); overflow: hidden; }
.sp-st-seg { display: block; height: 100%; min-width: 3px; }
.sp-st-seg.is-done { background: var(--st-done); }
.sp-st-seg.is-review { background: var(--st-scheduled); }
/* Doubles as the key for the bar's second colour, which is why it carries a dot. */
.sp-st-rev { display: inline-flex; align-items: center; gap: 5px; font-size: .72rem; color: var(--text-soft); }
.sp-st-rev::before { content: ""; flex: 0 0 auto; width: 7px; height: 7px; border-radius: 50%; background: var(--st-scheduled); }
@media (max-width: 560px) {
  .sp-stcol { padding: 0 10px; }
  .sp-st-label { font-size: .72rem; letter-spacing: .03em; }
  .sp-st-nums { font-size: .82rem; }
  .sp-st-nums b { font-size: 1.05rem; }
}


/* Level-card tiles: the two numbers that decide whether to open today's list. */
/* auto-fit, not a hard 3: the card shows two tiles ("new today", "reviews
   due") and a fixed three-column grid left the third slot as dead space. */
.sp-tiles { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: 10px; margin: 14px 0 0; }
.sp-tile { border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--bg-elev); padding: 10px 12px; text-align: center; }
.sp-tile b { display: block; font-size: 1.5rem; font-weight: 600; line-height: 1.15; }
.sp-tile span { font-size: .72rem; font-weight: 600; text-transform: uppercase; letter-spacing: .05em; color: var(--text-soft); }
.sp-tile.is-due b { color: var(--st-due); }

/* ── Effort heatmap ──────────────────────────────────────────
   One square per calendar day, weeks as columns, five shades of the "done"
   green by how much that day held. It fills the available width on phones and
   scrolls when longer elsewhere; the current week always ends bottom-right. */
.hm { margin: 18px 0 0; }
.hm-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin: 0 0 8px; }
.hm-streak { font-size: var(--step); font-weight: 600; color: var(--st-done); }
.hm-meta { font-size: .82rem; color: var(--text-soft); }
.hm-scroll { overflow-x: auto; padding-bottom: 4px; }
.hm-grid { display: flex; gap: 3px; min-width: max-content; }
.hm-col { display: grid; grid-template-rows: 13px repeat(7, 13px); gap: 3px; }
.hm-month { font-size: .72rem; line-height: 1; color: var(--text-soft); white-space: nowrap; }
.hm-cell { width: 13px; height: 13px; border-radius: var(--radius-xs); background: var(--surface-2); }
.hm-cell[data-lv="1"] { background: color-mix(in srgb, var(--st-done) 28%, var(--surface-2)); }
.hm-cell[data-lv="2"] { background: color-mix(in srgb, var(--st-done) 52%, var(--surface-2)); }
.hm-cell[data-lv="3"] { background: color-mix(in srgb, var(--st-done) 76%, var(--surface-2)); }
.hm-cell[data-lv="4"] { background: var(--st-done); }
.hm-cell.hm-void { background: transparent; }
.hm-cell.is-today { box-shadow: 0 0 0 1.5px var(--accent); }
.hm-legend { display: flex; align-items: center; gap: 4px; margin: 8px 0 0; font-size: .72rem; color: var(--text-soft); }
@media (max-width: 560px) {
  .sp-tiles { gap: 8px; }
  .sp-tile b { font-size: 1.25rem; }
  .hm-scroll { overflow-x: visible; }
  .hm-grid { width: 100%; min-width: 0; }
  .hm-col { flex: 1 1 0; min-width: 0; grid-template-rows: 12px repeat(7, auto); }
  .hm-col .hm-cell { width: 100%; height: auto; aspect-ratio: 1; }
  .hm-legend .hm-cell { width: 12px; height: 12px; }
}

.sp-sect-meta { font-size: .72rem; font-weight: 400; color: var(--text-soft); }

.sp-upnext { margin: 18px 0 0; padding-top: 14px; border-top: 1px solid var(--border); }
.sp-upnext summary { cursor: pointer; font-weight: 600; font-size: .88rem; color: var(--accent-strong); }
.sp-up-table { border-collapse: collapse; margin-top: 10px; width: 100%; font-size: .82rem; }
.sp-up-table th, .sp-up-table td { border: 0; border-bottom: 1px solid var(--border); padding: 6px 14px 6px 0; text-align: left; }
.sp-up-table th { white-space: nowrap; color: var(--text-soft); font-size: .82rem; text-transform: uppercase; letter-spacing: .04em; }

.sp-golink-row { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-top: 14px; }
.sp-golink-row .sp-note { margin: 0; }
.sp-golink { display: inline-block; text-decoration: none; }

/* ── The account gate ───────────────────────────────────────────────
   Running a schedule needs an account, so the Today view has three states
   before it has a study day: waiting on auth, the gate, and (for a schedule
   started before the gate existed) the claim prompt below. */

/* Held state. Deliberately quiet — it is a fraction of a second for anyone
   with a live session, and a spinner that announces itself would flash. */
.sp-waiting { display: flex; align-items: center; gap: 12px; padding: 26px 22px; }
.sp-waiting .sp-note { margin: 0; }
.sp-spinner { flex: none; width: 18px; height: 18px; border-radius: 50%;
  border: 2px solid var(--border); border-top-color: var(--accent-strong);
  animation: sp-spin .7s linear infinite; }
@keyframes sp-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .sp-spinner { animation-duration: 2.4s; } }

/* The wall. The ways in are sync.js's shared panel (57-signin.css), and the
   "everything else is open" line comes after them. */
.sp-gate { max-width: 34rem; }
/* The Today layout (/today/ and the flashcard decks) gives the gate a wide
   column to itself, so the card is centred and sized to the panel inside it
   rather than left as a wide box with the fields down one side. Same 30rem as
   /signin/, and the panel drops its own cap and fills. */
.sp-app-today .sp-gate { max-width: 30rem; margin-inline: auto; }
.sp-app-today .sp-gate-auth { max-width: none; }
.sp-gate-lede { margin: 0 0 14px; font-size: .95rem; line-height: 1.6; color: var(--text-soft); }
.sp-gate .sp-note { margin: 16px 0 0; font-size: .82rem; }

/* Claim prompt: the sign-in nudge, shown only for a schedule that predates the
   gate. Rendered hidden; sync.js unhides it when there is an account to offer. */
.sp-signin { display: flex; align-items: center; justify-content: space-between; gap: 10px 18px; flex-wrap: wrap; margin: 18px 0 0; padding: 14px 16px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface-2); }
.sp-signin[hidden] { display: none; }
.sp-signin-text { display: flex; flex-direction: column; gap: 2px; }
.sp-signin-text b { font-size: .88rem; }
.sp-signin-text span { font-size: .82rem; color: var(--text-soft); }
.sp-signin .sync-row { margin: 0; }
/* An unclaimed schedule is a real risk of loss, not a nicety, so this one
   carries a warm edge rather than sitting flat like the reminder card. */
.sp-claim { margin: 0 0 18px; border-color: var(--accent-strong); background: var(--accent-soft); }

/* Daily reminder card (sync.js fills it, and unhides it only when sync is set
   up). Shares the nudge shape of .sp-signin so the two sit together without
   looking like two different features. */
.sp-remind-card { margin: 18px 0 0; padding: 14px 16px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface-2); }
.sp-remind-card[hidden] { display: none; }
.sp-remind-head { display: flex; align-items: center; justify-content: space-between; gap: 10px 18px; flex-wrap: wrap; }
/* Box and label are one target, and the clock sits at the far end of the row. */
.sp-remind-toggle { display: flex; align-items: center; gap: 10px; cursor: pointer; }
.sp-remind-toggle input { width: 18px; height: 18px; flex: none; accent-color: var(--accent-strong); cursor: pointer; }
.sp-remind-toggle input:disabled, .sp-remind-toggle:has(input:disabled) { cursor: default; opacity: .6; }
.sp-remind-text { display: flex; flex-direction: column; gap: 2px; }
.sp-remind-text b { display: inline-flex; align-items: center; gap: 8px; font-size: .88rem; }
.sp-remind-text span { font-size: .82rem; color: var(--text-soft); }
.sp-remind-moved { color: var(--accent-strong) !important; }
.sp-remind-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 12px; }
.sp-remind-time { padding: 7px 10px; border: 1px solid var(--border-strong); border-radius: var(--radius-sm); background: var(--surface); color: var(--text); font: inherit; font-size: 1rem; font-weight: 600; }
/* The clock and the picker it opens are drawn by the browser, not by us, so
   they need to be told which scheme they are sitting on. */
[data-theme="dark"] .sp-remind-time { color-scheme: dark; }
.sp-remind-note { margin: 10px 0 0; font-size: .82rem; color: var(--text-soft); }
.sp-remind-error { margin: 10px 0 0; font-size: .82rem; color: var(--n1); }

/* What setting up this level would stop, on the join card. */
.sp-switch-note { margin: 14px 0 0; font-size: .82rem; }

.sp-editor { margin: 18px 0 0; padding-top: 14px; border-top: 1px solid var(--border); }
.sp-editor summary { cursor: pointer; font-weight: 600; font-size: .88rem; color: var(--accent-strong); }
.sp-goal-fields { display: flex; gap: 14px; flex-wrap: wrap; margin: 12px 0; }
.sp-goal-field { display: flex; flex-direction: column; gap: 4px; font-size: .82rem; font-weight: 600; color: var(--text-soft); }
.sp-goal-field input { width: 5.5em; padding: 7px 10px; border: 1px solid var(--border-strong); border-radius: var(--radius-sm); background: var(--surface); color: var(--text); font: inherit; font-weight: 600; }
.sp-editor-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.sp-preset { border: 1px solid var(--border-strong); background: var(--surface); color: var(--text-soft); font: inherit; font-size: .82rem; font-weight: 500; padding: 5px 12px; border-radius: var(--radius-sm); cursor: pointer; }
.sp-preset:hover { border-color: var(--accent); color: var(--accent-strong); }
.sp-finish-note { font-size: .82rem; color: var(--text-soft); }
.sp-save { margin-left: auto; }

/* Reset this level — destructive, so it is spelled out rather than tucked into
   the quiet "leave" link below it. */
.sp-danger { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: 18px 0 0; padding-top: 14px; border-top: 1px dashed var(--border); }
.sp-reset-btn {
  flex: none; border: 1px solid color-mix(in srgb, var(--n1) 45%, var(--border-strong)); background: var(--surface);
  color: var(--n1); border-radius: var(--radius); padding: 10px 18px; font: inherit; font-size: .82rem; font-weight: 500; cursor: pointer;
}
.sp-reset-btn:hover { border-color: var(--n1); background: color-mix(in srgb, var(--n1) 8%, var(--surface)); }
.sp-danger .sp-note { flex: 1 1 220px; margin: 0; font-size: .82rem; }

.sp-leave { margin: 16px 0 0; }
.sp-leave button { border: 0; background: none; padding: 0; font: inherit; font-size: .82rem; color: var(--text-faint); text-decoration: underline; cursor: pointer; }
.sp-leave button:hover { color: var(--n1); }

/* /progress/ chip for an active plan */
.sp-dash { display: flex; align-items: center; gap: 8px 16px; flex-wrap: wrap; margin: 0 0 26px; padding: 12px 16px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface-2); color: var(--text); text-decoration: none; }
.sp-dash:hover { border-color: var(--accent); }
.sp-dash span { font-size: .88rem; color: var(--text-soft); }
.sp-dash .sp-dash-go { margin-left: auto; font-weight: 600; color: var(--accent-strong); }
/* ---- /delete-account/ ----------------------------------------------------
   Self-service account deletion. The panel is the only destructive control on
   the site, so it is deliberately set apart from the prose around it and the
   confirming button is the only red thing on the page. Red is --n1, which
   already shifts to a legible tint in dark mode, so there are no per-theme
   overrides here any more. */

.page-delete-account .delete-steps { margin: 0 0 20px; }

.delete-panel {
  margin: 0 0 16px; padding: 20px;
  background: var(--surface-2); border: 1px solid var(--border-strong);
  border-radius: var(--radius);
}
.delete-panel > :first-child { margin-top: 0; }
.delete-panel > :last-child { margin-bottom: 0; }

.delete-note, .delete-who { font-size: .88rem; color: var(--text-soft); margin: 0 0 12px; }
.delete-who strong { color: var(--text); }

/* A quote indent, which is the one border-left the system keeps — the state
   itself is carried by the chip-style wash and the button below. */
.delete-confirm {
  margin: 0 0 16px; padding: 12px 16px; font-size: .88rem;
  background: color-mix(in srgb, var(--n1) 6%, var(--surface));
  border: 1px solid var(--border-strong);
  border-left: 2px solid var(--border-strong); border-radius: var(--radius-sm);
}

.delete-actions { display: flex; flex-wrap: wrap; gap: 10px; }
.delete-btn {
  border: 1px solid var(--border-strong); background: transparent;
  color: var(--text); border-radius: var(--radius);
  padding: 12px 22px; font-weight: 500; font-size: .88rem; cursor: pointer;
  transition: background-color .3s cubic-bezier(.4, 0, .2, 1), border-color .3s cubic-bezier(.4, 0, .2, 1), color .3s cubic-bezier(.4, 0, .2, 1);
}
.delete-btn:hover:not([disabled]) { background: var(--accent-soft); border-color: var(--accent); }
.delete-btn[disabled] { opacity: .5; cursor: default; }
.delete-btn-danger { border-color: color-mix(in srgb, var(--n1) 40%, var(--border-strong)); color: var(--n1); }
.delete-btn-danger:hover:not([disabled]) { background: color-mix(in srgb, var(--n1) 12%, var(--surface)); border-color: var(--n1); color: var(--n1); }

.delete-error { margin: 12px 0 0; font-size: .88rem; color: var(--n1); }
.delete-fine { font-size: .82rem; color: var(--text-faint); }

.delete-done {
  padding: 18px 20px;
  background: color-mix(in srgb, var(--st-done) 6%, var(--surface));
  border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.delete-done h3 { font-family: var(--sans); margin: 0 0 8px; font-size: 1.02rem; font-weight: 600; letter-spacing: -.01em; color: var(--st-done); }
.delete-done p { margin: 0; font-size: .88rem; color: var(--text-soft); }
/* Setup modal (/progress/): the level → pace → account flow, painted by app.js
   initPlanStart into [data-plan-start] and shipped hidden by pages/progress.js.

   One question fills the card at a time, the rows are the only thing to look
   at, and the only real colour is the level mark and, on the last screen, the
   sign-in button. It was a full page of its own until the dashboard took the
   job over; the step markup is unchanged, so everything below the modal chrome
   is the same as it was. */

/* ---- Modal chrome -------------------------------------------------------- */
.ob-modal { position: fixed; inset: 0; z-index: 70; display: flex; align-items: center; justify-content: center; padding: clamp(12px, 4vh, 48px) 16px; }
.ob-modal[hidden] { display: none; }
.ob-modal-veil { position: absolute; inset: 0; background: var(--scrim); backdrop-filter: blur(4px); }
.ob-modal-card {
  position: relative; width: 100%; max-width: 560px; max-height: 100%; overflow: auto;
  background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-lg, 0 24px 60px rgb(0 0 0 / .22));
}
.ob-close {
  position: absolute; top: 10px; right: 10px; z-index: 3; display: flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; padding: 0; border: 0; border-radius: 50%;
  background: transparent; color: var(--text-faint); cursor: pointer;
}
/* Hidden while the dashboard is waiting on a level (mustPick in app.js). */
.ob-close[hidden] { display: none; }
.ob-close:hover { background: var(--surface-2); color: var(--text); }

.ob { display: flex; flex-direction: column; }

/* Progress rail, pinned to the top edge of the card: how far in you are, and
   how much is left. */
.ob-rail { position: sticky; top: 0; z-index: 2; height: 3px; background: var(--border); }
.ob-rail-fill { display: block; height: 100%; background: var(--accent-strong); transition: width .3s cubic-bezier(.4, 0, .2, 1); }

.ob-shell { width: 100%; padding: 34px 24px 24px; display: flex; flex-direction: column; gap: 22px; }

.ob-count { margin: 0 0 8px; font-size: .72rem; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; color: var(--text-faint); }
.ob-q { margin: 0 0 22px; font-family: var(--sans); font-size: clamp(1.3rem, 3.6vw, 1.6rem); font-weight: 600; line-height: 1.2; letter-spacing: -.02em; }
.ob-lede { margin: 0 0 18px; font-size: var(--step); color: var(--text-soft); }
.ob-fine { margin: 18px 0 0; font-size: .82rem; line-height: 1.55; color: var(--text-faint); }

/* ---- The rows ------------------------------------------------------------ */
/* Every step is the same list: a mark, three lines, and an optional tag. The
   whole row is the target, and it is big enough to be one on a phone. */
.ob-opts { display: flex; flex-direction: column; gap: 10px; }
.ob-opt { display: flex; align-items: center; gap: 16px; width: 100%; padding: 16px 18px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); font: inherit; color: inherit; text-align: left; text-decoration: none; cursor: pointer; transition: background-color .18s cubic-bezier(.4, 0, .2, 1), border-color .18s cubic-bezier(.4, 0, .2, 1); }
.ob-opt:hover { background: var(--accent-soft); border-color: var(--border-strong); text-decoration: none; color: inherit; }
/* Selection is a darkened hairline and a wash, never a coloured border — see
   DESIGN.md. The level marks are the only colour on the screen. */
.ob-opt.is-sel { background: var(--accent-soft); border-color: var(--text); }

/* The level's N-number, or the pace's day count. Display serif, no container:
   the same .lvl-mark treatment the rest of the site uses. */
.ob-opt-mark { flex: none; min-width: 2ch; font-family: var(--display); font-weight: 400; font-size: 1.85rem; line-height: 1; text-align: center; color: var(--lvl, var(--text-soft)); }
.ob-opt-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.ob-opt-title { font-size: 1rem; font-weight: 600; }
.ob-opt-sub { font-size: .85rem; color: var(--text-soft); }
.ob-opt-out { font-size: .85rem; color: var(--accent-strong); }
.ob-opt-tag { flex: none; margin-left: auto; align-self: flex-start; padding: 3px 9px; border-radius: 999px; background: var(--cta); color: var(--cta-contrast); font-size: .68rem; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; }
/* The recommended row is the one obvious way in, and with the Continue button
   gone it has to say so by itself. A darkened hairline, not a coloured one. */
.ob-opt.is-rec { border-color: var(--text-soft); }

/* On the pace step the mark is a number, not a glyph, so it needs a unit under
   it or "60" reads as an item count rather than a duration. */
.ob-opts-pace .ob-opt-mark { display: flex; flex-direction: column; align-items: center; gap: 1px; min-width: 3.2ch; color: var(--text); }
.ob-opts-pace .ob-opt-mark::after { content: "days"; font-family: var(--sans); font-size: .62rem; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--text-faint); }

/* ---- The custom pace ----------------------------------------------------- */
/* The fourth pace, under the three named rows: quieter than they are until it
   is opened, because most people want one of the three. */
.ob-custom-open {
  display: block; width: 100%; margin: 10px 0 0; padding: 13px 18px;
  border: 1px dashed var(--border-strong); border-radius: var(--radius);
  background: transparent; color: var(--text-soft);
  font: inherit; font-size: .9rem; font-weight: 600; text-align: center; cursor: pointer;
}
.ob-custom-open:hover { border-color: var(--accent); color: var(--accent-strong); }

.ob-custom { margin: 10px 0 0; padding: 16px 18px; border: 1px solid var(--border-strong); border-radius: var(--radius); background: var(--surface); }
.ob-num-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(112px, 1fr)); gap: 12px; }
.ob-num { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.ob-num span { font-size: .74rem; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--text-soft); }
.ob-num input {
  width: 100%; padding: 9px 11px; border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--bg); color: inherit; font: inherit; font-size: 1rem; font-weight: 600;
}
.ob-num input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; border-color: var(--accent); }
.ob-custom-out { margin: 14px 0 0; font-size: .86rem; color: var(--text-soft); }
.ob-custom-go {
  margin: 14px 0 0; padding: 11px 18px; border: 0; border-radius: var(--radius-sm);
  background: var(--cta); color: var(--cta-contrast); font: inherit; font-size: .92rem; font-weight: 600; cursor: pointer;
}
.ob-custom-go:hover { filter: brightness(.94); }

/* ---- The kana box -------------------------------------------------------- */
/* On the pace screen kana is a box above the rows, not a screen of its own:
   the schedule is already running, and the rows below say what ticking it
   costs a day. */
.ob-check { display: flex; align-items: flex-start; gap: 12px; margin: 0 0 14px; padding: 14px 18px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); cursor: pointer; }
.ob-check input { flex: none; width: 18px; height: 18px; margin: 1px 0 0; accent-color: var(--cta); cursor: pointer; }
.ob-check span { display: block; font-size: .94rem; font-weight: 600; }
.ob-check .ob-check-sub { margin-top: 2px; font-size: .82rem; font-weight: 400; color: var(--text-soft); }
.ob-check:hover { border-color: var(--text-soft); }

/* ---- The account screen -------------------------------------------------- */
/* The choices already made, carried onto the screen that asks for the account,
   so it reads as the last step of something rather than a wall that appeared.
   Same mark treatment as the rows above it. */
.ob-recap { display: flex; align-items: center; gap: 12px; margin: 0 0 20px; padding: 13px 16px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); font-size: .88rem; color: var(--text-soft); }
.ob-recap-mark { flex: none; min-width: 2ch; font-family: var(--display); font-weight: 400; font-size: 1.5rem; line-height: 1; text-align: center; color: var(--lvl, var(--text-soft)); }

/* The ways in are sync.js's shared panel, styled in 57-signin.css: the same
   three offered on every other surface, so this screen is not a fourth answer
   to "how do I sign in". */

/* ---- Footer -------------------------------------------------------------- */
/* No Continue button: the question rows commit on the tap, so the only control
   down here is the way back out. It is quiet on purpose — the rows are the
   action. */
.ob-foot { display: flex; align-items: center; }
.ob-back { display: inline-flex; align-items: center; gap: 7px; padding: 10px 16px 10px 12px; border: 1px solid var(--border); border-radius: var(--radius); background: transparent; color: var(--text-soft); font: inherit; font-size: .88rem; cursor: pointer; transition: border-color .18s cubic-bezier(.4, 0, .2, 1), color .18s cubic-bezier(.4, 0, .2, 1); }
.ob-back:hover { border-color: var(--border-strong); color: var(--text); }
/* Hidden on the first screen: the close button is the only way out from there. */
.ob-back[hidden] { display: none; }


.ob-busy { display: flex; flex-direction: column; align-items: center; gap: 14px; padding: 40px 0; text-align: center; }

@media (max-width: 560px) {
  .ob-modal { padding: 0; align-items: flex-end; }
  .ob-modal-card { max-width: none; max-height: 92svh; border-radius: var(--radius) var(--radius) 0 0; border-bottom: 0; }
  .ob-shell { padding: 30px 16px 24px; gap: 20px; }
  .ob-opt { padding: 14px 15px; gap: 13px; }
  .ob-opt-mark { font-size: 1.6rem; }
  .ob-recap { padding: 12px 14px; gap: 10px; }
}

/* Settings: a sheet behind the gear on the study day, and a folded block at the
   foot of the dashboard. Everything that is not the studying itself lives in
   one or the other, so the study card ends where the studying ends. The sheet
   is painted by app.js (openSettings) into the end of <body>, which is why its
   chrome is defined here rather than beside either page. */
.set-btn {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; padding: 0;
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--text-soft); cursor: pointer;
}
.set-btn:hover { border-color: var(--accent); color: var(--accent-strong); }

/* ---- More settings (dashboard) ------------------------------------------- */
/* The dashboard has no gear: the same blocks sit folded at the foot of the
   page, shut until someone goes looking for them. */
.set-more { margin: 0 0 30px; padding-top: 20px; border-top: 1px solid var(--border); }
.set-more > summary { cursor: pointer; font-weight: 600; font-size: .88rem; color: var(--accent-strong); }
.set-more > summary::marker { color: var(--text-faint); }
.set-more .set-block:first-child { margin-top: 16px; }

/* ---- The sheet ----------------------------------------------------------- */
.set-modal { position: fixed; inset: 0; z-index: 75; display: flex; align-items: center; justify-content: center; padding: clamp(12px, 4vh, 48px) 16px; }
.set-veil { position: absolute; inset: 0; background: var(--scrim); backdrop-filter: blur(4px); }
.set-card {
  position: relative; width: 100%; max-width: 620px; max-height: 100%; overflow: auto;
  background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-lg, 0 24px 60px rgb(0 0 0 / .22));
}
.set-head {
  position: sticky; top: 0; z-index: 2; display: flex; align-items: center; gap: 12px;
  padding: 16px 16px 14px 22px; background: var(--bg); border-bottom: 1px solid var(--border);
}
.set-h { margin: 0; font-family: var(--sans); font-size: 1.1rem; font-weight: 600; letter-spacing: -.01em; }
.set-close {
  margin-inline-start: auto; display: flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; padding: 0; border: 0; border-radius: 50%;
  background: transparent; color: var(--text-faint); cursor: pointer;
}
.set-close:hover { background: var(--surface-2); color: var(--text); }
.set-body { padding: 4px 22px 26px; }

.set-block { margin: 22px 0 0; padding-top: 20px; border-top: 1px solid var(--border); }
.set-block:first-child { margin-top: 18px; padding-top: 0; border-top: 0; }
.set-block > h3 { margin: 0 0 12px; font-family: var(--sans); font-size: .82rem; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--text-soft); }

/* The blocks below were written for a card and a <details>; inside the sheet
   the sheet owns the spacing and the rules above own the heading. */
.set-block.sp-editor { margin-top: 22px; }
.set-block:first-child.sp-editor { margin-top: 18px; }
.set-block .sp-remind-card, .set-block .sp-upnext { margin: 0; padding-top: 0; border-top: 0; }
.set-block .sp-editor-sum { margin: 0 0 12px; }
.set-block .progress-actions { margin: 0; }
.set-block .progress-reset-level { margin: 0; }
.set-danger .progress-actions { margin-top: 18px; padding-top: 14px; border-top: 1px dashed var(--border); }
/* The heading is the separator now, so the block's own rule would double it. */
.set-danger .sp-danger { margin-top: 0; padding-top: 0; border-top: 0; }

@media (max-width: 560px) {
  .set-modal { padding: 0; align-items: flex-end; }
  .set-card { max-width: none; max-height: 92svh; border-radius: var(--radius) var(--radius) 0 0; border-bottom: 0; }
  .set-body { padding: 4px 16px 24px; }
  .set-head { padding-left: 16px; }
  .sp-save { margin-left: 0; }
}
/* ── The sign-in panel (sync.js) ───────────────────────────────────────────
   One control, mounted wherever the site asks for an account: the header menu,
   the dashboard, the study-plan gate, the last screen of the setup modal, the
   reminder opt-in and the deletion page. Three ways in, in one order
   everywhere: the two popups, then an email and a password.

   The popup buttons come first because they are one tap; the form under them is
   for everyone who does not want a Google or Apple account in this. Neither is
   styled as the loud action, so the panel does not push a way in. --cta stays
   for the setup modal, which overrides the submit button below. */

.si { display: flex; flex-direction: column; gap: 14px; text-align: left; }
.si-oauth-row { display: flex; flex-direction: column; gap: 8px; }

.si-btn {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  width: 100%; padding: 12px 16px; font: inherit; font-size: .9rem; font-weight: 600;
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--text); cursor: pointer;
  transition: border-color .18s cubic-bezier(.4, 0, .2, 1), background-color .18s cubic-bezier(.4, 0, .2, 1);
}
.si-btn:hover:not([disabled]) { border-color: var(--accent); background: var(--surface-2); }
.si-btn[disabled] { opacity: .55; cursor: default; }
.si-mark { flex: none; width: 17px; height: 17px; }
/* Apple's mark is drawn in currentColor, so it inverts with the theme the way
   the words beside it do. Google's is fixed four-colour by their rules. Its
   glyph is taller than it is wide, so it gets its own box rather than being
   squashed into the square one. */
.si-mark-apple { width: 14px; height: 17px; margin-top: -2px; }

/* A divider that says what it separates. The rules are drawn on the row, not
   on the word, so it centres whatever the word's width turns out to be. */
.si-or { display: flex; align-items: center; gap: 12px; color: var(--text-faint); font-size: .78rem; }
.si-or::before, .si-or::after { content: ""; flex: 1; height: 1px; background: var(--border); }

.si-form { display: flex; flex-direction: column; gap: 10px; }
.si-field { display: flex; flex-direction: column; gap: 5px; }
.si-field > span { font-size: .78rem; font-weight: 600; letter-spacing: .02em; color: var(--text-soft); }
.si-field > input {
  width: 100%; padding: 11px 13px; font: inherit; font-size: .92rem;
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  background: var(--bg-elev); color: var(--text);
}
.si-field > input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; border-color: transparent; }

.si-primary { margin-top: 2px; border-color: transparent; background: var(--accent); color: var(--accent-contrast); }
.si-primary:hover:not([disabled]) { border-color: transparent; background: color-mix(in srgb, var(--accent) 86%, var(--surface)); }

/* Sign in / create an account / forgot it: a change of mind about which form
   this is, not an action, so these are links rather than more buttons. */
.si-alt { display: flex; flex-wrap: wrap; gap: 4px 16px; }
.si-link {
  padding: 0; border: 0; background: none; font: inherit; font-size: .82rem;
  color: var(--text-soft); text-decoration: underline; text-underline-offset: 3px; cursor: pointer;
}
.si-link:hover { color: var(--accent-strong); }

/* The privacy policy: a box to tick on the create-an-account screen, a line of
   fine print everywhere else. The box sits inside the form, under the fields
   it gates, so the submit button below it is visibly the next thing. */
.si-agree { display: flex; align-items: flex-start; gap: 9px; font-size: .82rem; line-height: 1.45; color: var(--text-soft); cursor: pointer; }
.si-agree > input { flex: none; width: 16px; height: 16px; margin: 1px 0 0; accent-color: var(--accent); cursor: pointer; }
.si-agree > input[disabled] { cursor: default; }
.si-agree a { color: var(--text); text-decoration: underline; text-underline-offset: 3px; }
.si-agree a:hover { color: var(--accent-strong); }
.si-fine { margin: 0; font-size: .78rem; line-height: 1.45; color: var(--text-faint); }
.si-fine a { color: var(--text-soft); text-decoration: underline; text-underline-offset: 3px; }
.si-fine a:hover { color: var(--accent-strong); }

.si-note, .si-error {
  margin: 0; padding: 9px 12px; border-radius: var(--radius-sm);
  font-size: .84rem; line-height: 1.5;
}
.si-note { background: var(--surface-2); color: var(--text); }
.si-error { background: color-mix(in srgb, var(--n1) 10%, var(--surface)); color: var(--text); }

/* ---- The header dropdown ------------------------------------------------ */
/* 240px wide, under a 38px icon. Everything shrinks a step and the popup
   buttons lose their labels' breathing room, but the set on offer stays the
   same as every other surface. */
.si-compact { gap: 10px; }
.si-compact .si-btn { padding: 9px 12px; font-size: .82rem; }
.si-compact .si-mark { width: 15px; height: 15px; }
.si-compact .si-mark-apple { width: 13px; height: 15px; }
.si-compact .si-field > input { padding: 8px 10px; font-size: .85rem; }
.si-compact .si-alt { flex-direction: column; gap: 5px; }
.si-compact .si-link { font-size: .78rem; }
.si-compact .si-agree, .si-compact .si-fine { font-size: .76rem; }

/* ---- Where the panel is mounted ----------------------------------------- */
/* The setup modal. The submit button is the one loud action on that screen, as
   the single Continue button used to be, and the panel gets the room a full
   screen can give it. */
.ob-auth { margin-top: 4px; }
.ob-auth .si { gap: 16px; }
.ob-auth .si-btn { padding: 14px 18px; font-size: .92rem; }
.ob-auth .si-primary { padding: 15px 22px; border-radius: var(--radius); background: var(--cta); color: var(--cta-contrast); }
.ob-auth .si-primary:hover:not([disabled]) { background: var(--cta); filter: brightness(.94); }

/* The study-plan gate: a column, capped so the fields do not run the width of
   a wide card. */
.sp-gate-auth { max-width: 22rem; margin-top: 16px; }
.sp-remind-signin { max-width: 22rem; margin: 12px 0 2px; }
.delete-signin { max-width: 22rem; margin-top: 12px; }

/* The password asked for again before an email account is deleted. */
.delete-field { display: flex; flex-direction: column; gap: 5px; max-width: 20rem; margin: 0 0 14px; }
.delete-field > span { font-size: .78rem; font-weight: 600; color: var(--text-soft); }
.delete-field > input {
  padding: 10px 12px; font: inherit; font-size: .92rem;
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  background: var(--bg-elev); color: var(--text);
}

@media (max-width: 560px) {
  .sp-gate-auth, .sp-remind-signin, .delete-signin { max-width: none; }
}

/* ---- /signin/ ------------------------------------------------------------ */
/* The one page that carries the form. Everything else that asks for an account
   links here, so this is the only place the panel gets a screen of its own:
   one narrow card, centred, with nothing beside it to read. */
.page-signin main { max-width: 30rem; }
.signin-page { padding: 8px 0 40px; }
.signin-card {
  display: flex; flex-direction: column; gap: 12px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 22px;
}
.signin-card .si { gap: 16px; }
.signin-card .si-btn { padding: 13px 18px; font-size: .92rem; }
.signin-note { margin: 0; font-size: .88rem; color: var(--text-soft); }
.signin-who { margin: 0; font-size: .92rem; color: var(--text); }
.signin-card a.si-btn { text-decoration: none; }
.signin-card a.si-btn:hover { color: var(--accent-contrast); text-decoration: none; }

/* The account row on the dashboard and the hub pages: a link that looks like
   the button it replaced. */
a.sync-btn-cta { display: inline-flex; align-items: center; text-decoration: none; }
a.sync-btn-cta:hover { text-decoration: none; }
/* ── Test what you know ────────────────────────────────────────────────────
   Two places: the row on the dashboard that opens it (.dash-known), and the
   page itself (/progress/test/, .self-test). The questions reuse the .dq-*
   block from 53-study-plan.css and the score card the .quiz-score block from
   21-practice-quiz.css, so a question and a score look the same here as they
   do in the study day and in a past-paper test. Only what is new to this
   screen is below. */

/* The dashboard button. A peer of the section deck buttons above it: bordered
   and pressable, but not the filled CTA, which is the day itself. */
.dash-known-row {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px;
  margin: 18px 0 6px;
}
.dash-known {
  position: relative; display: flex; flex-direction: column; gap: 3px;
  padding: 14px 44px 14px 18px;
  border: 1px solid var(--border-strong); border-radius: var(--radius);
  background: var(--surface); color: var(--text);
  font-family: var(--sans);
  transition: border-color .15s, color .15s;
}
.dash-known::after {
  content: "\2192"; position: absolute; right: 18px; top: 50%;
  transform: translateY(-50%); color: var(--text-soft); font-size: 1rem;
  transition: transform .15s, color .15s;
}
.dash-known-t { font-size: .95rem; font-weight: 600; }
.dash-known-s { font-size: .82rem; font-weight: 400; color: var(--text-soft); line-height: 1.4; }
.dash-known:hover { border-color: var(--accent); color: var(--accent-strong); text-decoration: none; }
.dash-known:hover::after { color: var(--accent-strong); transform: translate(3px, -50%); }

/* The page. One column, narrow enough that a stem and its four options read as
   one block rather than as a line stretched across a desktop. */
.self-test { max-width: 46rem; margin: 0 auto; padding: 4px 0 40px; }
/* Short screens (the start, one question, the score) still fill the window,
   so the footer starts below the fold rather than halfway up it. */
.page-self-test main.shell {
  min-height: calc(100vh - var(--main-top, var(--header-h)));
  min-height: calc(100svh - var(--main-top, var(--header-h)));
}
.st-note { margin: 24px 0; font-size: .92rem; color: var(--text-soft); }

/* Where the questions came from. Above the test, not under it: at the foot it
   sat below the fold on a question and read as page furniture. No rule and no
   box, so it stays a note rather than becoming a banner. */
.st-disclaimer {
  margin: 0 0 18px; font-size: .84rem; line-height: 1.6; color: var(--text-soft);
}
.st-disclaimer a { font-weight: 600; }

/* The opening screen, and every screen that has nothing to ask: the level and
   the size of the pool, how many questions, and the one button. */
.st-card {
  margin: 8px 0 0; padding: 26px 24px;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface); box-shadow: var(--shadow-sm);
}
.st-h { margin: 0; font-size: 1.28rem; letter-spacing: -.01em; }
.st-fact { margin: 6px 0 0; font-size: .95rem; color: var(--text-soft); }
.st-card .st-note { margin: 14px 0 0; }
.st-lens { display: flex; gap: 8px; margin: 18px 0 0; }
.st-len {
  min-width: 56px; padding: 9px 14px;
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--text-soft);
  font: inherit; font-size: .92rem; font-weight: 600; font-variant-numeric: tabular-nums;
  cursor: pointer; transition: border-color .12s ease, background .12s ease, color .12s ease;
}
.st-len:hover { border-color: var(--accent); color: var(--accent-strong); }
.st-len.is-active { border-color: var(--accent); background: var(--surface-2); color: var(--text); }
.st-start { margin-top: 20px; }
a.st-start { display: inline-flex; }

/* The bar over every question. The level, how far in, and the progress as a
   hairline along the bottom edge, the same shape the study day's day bar uses
   so the two read as one family. */
.st-bar {
  position: relative; display: flex; align-items: baseline; gap: 12px;
  margin: 8px 0 24px; padding: 0 0 12px;
  border-bottom: 1px solid var(--border);
}
.st-level {
  font-family: var(--sans); font-size: .78rem; font-weight: 700;
  letter-spacing: .06em; color: var(--text-faint);
}
.st-count { font-family: var(--sans); font-size: .92rem; font-weight: 600; color: var(--text); }
.st-rail { position: absolute; left: 0; right: 0; bottom: -1px; height: 2px; background: var(--border); }
.st-rail-fill { display: block; height: 100%; background: var(--accent); transition: width .18s ease; }

/* The number lives in the bar, so the stem carries none and the options line
   up under the stem rather than under a gutter that is no longer there. */
.self-test .dq-opts { padding-left: 0; }
.self-test .dq-explain { margin-left: 0; }
.st-next { margin: 26px 0 0; }
.st-next .sp-cta { width: 100%; justify-content: center; }

/* What was missed, under the score. The only rows worth carrying out of a
   test, so they are links into the items themselves. */
.st-missed { margin: 28px 0 0; }
.st-missed-h {
  margin: 0 0 10px; font-family: var(--sans); font-size: .76rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: .05em; color: var(--text-soft);
}
.st-missed-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.st-missed-list a {
  display: flex; align-items: baseline; gap: 10px; padding: 10px 14px;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--text);
}
.st-missed-list a:hover { border-color: var(--accent); text-decoration: none; }
.st-missed-t { font-family: var(--ja); font-size: 1.02rem; font-weight: 600; }
.st-missed-m { font-size: .86rem; color: var(--text-soft); }

@media (max-width: 560px) {
  .st-card { padding: 22px 18px; }
  .st-lens { gap: 6px; }
  .st-len { flex: 1 1 0; min-width: 0; padding: 10px 8px; }
  .st-start { width: 100%; justify-content: center; }
  .dash-known-row { grid-template-columns: 1fr; gap: 10px; }
  .dash-known { padding: 13px 40px 13px 16px; }
}
/* ---- Chat in Japanese ---------------------------------------------------- */
/* /chat/ only, painted by static/chat.js.

   Once the conversation mounts this stops being a page and becomes an app: the
   crumbs and the page head go and the column fills the screen under the
   header. The thread has no scroller of its own, it runs down the page on the
   site's scrollbar, with the bar and the composer held to the top and the foot
   of the window. The footer stays below the fold, the way the deck leaves it
   (31-srs-deck).

   Only the message you sent carries a bubble. A reply is often four or five
   lines of Japanese set at 1.95, and a box drawn around that reads as a wall;
   flat on the ground it reads as writing. */

/* ---- Page shell ---------------------------------------------------------- */
.page-chat main.shell { padding-bottom: 32px; }
/* Every screen that is not the app (signed out, the membership gate, an
   error) still fills the window, so the footer never shows under a short box.
   The app does its own measuring below. */
.page-chat:not(.is-chat-live) main.shell {
  min-height: calc(100vh - var(--main-top, var(--header-h)));
  min-height: calc(100svh - var(--main-top, var(--header-h)));
}
.chat-page { max-width: 46rem; margin: 0 auto; }
.chat-page .page-head { text-align: center; margin-bottom: 20px; }
.chat-page .page-head .lede { margin-left: auto; margin-right: auto; max-width: 32rem; }

.page-chat.is-chat-live main.shell { padding-top: 12px; padding-bottom: 0; }
.page-chat.is-chat-live .breadcrumbs,
.page-chat.is-chat-live .chat-page .page-head { display: none; }

/* The gate screens and every "nothing to show yet" state. Same shape as the
   members' tests use, because it is the same membership being asked about. */
.chat-message {
  max-width: 30rem; margin: 40px auto; padding: 32px 28px;
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  background: var(--surface); box-shadow: var(--shadow-sm);
  text-align: center;
}
.chat-message h2 { margin-top: 0; }
.chat-message p { color: var(--text-soft); }
.chat-message p:empty { display: none; }
.chat-message-actions { display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; margin-top: 20px; }
.chat-note { color: var(--text-soft); font-size: .88rem; }

/* Sasaki and Hoshizaki, introduced before the chat is up: on the landing page
   (face beside the text) and on the signed-out screen (face above it). */
.cast { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px 28px; margin: 0; padding: 0; list-style: none; }
.cast-card { display: grid; grid-template-columns: 72px minmax(0, 1fr); column-gap: 16px; align-content: center; }
.cast-card .chat-avatar { grid-row: span 2; align-self: center; }
.cast-card p { margin: 0; }
.cast-name { align-self: end; font-weight: 600; }
.cast-name [lang="ja"] { font-family: var(--ja); font-weight: 400; color: var(--text-faint); }
.cast-about { align-self: start; color: var(--text-soft); font-size: .88rem; line-height: 1.45; }
.home-chat .cast { max-width: 44rem; margin-bottom: 24px; }
.chat-message .cast-card { grid-template-columns: minmax(0, 1fr); justify-items: center; row-gap: 4px; }
.chat-message .cast-card .chat-avatar { grid-row: auto; margin-bottom: 6px; }
@media (max-width: 560px) {
  .home-chat .cast { grid-template-columns: minmax(0, 1fr); }
}

/* ---- The app ------------------------------------------------------------- */
/* The app ends exactly at the fold, so no strip of the footer shows under it
   on the first screen. What sits above it is the header, which takes more rows
   on a narrow window, so static/chat.js measures it into --chat-start; the
   fallback is the one-row header plus the shell's 12px. */
.chat-app {
  position: relative;
  display: flex; flex-direction: column;
  min-height: calc(100vh - var(--chat-start, calc(var(--header-h) + 13px)));
  min-height: calc(100svh - var(--chat-start, calc(var(--header-h) + 13px)));
}

/* ---- Bar ----------------------------------------------------------------- */
/* What the partner is holding itself to, and the two things you can do to the
   conversation. The message count is not here: it belongs under the box you
   are about to spend one from. */
.chat-top {
  position: sticky; top: calc(var(--header-h) - 1px); z-index: 3;
  display: flex; align-items: center; gap: 8px;
  padding: 6px 0 10px; background: var(--bg);
}
.chat-mode {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 7px 9px 7px 12px; border: 0; border-radius: 999px;
  background: none; color: var(--text); font: inherit; font-size: .94rem; font-weight: 500;
  cursor: pointer; transition: background-color .18s cubic-bezier(.4, 0, .2, 1);
}
.chat-mode:hover { background: var(--surface-2); }
.chat-mode svg { color: var(--text-faint); }
.chat-top-tools { margin-inline-start: auto; display: flex; gap: 2px; }
/* Who you are talking to. Their face and name, opening the settings. */
.chat-partner {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 3px 12px 3px 3px; border: 0; border-radius: 999px;
  background: none; color: var(--text); font: inherit; font-size: .94rem; font-weight: 600;
  cursor: pointer; transition: background-color .18s cubic-bezier(.4, 0, .2, 1);
}
.chat-partner:hover { background: var(--surface-2); }
.chat-avatar {
  flex: none; display: block; border-radius: 50%; object-fit: cover; object-position: 50% 12%;
  background: var(--accent-soft); box-shadow: inset 0 0 0 1px var(--border);
}
.chat-icon {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; padding: 0; border: 0; border-radius: 50%;
  background: none; color: var(--text-soft); cursor: pointer;
  transition: background-color .18s cubic-bezier(.4, 0, .2, 1), color .18s cubic-bezier(.4, 0, .2, 1);
}
.chat-icon:hover:not(:disabled) { background: var(--surface-2); color: var(--text); }
.chat-icon:disabled { opacity: .35; cursor: default; }

/* Chat is new and still changing. A small tag beside what it is set to, and
   beside the page title on the screens before it. */
.chat-beta {
  flex: none; display: inline-flex; align-items: center;
  padding: 3px 8px; border-radius: 999px;
  background: var(--accent-soft); color: var(--accent-strong);
  font-family: var(--sans); font-size: .68rem; font-weight: 600; letter-spacing: .06em;
  text-transform: uppercase; line-height: 1.2; vertical-align: middle;
}
h1 .chat-beta { position: relative; top: -.2em; margin-left: 6px; font-size: .8rem; }

/* ---- The list of conversations ------------------------------------------- */
/* Beside the column once there is room for it, which is ~1280px: the column is
   46rem centred, and the list takes the margin to its left. Narrower, it is a
   drawer from the left behind the button at the start of the bar. The list
   stays inside the app's own height, so it never runs over the footer. */
.chat-side {
  position: absolute; top: 0; bottom: 0; right: calc(100% + 24px); width: 244px; z-index: 4;
}
.chat-side-inner {
  position: sticky; top: calc(var(--header-h) + 8px);
  display: flex; flex-direction: column;
  max-height: calc(100vh - var(--header-h) - 20px);
  max-height: calc(100svh - var(--header-h) - 20px);
}
.chat-side-head { display: flex; align-items: center; gap: 6px; padding-bottom: 10px; }
.chat-new, .chat-feedback {
  flex: 1; display: inline-flex; align-items: center; gap: 9px;
  padding: 9px 12px; border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--text); font: inherit; font-size: .9rem; font-weight: 500;
  cursor: pointer; text-align: left;
  transition: background-color .18s cubic-bezier(.4, 0, .2, 1), border-color .18s cubic-bezier(.4, 0, .2, 1);
}
.chat-new:hover:not(:disabled), .chat-feedback:hover { background: var(--surface-2); border-color: var(--accent); }
.chat-new:disabled { opacity: .5; cursor: default; }
.chat-new svg, .chat-feedback svg { color: var(--text-soft); }
.chat-feedback { border-color: transparent; background: none; color: var(--text-soft); }
.chat-feedback:hover { color: var(--text); }
.chat-side-close { display: none; }

.chat-list { flex: 1; min-height: 0; overflow-y: auto; margin: 0 -4px; padding: 0 4px; overscroll-behavior: contain; }
.chat-list-day {
  margin: 14px 0 4px; padding: 0 10px;
  font-family: var(--sans); font-size: .72rem; font-weight: 600; letter-spacing: .04em; color: var(--text-faint);
}
.chat-list-day:first-child { margin-top: 4px; }
.chat-list-empty { margin: 8px 10px; font-size: .86rem; color: var(--text-faint); }
.chat-item {
  position: relative; display: flex; align-items: center; border-radius: var(--radius-sm);
  transition: background-color .15s cubic-bezier(.4, 0, .2, 1);
}
.chat-item:hover, .chat-item:focus-within { background: var(--surface-2); }
.chat-item.is-on { background: var(--surface-2); }
.chat-item-open {
  flex: 1; min-width: 0; padding: 8px 10px; border: 0; background: none;
  color: var(--text-soft); font-family: var(--ja); font-size: .88rem; text-align: left;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: pointer;
}
.chat-item.is-on .chat-item-open, .chat-item:hover .chat-item-open { color: var(--text); }
.chat-item-del {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; margin-right: 3px; padding: 0; border: 0; border-radius: var(--radius-xs);
  background: none; color: var(--text-faint); cursor: pointer; opacity: 0;
  transition: opacity .15s cubic-bezier(.4, 0, .2, 1), color .15s cubic-bezier(.4, 0, .2, 1);
}
.chat-item:hover .chat-item-del, .chat-item:focus-within .chat-item-del, .chat-item.is-on .chat-item-del { opacity: 1; }
.chat-item-del:hover { color: var(--n2, var(--text)); }
@media (hover: none) { .chat-item-del { opacity: 1; } }
.chat-side-foot { padding-top: 8px; margin-top: 6px; border-top: 1px solid var(--border); display: flex; }
.chat-side-veil { display: none; }

/* On a wide screen the list is always there, so the bar needs neither button. */
.chat-side-open { display: none; }
.chat-feedback-top { display: none; }

@media (max-width: 1279px) {
  .chat-side-open { display: inline-flex; margin-inline-start: -8px; }
  .chat-side {
    position: fixed; top: 0; bottom: 0; left: 0; right: auto; z-index: 90;
    width: min(300px, 86vw); padding: 14px 12px 12px;
    background: var(--bg-elev); border-right: 1px solid var(--border); box-shadow: var(--shadow-lg);
    transform: translateX(-100%); visibility: hidden;
    transition: transform .22s cubic-bezier(.4, 0, .2, 1), visibility 0s linear .22s;
  }
  .chat-side-inner { position: static; height: 100%; max-height: none; }
  .chat-side-close { display: inline-flex; }
  .chat-feedback-top { display: inline-flex; }
  .chat-app.is-side-open .chat-side { transform: none; visibility: visible; transition: transform .22s cubic-bezier(.4, 0, .2, 1); }
  .chat-side-veil {
    display: block; position: fixed; inset: 0; z-index: 89;
    background: var(--scrim); opacity: 0; pointer-events: none;
    transition: opacity .22s cubic-bezier(.4, 0, .2, 1);
  }
  .chat-app.is-side-open .chat-side-veil { opacity: 1; pointer-events: auto; }
}
/* On a phone the bar has no room for it; feedback is in the drawer there. */
@media (max-width: 479px) { .chat-feedback-top { display: none; } }
@media (prefers-reduced-motion: reduce) {
  .chat-side, .chat-app.is-side-open .chat-side, .chat-side-veil { transition: none; }
}

/* ---- Feedback ------------------------------------------------------------ */
.chat-sheet-body.chat-feedback-body { padding-top: 18px; padding-bottom: 22px; }
.chat-feedback-lede { margin: 0 0 16px; color: var(--text-soft); font-size: .94rem; }
.chat-mailrow {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 10px 10px 10px 16px; border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface);
}
.chat-mail { flex: 1; min-width: 0; font-weight: 500; overflow-wrap: anywhere; }
.chat-mail-write { margin-top: 16px; }

/* ---- The conversation ---------------------------------------------------- */
.chat-log {
  flex: 1;
  display: flex; flex-direction: column; gap: 26px;
  padding: 6px 2px 12px;
}

.chat-msg { display: flex; flex-direction: column; gap: 6px; }
.chat-msg.is-them { align-self: stretch; align-items: flex-start; max-width: 100%; }
.chat-msg.is-you { align-self: flex-end; align-items: flex-end; max-width: 82%; }

.chat-msg.is-them .chat-bubble { padding: 0; background: none; border: 0; }
.chat-who { display: flex; align-items: center; gap: 8px; margin-bottom: -2px; }
.chat-who-name { font-size: .88rem; font-weight: 600; color: var(--text-soft); }
.chat-msg.is-you .chat-bubble {
  padding: 11px 18px; border-radius: var(--radius-lg);
  background: var(--surface-2); border: 1px solid var(--border);
}
.chat-bubble p { margin: 0; white-space: pre-wrap; }
.chat-ja { margin: 0; font-family: var(--ja); font-size: 1.06rem; line-height: 1.95; }
.chat-msg.is-you .chat-ja { font-size: 1rem; line-height: 1.85; }
/* The English translation. It comes with every reply; the setting shows all of
   them, and a message's own button shows that one. A footnote to the Japanese
   rather than part of what was said. */
.chat-gloss {
  display: none;
  max-width: 62ch;
  margin: 2px 0 0; padding-top: 10px; border-top: 1px solid var(--border);
  color: var(--text-soft); font-size: .9rem; line-height: 1.65;
  white-space: pre-wrap;
}
.chat-log.is-en .chat-gloss, .chat-msg.is-en .chat-gloss { display: block; }
/* With every translation already on screen, the button has nothing to say. */
.chat-log.is-en .chat-translate { display: none; }

/* English and copy, under a reply, on hover or on the way through with a
   keyboard. */
.chat-acts { display: flex; gap: 2px; margin-top: -2px; opacity: 0; transition: opacity .15s cubic-bezier(.4, 0, .2, 1); }
.chat-msg:hover .chat-acts, .chat-msg:focus-within .chat-acts { opacity: 1; }
@media (hover: none) { .chat-acts { opacity: 1; } }
.chat-act {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; padding: 0; border: 0; border-radius: var(--radius-xs);
  background: none; color: var(--text-faint); cursor: pointer;
  transition: background-color .18s cubic-bezier(.4, 0, .2, 1), color .18s cubic-bezier(.4, 0, .2, 1);
}
.chat-act:hover { background: var(--surface-2); color: var(--text); }
.chat-act.is-on { background: var(--surface-2); color: var(--text); }
.chat-msg.is-en .chat-acts { opacity: 1; }

/* ---- Words and grammar --------------------------------------------------- */
/* .rw is the reading pages' word span; app.js mounts the same hover card on the
   log, so these only have to look right here. The underline is dotted so a
   sentence still reads as a sentence with every other word marked. */
.chat-ja .rw {
  border-bottom: 1px dotted var(--border-strong);
  color: inherit; text-decoration: none; cursor: help;
}
.chat-ja a.rw { cursor: pointer; }
.chat-ja .rw:hover, .chat-ja .rw.is-open, .chat-ja .rw:focus-visible {
  background: var(--surface-2); border-bottom-color: var(--accent-strong); border-radius: var(--radius-xs);
}
/* Your own message already sits on surface-2, so the lift has to go the other
   way there. */
.chat-msg.is-you .chat-ja .rw:hover,
.chat-msg.is-you .chat-ja .rw.is-open,
.chat-msg.is-you .chat-ja .rw:focus-visible { background: var(--surface); }
/* A word the level check caught and the retry could not talk the model out of.
   Marked rather than hidden: it is the one word on screen worth a second look. */
.chat-ja .rw.is-over { border-bottom-style: solid; border-bottom-color: var(--n1, var(--accent-strong)); }
.chat-grammar { border-bottom-style: dashed !important; }
.chat-log.is-nopop .rw { border-bottom-color: transparent; cursor: inherit; }

/* ---- A correction -------------------------------------------------------- */
/* The wrong part struck through with the fix directly under it, in the flow of
   the sentence, so the shape of the mistake is visible where it was made. */
.chat-fix {
  display: inline-flex; flex-direction: column; align-items: flex-start;
  vertical-align: bottom; line-height: 1.35; margin: 0 1px;
}
.chat-wrong { text-decoration: line-through; text-decoration-color: var(--text-faint); opacity: .6; }
.chat-right { text-decoration: none; font-weight: 500; border-bottom: 2px solid var(--st-done); }

/* Why, under their own message. */
.chat-notes { max-width: 46ch; margin: 0; padding: 0; list-style: none; display: grid; gap: 4px; }
.chat-notes li { font-size: .8rem; color: var(--text-soft); text-align: right; }
.chat-notes b { font-family: var(--ja); font-weight: 500; color: var(--text); }

/* What they could have written, when they wrote in English. It sits under
   their own message, on their own side. */
.chat-suggest {
  max-width: 100%;
  padding: 11px 15px; border-radius: var(--radius); border: 1px dashed var(--border-strong);
  background: var(--surface);
}
.chat-msg.is-you .chat-suggest { text-align: right; }
.chat-suggest-label {
  display: block; margin-bottom: 4px;
  font-size: .7rem; letter-spacing: .08em; text-transform: uppercase; color: var(--text-faint);
}

/* ---- Empty and waiting --------------------------------------------------- */
.chat-empty { margin: auto; padding: 16px 0 28px; text-align: center; max-width: 32rem; }
.chat-empty h2 { margin: 0 0 8px; font-size: clamp(1.7rem, 5vw, 2.2rem); letter-spacing: -.02em; }
.chat-empty p { margin: 0; color: var(--text-soft); font-size: .94rem; }
.chat-how {
  display: inline-flex; flex-direction: column; gap: 6px;
  margin: 4px 0 0; padding: 0; list-style: none;
  color: var(--text-soft); font-size: .94rem;
}
.chat-how li::before { content: "·"; margin-right: 8px; color: var(--accent); font-weight: 700; }
.chat-picks { display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; margin: 18px 0 20px; }
.chat-pick {
  flex: 1 1 140px; min-width: 0; max-width: 200px; display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 16px 12px 14px; border: 1px solid var(--border); border-radius: var(--radius-lg);
  background: var(--surface); color: var(--text); font: inherit; cursor: pointer;
  transition: background-color .18s cubic-bezier(.4, 0, .2, 1), border-color .18s cubic-bezier(.4, 0, .2, 1);
}
.chat-pick:hover { background: var(--surface-2); }
.chat-pick.is-on { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.chat-pick > .chat-avatar, .chat-pick > .chat-faces { margin-bottom: 6px; }
/* The group card's two faces keep the height of one big one. */
.chat-pick.is-group > .chat-faces { margin-block: 8px 14px; }
/* Both of them, the second tucked behind the first's shoulder. */
.chat-faces { display: inline-flex; flex: none; }
.chat-faces .chat-avatar + .chat-avatar { box-shadow: 0 0 0 2px var(--bg); }
.chat-pick-name { font-weight: 600; white-space: nowrap; }
.chat-pick-ja { font-family: var(--ja); font-weight: 400; color: var(--text-faint); }
.chat-pick-about { color: var(--text-soft); font-size: .84rem; line-height: 1.45; }
.chat-starters { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin-top: 24px; }
.chat-starter {
  padding: 9px 16px; border: 1px solid var(--border-strong); border-radius: 999px;
  background: var(--surface); color: var(--text);
  font-family: var(--ja); font-size: .94rem; cursor: pointer;
  transition: background-color .18s cubic-bezier(.4, 0, .2, 1), border-color .18s cubic-bezier(.4, 0, .2, 1);
}
.chat-starter:hover { background: var(--surface-2); border-color: var(--accent); }

.chat-typing { display: inline-flex; align-items: center; gap: 5px; height: 26px; }
.chat-typing span {
  width: 7px; height: 7px; border-radius: 50%; background: var(--text-faint);
  animation: chat-bounce 1.2s ease-in-out infinite;
}
.chat-typing span:nth-child(2) { animation-delay: .15s; }
.chat-typing span:nth-child(3) { animation-delay: .3s; }
@keyframes chat-bounce {
  0%, 60%, 100% { transform: translateY(0); opacity: .35; }
  30% { transform: translateY(-4px); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) { .chat-typing span { animation: none; opacity: .6; } }

/* Back to the newest message after reading up the thread. It hangs off the
   composer, so it sits clear of it however many lines are in the box. */
.chat-jump {
  position: absolute; left: 50%; bottom: calc(100% - 4px); z-index: 1; transform: translateX(-50%);
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; padding: 0; border-radius: 50%;
  border: 1px solid var(--border-strong); background: var(--surface); color: var(--text-soft);
  box-shadow: var(--shadow-md); cursor: pointer;
}
.chat-jump:hover { color: var(--text); border-color: var(--accent); }
.chat-jump[hidden] { display: none; }

/* ---- Composer ------------------------------------------------------------ */
.chat-dock {
  position: sticky; bottom: 0; z-index: 3;
  flex: none; padding: 6px 0 10px; background: var(--bg);
}
/* The last line of the conversation fades into the composer rather than
   stopping dead against it. */
.chat-dock::before {
  content: ""; position: absolute; left: 0; right: 0; bottom: 100%; height: 22px;
  background: linear-gradient(to top, var(--bg), transparent); pointer-events: none;
}
.chat-composer { margin: 0; }
.chat-field {
  display: flex; align-items: flex-end; gap: 6px;
  padding: 7px 7px 7px 4px;
  border: 1px solid var(--border-strong); border-radius: var(--radius-xl);
  background: var(--surface); box-shadow: var(--shadow-md);
  transition: border-color .18s cubic-bezier(.4, 0, .2, 1), box-shadow .18s cubic-bezier(.4, 0, .2, 1);
}
.chat-field:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.chat-input {
  flex: 1; min-width: 0; resize: none; border: 0; outline: none; background: none;
  min-height: 40px; max-height: 168px; padding: 9px 2px 9px 16px;
  color: var(--text); font-family: var(--ja); font-size: 1rem; line-height: 1.55;
}
.chat-input::placeholder { color: var(--text-faint); }
.chat-input:disabled { opacity: .6; }
.chat-send {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; padding: 0; border: 0; border-radius: 50%;
  background: var(--accent); color: var(--accent-contrast); cursor: pointer;
  transition: background-color .18s cubic-bezier(.4, 0, .2, 1);
}
.chat-send:disabled { background: var(--border-strong); color: var(--surface); cursor: default; }
/* Until the account is checked the arrow is a turning ring. */
.chat-spin {
  display: none; width: 16px; height: 16px; border-radius: 50%;
  border: 2px solid currentColor; border-right-color: transparent;
  animation: chat-turn .8s linear infinite;
}
.chat-send.is-pending svg { display: none; }
.chat-send.is-pending .chat-spin { display: block; }
@keyframes chat-turn { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .chat-spin { animation-duration: 2.4s; } }

.chat-foot { margin: 9px 0 0; text-align: center; font-size: .76rem; color: var(--text-faint); }
.chat-foot.is-out { color: var(--n2); }
.chat-status { margin: 7px 0 0; text-align: center; font-size: .84rem; color: var(--text-soft); }
.chat-status:empty { display: none; }

/* ---- Settings ------------------------------------------------------------ */
/* One pane of rows: what it is on the left, what it is set to on the right.
   There is no folded drawer in the bar any more, because a panel that pushes
   the conversation down the screen to be read is the wrong trade. */
.chat-modal {
  position: fixed; inset: 0; z-index: 80;
  display: flex; align-items: center; justify-content: center;
  padding: clamp(12px, 5vh, 56px) 16px;
}
.chat-veil { position: absolute; inset: 0; background: var(--scrim); backdrop-filter: blur(4px); }
.chat-sheet {
  position: relative; width: 100%; max-width: 560px; max-height: 100%; overflow: auto;
  background: var(--bg-elev); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  animation: chat-rise .18s cubic-bezier(.4, 0, .2, 1);
}
@keyframes chat-rise { from { opacity: 0; transform: translateY(10px); } }
@media (prefers-reduced-motion: reduce) { .chat-sheet { animation: none; } }
.chat-sheet-head {
  position: sticky; top: 0; z-index: 2; display: flex; align-items: center; gap: 12px;
  padding: 15px 13px 13px 22px; background: var(--bg-elev); border-bottom: 1px solid var(--border);
}
.chat-sheet-head h2 { margin: 0; font-family: var(--sans); font-size: 1.05rem; font-weight: 600; letter-spacing: -.01em; }
.chat-sheet-close { margin-inline-start: auto; }
.chat-sheet-body { padding: 2px 22px 18px; }

.chat-row {
  display: flex; align-items: center; justify-content: space-between; gap: 20px;
  padding: 15px 0; border-top: 1px solid var(--border);
}
.chat-row:first-child { border-top: 0; }
.chat-row[hidden] { display: none; }
.chat-row-label { font-size: .94rem; font-weight: 500; color: var(--text); }

/* A native select, so a phone still opens its own picker, with the arrow drawn
   rather than dropped in as an image: a data URI would need a literal colour
   and only 00-tokens is allowed one. */
.chat-selectwrap { position: relative; display: inline-flex; align-items: center; flex: none; }
.chat-selectwrap::after {
  content: ""; position: absolute; right: 14px; width: 7px; height: 7px; pointer-events: none;
  border-right: 1.5px solid var(--text-faint); border-bottom: 1.5px solid var(--text-faint);
  transform: translateY(-2px) rotate(45deg);
}
.chat-select {
  appearance: none; -webkit-appearance: none;
  padding: 9px 36px 9px 14px; border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--text); font: inherit; font-size: .9rem; cursor: pointer;
  transition: border-color .18s cubic-bezier(.4, 0, .2, 1);
}
.chat-select:hover { border-color: var(--accent); }

.chat-seg {
  flex: none; display: inline-flex; gap: 2px; padding: 3px;
  border: 1px solid var(--border); border-radius: 999px; background: var(--surface-2);
}
.chat-seg label { display: inline-flex; cursor: pointer; }
.chat-seg input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.chat-seg span {
  display: inline-flex; align-items: center; padding: 6px 12px; border-radius: 999px;
  font-size: .84rem; font-weight: 500; line-height: 1; color: var(--text-soft);
  transition: background-color .18s cubic-bezier(.4, 0, .2, 1), color .18s cubic-bezier(.4, 0, .2, 1);
}
.chat-seg input:checked + span { background: var(--surface); color: var(--text); box-shadow: var(--shadow-sm); }
.chat-seg input:focus-visible + span { outline: 2px solid var(--accent-strong); outline-offset: 2px; }

.chat-switch { flex: none; display: inline-flex; cursor: pointer; }
.chat-switch input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.chat-switch span {
  position: relative; display: block; width: 44px; height: 26px; border-radius: 999px;
  background: var(--border-strong); transition: background-color .18s cubic-bezier(.4, 0, .2, 1);
}
.chat-switch span::after {
  content: ""; position: absolute; top: 3px; left: 3px; width: 20px; height: 20px; border-radius: 50%;
  background: var(--surface); box-shadow: var(--shadow-sm);
  transition: transform .18s cubic-bezier(.4, 0, .2, 1);
}
.chat-switch input:checked + span { background: var(--accent); }
.chat-switch input:checked + span::after { transform: translateX(18px); }
.chat-switch input:focus-visible + span { outline: 2px solid var(--accent-strong); outline-offset: 2px; }

@media (max-width: 480px) {
  .chat-partner { padding-right: 3px; }
  .chat-partner > span:not(.chat-faces) { display: none; }
  /* Three cards do not fit side by side: a list, face on the left. */
  .chat-picks { flex-direction: column; align-items: stretch; gap: 8px; }
  .chat-pick {
    flex: none; max-width: none; display: grid; grid-template-columns: 76px 1fr;
    column-gap: 12px; row-gap: 2px; align-items: center; padding: 10px 14px; text-align: left;
  }
  .chat-pick > .chat-avatar, .chat-pick > .chat-faces { grid-row: span 2; justify-self: center; margin: 0; }
  .chat-pick.is-group > .chat-faces { margin: 0; }
  .chat-pick > .chat-avatar { width: 52px; height: 52px; }
  .chat-pick > .chat-faces .chat-avatar { width: 42px; height: 42px; }
  .chat-pick > .chat-faces .chat-avatar + .chat-avatar { margin-inline-start: -12px !important; }
}

@media (max-width: 640px) {
  .chat-page { max-width: none; }
  .page-chat.is-chat-live main.shell { padding-inline: 14px; }
  .chat-log { gap: 20px; padding-inline: 0; }
  .chat-msg.is-you { max-width: 92%; }
  .chat-modal { padding: 0; align-items: flex-end; }
  .chat-sheet { max-width: none; max-height: 92svh; border-radius: var(--radius) var(--radius) 0 0; border-bottom: 0; }
  .chat-sheet-head { padding-left: 16px; }
  .chat-sheet-body { padding: 2px 16px 24px; }
  .chat-row { gap: 12px; }
}

/* ---- Phones: a fixed shell ----------------------------------------------- */
/* On a phone the site header does not pin (02-header), so a bar pinned where
   it used to be floats over nothing, and a column sized in svh ends short of
   the fold once the browser's toolbar tucks away. Here the app is fixed from
   the foot of the header to the foot of the window instead: the bar stays put
   under the header, only the conversation scrolls, and the composer sits on
   the bottom edge. static/chat.js measures --chat-start (the header) and
   --chat-vh (the visible window, which shrinks when the keyboard is up). */
@media (max-width: 720px) {
  html:has(body.page-chat.is-chat-live),
  body.page-chat.is-chat-live { overflow: hidden; overscroll-behavior: none; }
  .page-chat.is-chat-live .site-footer { display: none; }
  .page-chat.is-chat-live main.shell { padding: 0; }
  .page-chat.is-chat-live .chat-app {
    position: fixed; left: 0; right: 0; top: var(--chat-start, var(--header-h)); z-index: 5;
    min-height: 0;
    height: calc(100dvh - var(--chat-start, var(--header-h)));
    height: calc(var(--chat-vh, 100dvh) - var(--chat-start, var(--header-h)));
    padding-inline: 14px; background: var(--bg);
  }
  .page-chat.is-chat-live .chat-top { position: relative; top: auto; flex: none; padding-top: 8px; }
  .page-chat.is-chat-live .chat-log {
    min-height: 0; overflow-y: auto; overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
  }
  .page-chat.is-chat-live .chat-dock { position: relative; padding-bottom: 8px; }
  .page-chat.is-chat-live .chat-foot:empty { display: none; }
}
