/* Fonts, served from this origin. Google Fonts cost a render-blocking
   stylesheet from a second host and the font files from a third: two
   connections to open before the first paint. The same files (Inter v20,
   Cormorant Garamond v21, both variable, both OFL) now sit in assets/fonts
   and the browser fetches only the subset a page's text needs.

   The two Fallback faces are the system fonts with their metrics adjusted
   to match, so the text set while a web font loads occupies the same width
   and line height and the swap moves nothing. Figures computed from the
   font files: average advance over English letter frequencies, then the
   hhea ascent and descent divided by that ratio. */
@font-face { font-family: "Inter"; font-style: normal; font-weight: 100 900; font-display: swap;
  src: url("../fonts/inter-v20-latin.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; }
@font-face { font-family: "Inter"; font-style: normal; font-weight: 100 900; font-display: swap;
  src: url("../fonts/inter-v20-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family: "Inter"; font-style: normal; font-weight: 100 900; font-display: swap;
  src: url("../fonts/inter-v20-vietnamese.woff2") format("woff2");
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; }
@font-face { font-family: "Cormorant Garamond"; font-style: normal; font-weight: 300 700; font-display: swap;
  src: url("../fonts/cormorant-v21-latin.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; }
@font-face { font-family: "Cormorant Garamond"; font-style: normal; font-weight: 300 700; font-display: swap;
  src: url("../fonts/cormorant-v21-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family: "Cormorant Garamond"; font-style: normal; font-weight: 300 700; font-display: swap;
  src: url("../fonts/cormorant-v21-vietnamese.woff2") format("woff2");
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB; }
@font-face { font-family: "Inter Fallback"; src: local("Arial");
  size-adjust: 107.48%; ascent-override: 90.14%; descent-override: 22.44%; line-gap-override: 0%; }
@font-face { font-family: "Cormorant Fallback"; src: local("Georgia");
  size-adjust: 89.45%; ascent-override: 103.29%; descent-override: 32.08%; line-gap-override: 0%; }

:root {
  --bg: #f4f7fb;
  --panel: #ffffff;
  --panel-soft: #edf3fa;
  --navy: #112d4e;
  --navy-2: #183765;
  --blue: #2d6eb4;
  --gold: #d9a63a;
  /* The N1 colour where it carries text or sits behind white text: the
     bright gold above is 2.2:1 against white, this is 4.75:1. */
  --gold-ink: #976b0b;
  --text: #1f2937;
  --muted: #5c697a;
  --line: #e4ebf4;
  --success: #2f7d57;
  --danger: #c84a52;
  --shadow: 0 18px 36px rgba(17, 45, 78, 0.12);
  --radius: 24px;
  --font-sans: "Inter", "Inter Fallback", sans-serif;
  --font-display: "Cormorant Garamond", "Cormorant Fallback", serif;

  /* Layout scale. Every shell on the site - header, hero, page body, footer -
     is a .container, so these two numbers set the single pair of vertical
     lines the whole site is built on. Alignment is most of what reads as
     "designed": nothing should start or end at a width nothing else uses.

     The container is capped well before the viewport is. Filling a 1512px
     laptop edge to edge leaves no frame, so the page reads as a document
     dumped into a window rather than one laid out in it - and a table row or
     a paragraph stretched that far is genuinely harder to track back to the
     next line.

     One number, used by everything. The study lists briefly had a narrower
     measure of their own, which read better on its own but put the brand and
     the nav 100px to the left of the first word under them - the header and
     the page looked like two documents on one screen. A per-page width also
     means the header changes size as you navigate, which is worse than either
     width. So the measure the reading columns want is the measure the whole
     site uses: 1120px is wide enough for a full exam paper and
     narrow enough that a row of vocabulary does not run away from the eye. */
  --gutter: clamp(18px, 3.4vw, 72px);
  --content: clamp(940px, 84vw, 1120px);

  /* One vertical rhythm for every section, tightened on short screens by the
     laptop-height query at the end of this file. */
  --sp-section: clamp(28px, 3.2vw, 54px);
  --card-pad: clamp(18px, 1.5vw, 26px);
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  margin: 0;
  font-family: var(--font-sans);
  /* Cormorant Garamond defaults to old-style figures: 1 and 2 sit at
     x-height, 3 4 5 7 9 hang below the baseline. In a heading that is
     handsome; in "9,639" it reads as a mistake. Ask for lining figures,
     which the font does carry, and let every number on the site inherit it.
     Any rule that sets font-variant-numeric sets it wholesale, so the
     tabular-nums declarations elsewhere name lining-nums too or they would
     switch this back off. */
  font-variant-numeric: lining-nums;
  background: linear-gradient(180deg, #edf3f9 0%, #f6f8fb 100%);
  color: var(--text);
}
/* height:auto is the partner of the width and height attributes the exam
   figures now carry: the attributes tell the browser the shape so it can
   hold the space open, and this lets max-width shrink the picture without
   stretching it out of that shape. */
img { max-width: 100%; height: auto; display: block; }
a { text-decoration: none; color: inherit; }
button, select, input { font: inherit; }

.container {
  width: min(var(--content), calc(100% - var(--gutter) * 2));
  margin-inline: auto;
}

/* Keeps the footer on the bottom edge of tall desktop screens instead of
   floating halfway up whenever a page is short (about, an empty stats page). */
body {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: 100dvh; /* mobile: discount the browser's own retracting chrome */
}
body > main { flex: 1 0 auto; }

/* The frosted header.

   Two things were wrong with it. backdrop-filter had no -webkit- prefix, so
   on Safari - a large share of the phones this site is read on - the blur
   never applied and the header was simply 78% white with sharp kanji sliding
   underneath it. And there was no fallback at all for a browser that cannot
   blur, which is the same failure again.

   So: blur where it is supported, and opaque where it is not. 0.88 rather
   than 0.78 because even a correct blur leaves dark text visible as a smudge
   at 0.78, and a header you can read through is not a header. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  -webkit-backdrop-filter: saturate(180%) blur(12px);
  backdrop-filter: saturate(180%) blur(12px);
  background: rgba(255, 255, 255, 0.88);
  border-bottom: 1px solid rgba(17, 45, 78, 0.08);
}

@supports not ((backdrop-filter: blur(1px)) or
               (-webkit-backdrop-filter: blur(1px))) {
  .site-header { background: #fff; }
}

/* Once the page has moved, the header needs an edge of its own: at the top
   it sits on the same pale ground as the page and does not need one, but
   over content a hairline alone reads as a seam rather than a layer. */
.site-header.is-stuck {
  box-shadow: 0 6px 20px -8px rgba(17, 45, 78, 0.28);
  border-bottom-color: rgba(17, 45, 78, 0.12);
}

.nav-wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: clamp(12px, 1.3vw, 18px) 0;
  gap: clamp(12px, 1.6vw, 24px);
}

/* The tagline under the logo names the page, so its length changes from page
   to page - "About" to "Word and grammar lists", and longer again in the
   other eleven languages. With the three parts merely spread apart, the
   brand's width decided where the menu began, so the menu slid sideways
   every time you moved between tabs: 416px on About, 424px on Study, and up
   to 40px in Vietnamese. Three columns instead, the outer two equal, put
   the menu on the container's centre line and hold it there.

   For that to hold, the brand has to measure the same on every page, so the
   tagline is taken out of the measurement - width:0, then stretched back
   over the column it was given by min-width - and what sets the floor is
   the logo and "JLPT Practice", which never change. The tagline still gets
   the full width of the column to draw in, and only truncates if a
   translation outgrows it. Reserving a fixed slot for it instead would have
   been simpler, but it steals that width from the menu, which wraps to two
   rows above 1100px rather than scrolling: it put the English header onto
   two rows at 1200px. The stacked phone header below 760px keeps its own
   flex rules. */
@media (min-width: 761px) {
  .nav-wrap {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
  }

  .nav-tools { justify-self: end; }

  .brand-text { flex: 1 1 auto; min-width: min-content; }

  .brand-tag {
    width: 0;
    min-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
  }
}

.brand-wrap {
  display: flex;
  align-items: center;
  gap: 12px;
}

/* JL, white on the brand gradient - the same mark as the installed app
   icon. */
.brand-mark {
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  border-radius: 12px;
  background: linear-gradient(135deg, var(--navy), var(--blue));
  color: #fff;
  font-weight: 800;
  letter-spacing: 0.08em;
}

.brand-name {
  font-weight: 800;
  font-size: 1.05rem;
  color: var(--navy);
  white-space: nowrap;
}

.brand-tag {
  font-size: 0.72rem;
  color: var(--muted);
  white-space: nowrap;
}

.main-nav {
  display: flex;
  align-items: center;
  gap: 18px;
  flex-wrap: wrap;
}

/* The bar's height is a constant, not a consequence of which controls a page
   happens to carry. Everywhere else the language picker is the tallest thing
   in the header and sets that height; the guide pages have no picker - they
   exist in one language only - so the bar came out 8px shorter there and the
   whole page stepped up when you moved between Study and Guides. The floor
   matches the picker's own height, which drops to 44px at 1180px, and is
   lifted entirely below 760px where the header stops being one row. */
@media (min-width: 761px) {
  .main-nav { min-height: 48px; }
}
@media (min-width: 761px) and (max-width: 1180px) {
  .main-nav { min-height: 44px; }
}

.main-nav a {
  color: var(--muted);
  font-weight: 600;
  padding: 9px 12px;
  border-radius: 999px;
  transition: 0.2s ease;
}

.main-nav a:hover,
.main-nav a.active {
  background: rgba(17, 45, 78, 0.06);
  color: var(--navy);
}

.nav-tools {
  display: flex;
  align-items: center;
  gap: 12px;
}

.language-select {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  background: rgba(17, 45, 78, 0.03);
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--muted);
  font-size: 0.86rem;
}

.language-select select {
  border: none;
  background: transparent;
  color: var(--navy);
  font-weight: 600;
  outline: none;
  /* The label forwards taps, but the select itself was an 18px strip; on a
     phone the difference is a control you hit and one you miss. */
  min-height: 26px;
}

/* ==========================================================================
   Buttons

   One definition for the whole site. This block used to exist twice - once
   here and once in exam.css - with different values, so the same
   .btn-primary was a 12px-radius rectangle on the home page and a full pill
   inside a paper. exam.css now only adds what is genuinely exam-only.

   Three tiers, and the gap between them is the point. A primary button is
   noticeable because the things around it are not: give every control a
   gradient and a glow and the eye has nowhere to land. So the weight goes
   almost entirely into tier one.

     .btn-primary   the one action - filled, lit, with a coloured shadow
     .btn-gold      picking a paper back up, and nothing else
     .btn-ghost     the alternative - raised white, quiet until touched
     .btn-secondary the same, on a dark hero

   Every tier gets the same three states, which is most of what makes a
   button feel solid: it lifts under the pointer, it presses in when clicked,
   and it shows a ring when focused by keyboard.
   ========================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 12px 22px;
  border: 1px solid transparent;
  border-radius: 999px;
  font-weight: 700;
  font-size: 0.92rem;
  letter-spacing: 0.01em;
  white-space: nowrap;
  cursor: pointer;
  /* Slight overshoot on the way up, so the lift reads as a response rather
     than a slide. */
  transition: transform 0.18s cubic-bezier(0.34, 1.4, 0.64, 1),
              box-shadow 0.18s ease, background 0.18s ease,
              border-color 0.18s ease, color 0.18s ease;
}

.btn:hover { transform: translateY(-2px); }
/* Pressed: the button goes back down and its shadow tightens under it, which
   is what "pushed" looks like. Without this a click has no feedback at all
   until the next page loads. */
.btn:active { transform: translateY(0); transition-duration: 0.06s; }

.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
}

.btn-lg { padding: 15px 32px; font-size: 1rem; }

/* -- tier one ------------------------------------------------------------ */

/* The shadow is tinted with the button's own colour rather than plain black:
   a navy button casting a navy shadow looks lit, a black one looks dirty.
   The inset hairline along the top edge is the light catching it. */
.btn-primary {
  background: linear-gradient(135deg, var(--navy) 0%, var(--blue) 100%);
  color: #fff;
  box-shadow: 0 6px 18px -4px rgba(45, 110, 180, 0.55),
              inset 0 1px 0 rgba(255, 255, 255, 0.18);
}
.btn-primary:hover {
  background: linear-gradient(135deg, var(--navy-2) 0%, #3a82cd 100%);
  box-shadow: 0 12px 26px -6px rgba(45, 110, 180, 0.62),
              inset 0 1px 0 rgba(255, 255, 255, 0.22);
}
.btn-primary:active {
  box-shadow: 0 3px 10px -3px rgba(45, 110, 180, 0.5),
              inset 0 1px 0 rgba(255, 255, 255, 0.14);
}

.btn-gold {
  background: linear-gradient(135deg, #e8c163 0%, var(--gold) 100%);
  color: #3a2a06;
  box-shadow: 0 6px 18px -4px rgba(217, 166, 58, 0.6),
              inset 0 1px 0 rgba(255, 255, 255, 0.35);
}
.btn-gold:hover {
  background: linear-gradient(135deg, #f0cd76 0%, #e0af44 100%);
  box-shadow: 0 12px 26px -6px rgba(217, 166, 58, 0.68),
              inset 0 1px 0 rgba(255, 255, 255, 0.42);
}
.btn-gold:active {
  box-shadow: 0 3px 10px -3px rgba(217, 166, 58, 0.55),
              inset 0 1px 0 rgba(255, 255, 255, 0.3);
}

/* -- tier two ------------------------------------------------------------ */

/* Raised, not flat. A white button with a hairline border and no shadow at
   all reads as disabled next to a lit one; a small shadow is what separates
   "quiet" from "off". */
/* This used to rest on a #dbe4f0 outline, which is 1.28:1 against the white
   card it sits on - WCAG asks 3:1 of anything that draws the edge of a
   control, so the button had no visible boundary at all and read as empty
   space beside the filled Start. The old hover state was already the right
   answer (blue edge, blue label) and it is what this rule now rests on; the
   button was only ever legible to a mouse, and a phone has no hover, so on
   a phone it was never legible at all. Blue edge on white is 5.25:1, and it
   still reads as the quieter of the pair because Start is filled and this
   is not. Hover deepens to navy rather than introducing a new colour. */
.btn-ghost {
  background: #fff;
  border-color: var(--blue);
  color: var(--blue);
  box-shadow: 0 2px 6px rgba(17, 45, 78, 0.07);
}
.btn-ghost:hover {
  background: #eef4fb;
  border-color: var(--navy);
  color: var(--navy);
  box-shadow: 0 8px 18px -5px rgba(45, 110, 180, 0.32);
}
.btn-ghost:active { box-shadow: 0 1px 4px rgba(17, 45, 78, 0.1); }

/* The same tier, sitting on one of the dark hero panels. */
.btn-secondary {
  background: rgba(255, 255, 255, 0.14);
  border-color: rgba(255, 255, 255, 0.42);
  color: #fff;
  backdrop-filter: blur(6px);
}
.btn-secondary:hover {
  background: rgba(255, 255, 255, 0.19);
  border-color: rgba(255, 255, 255, 0.55);
  box-shadow: 0 10px 22px -8px rgba(0, 0, 0, 0.45);
}
.btn-secondary:active { box-shadow: 0 2px 8px -4px rgba(0, 0, 0, 0.4); }

/* Nobody who has asked for less motion wants a button springing at them. */
@media (prefers-reduced-motion: reduce) {
  .btn { transition: background 0.15s ease, border-color 0.15s ease; }
  .btn:hover, .btn:active { transform: none; }
}

/* ==========================================================================
   Everything else that is pressable

   Chips, tabs, the back links, the kanji tiles. These are
   tier three: they must feel like the same family as the buttons above -
   same easing, same press, same focus ring - without competing with them.
   So they get the press and the ring, but no lift and no glow. The lift is
   reserved for things that are actually a call to action; when a filter chip
   jumps at the pointer too, nothing on the page has priority.

   Written as one list so a new control cannot quietly miss out.
   ========================================================================== */

.chip,
.chip-back,
.skill-btn,
.study-tab,
.paper-tab,
.exam-back,
.kanji-nav,
.kanji-char,
.words-btn,
.flag-btn,
.lvcard,
.to-top,
.language-select {
  cursor: pointer;
  transition: background 0.16s ease, border-color 0.16s ease,
              color 0.16s ease, box-shadow 0.16s ease,
              transform 0.12s ease;
}

/* A select sizes itself to its longest option. The guide pages list only the
   five languages the guides exist in, longest "Tiếng Việt", so their picker
   came out 52px narrower than the twelve-language picker on every other
   page, longest "Português (Brasil)" - and because the menu is centred
   between the brand and the picker, it slid 26px on the way into Guides.
   One width, the twelve-language one, on the single-row header. The stacked
   phone header keeps its own narrower rule below. */
@media (min-width: 761px) {
  .language-select select { min-width: 10.2em; }
}

/* Every one of them acknowledges the press. */
.chip:active,
.chip-back:active,
.skill-btn:active,
.study-tab:active,
.paper-tab:active,
.exam-back:active,
.kanji-nav:active,
.kanji-char:active,
.words-btn:active,
.flag-btn:active,
.to-top:active { transform: scale(0.96); }

.lvcard:active { transform: scale(0.995); }

/* One ring, everywhere, so keyboard focus is never a guess. */
:where(a, button, select, input, summary, [tabindex]):focus-visible {
  outline: 3px solid var(--blue);
  outline-offset: 2px;
  border-radius: 10px;
}

/* On the dark panels the blue ring disappears into the background. */
.hero-section :where(a, button, select, input):focus-visible,
.exam-hero :where(a, button, select, input):focus-visible,
.exams-hero :where(a, button, select, input):focus-visible {
  outline-color: var(--gold);
}

@media (prefers-reduced-motion: reduce) {
  .chip:active, .chip-back:active, .skill-btn:active, .study-tab:active,
  .paper-tab:active, .exam-back:active, .kanji-nav:active, .kanji-char:active,
  .words-btn:active, .flag-btn:active,   .to-top:active, .lvcard:active { transform: none; }
}

.hero-section {
  padding: clamp(34px, 4.4vw, 64px) 0 clamp(26px, 2.6vw, 38px);
  background: linear-gradient(135deg, #112d4e 0%, #183765 38%, #2d6eb4 100%);
  color: white;
}

.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(300px, 0.85fr);
  gap: clamp(24px, 3vw, 48px);
  /* Centred, not stretched.

     Stretching the copy to the panel's height and spacing it out did line
     the two columns up at top and bottom - but the copy's own content is
     192px inside a 284px column, so it bought that edge with a 39px hole
     under the headline and a 53px hole above the buttons. A headline, its
     sentence and its buttons are one thing; pulling them apart to make an
     edge agree is the wrong trade. The panel is tightened instead, so the
     two columns end up close in height on their own. */
  align-items: center;
}

/* The next sitting, in the hero, on the dark gradient. A quiet strip rather
   than a panel: it is context for the pitch above it, not a sixth thing to
   click, so it is bounded by a rule on one edge instead of a box on four.

   Every rule below is written as .hero-countdown <class> - two classes - and
   not as a bare class. .hero-copy p sets a colour and a margin on every
   paragraph in this column, and a bare .cal-head would tie with it on
   specificity and lose on source order, so the gold count and the muted
   note would both have come out as hero body text. */
.hero-countdown {
  margin-top: 22px;
  padding: 12px 0 12px 16px;
  border-left: 3px solid var(--gold);
}
.hero-countdown p { margin: 0; }
.hero-countdown .cal-head {
  color: rgba(255, 255, 255, 0.66);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 0.68rem;
  font-weight: 700;
  line-height: 1.4;
}
.hero-countdown .cal-date {
  margin-top: 3px;
  color: #fff;
  font-size: 1.12rem;
  font-weight: 600;
  line-height: 1.4;
}
/* Tabular figures so the number holds its width as the count falls through
   a digit boundary. */
.hero-countdown .cal-count {
  margin-top: 2px;
  color: var(--gold);
  font-size: 0.95rem;
  font-weight: 600;
  line-height: 1.4;
  font-variant-numeric: lining-nums tabular-nums;
}
.hero-countdown .cal-note {
  margin-top: 7px;
  max-width: 46ch;
  color: rgba(255, 255, 255, 0.62);
  font-size: 0.78rem;
  line-height: 1.55;
}
/* a { text-decoration: none } is set globally, so this needs its underline
   back or it is a bold word in a sentence, not a link. */
.hero-countdown .cal-link {
  color: rgba(255, 255, 255, 0.85);
  text-decoration: underline;
  text-decoration-color: rgba(255, 255, 255, 0.4);
  text-underline-offset: 3px;
  white-space: nowrap;
}
.hero-countdown .cal-link:hover {
  color: #fff;
  text-decoration-color: var(--gold);
}

@media (max-width: 640px) {
  .hero-countdown { margin-top: 18px; padding-left: 13px; }
  .hero-countdown .cal-date { font-size: 1.02rem; }
}

/* Five ways straight into the library, one per level. */
.hero-levels {
  display: grid;
  gap: 4px;
  padding: 16px;
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 20px;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
.hero-levels-head {
  margin-bottom: 6px;
  padding: 0 8px;
  color: rgba(255, 255, 255, 0.72);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 0.7rem;
  font-weight: 700;
}
.hero-level {
  display: grid;
  grid-template-columns: 2.6rem minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
  padding: 7px 10px;
  border-radius: 12px;
  color: rgba(255, 255, 255, 0.88);
  text-decoration: none;
  transition: 0.18s cubic-bezier(.34, 1.56, .64, 1);
}
.hero-level b {
  font-family: var(--font-display);
  font-size: 1.2rem;
  font-weight: 700;
  color: #fff;
  font-variant-numeric: lining-nums;
}
.hero-level span {
  font-size: 0.82rem;
  line-height: 1.35;
  color: rgba(255, 255, 255, 0.78);
  /* Two of the five descriptions run long; one line each keeps the five rows
     the same height, which is what makes the column scannable. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.hero-level i {
  font-style: normal;
  opacity: 0;
  transform: translateX(-4px);
  transition: 0.18s ease;
}
.hero-level:hover {
  background: rgba(255, 255, 255, 0.13);
  color: #fff;
}
.hero-level:hover i { opacity: 0.9; transform: translateX(0); }
.hero-level:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
  .hero-level, .hero-level i { transition: none; }
}

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

/* On a phone the panel is 222px wide and the shortest description needs 247,
   so every one of the five was cut off mid-word - a list where nothing could
   be read. The descriptions are on the levels page anyway; what this panel
   is for on a small screen is the five links. So it becomes five level
   buttons in a row: one line instead of five, nothing truncated, and a
   bigger tap target than the row it replaces. */
/* Fill the row rather than sitting left with the rest of the width empty.
   Two equal halves keep them on one line and roughly double the tap area of
   each, which on a phone is the difference between aiming and pressing. */
@media (max-width: 640px) {
  .hero-actions .btn { flex: 1 1 0; min-width: 0; }
}

@media (max-width: 640px) {
  .hero-levels {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    padding: 14px;
  }
  .hero-levels-head {
    flex-basis: 100%;
    margin-bottom: 0;
    padding: 0;
  }
  .hero-level {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    justify-content: center;
    padding: 11px 4px;
    background: rgba(255, 255, 255, 0.1);
    border: 1px solid rgba(255, 255, 255, 0.18);
  }
  .hero-level span,
  .hero-level i { display: none; }
  .hero-level b { font-size: 1.08rem; }
  .hero-level:hover { background: rgba(255, 255, 255, 0.2); }
}

.eyebrow {
  display: inline-block;
  padding: 8px 12px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.15);
  color: #e7f0ff;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 0.72rem;
  font-weight: 700;
}

.hero-copy h1 {
  margin: 0 0 12px;
  max-width: 22ch;
  font-size: clamp(2.3rem, 3.2vw, 3.7rem);
  line-height: 0.98;
  font-family: var(--font-display);
  font-weight: 700;
}

.hero-copy p {
  margin: 4px 0 0;
  max-width: 62ch;
  font-size: 1.06rem;
  line-height: 1.7;
  color: rgba(255, 255, 255, 0.88);
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 30px;
}

.level-section,
.page-shell {
  padding-top: var(--sp-section);
  padding-bottom: var(--sp-section);
}
/* The level cards arrive after a fetch; until then the footer would sit in
   the first screen and drop when they render. */
.levels-page .page-shell { min-height: 100vh; }
/* The kanji viewer is drawn entirely by its script after a fetch; until then
   the footer sat in the first screen and dropped 900px when the character
   rendered (a shift of 0.58). The view is always taller than a screen. */
#kanjiView { min-height: 100vh; }

.level-card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 22px;
  box-shadow: 0 10px 24px rgba(17, 45, 78, 0.04);
}

.page-intro p,
.section-header p {
  color: var(--muted);
  margin: 0;
  line-height: 1.7;
}

.section-header {
  margin-bottom: 18px;
}

.section-header h2,
.page-intro h1 {
  margin: 0 0 10px;
  color: var(--navy);
  font-size: clamp(2rem, 3vw, 2.8rem);
  font-family: var(--font-display);
}

.level-header-row {
  display: flex;
  justify-content: space-between;
  align-items: end;
  gap: 12px;
}

.text-link {
  color: var(--navy);
  font-weight: 700;
}

.level-grid {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: clamp(12px, 1.1vw, 18px);
}

.level-card {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  min-height: 200px;
  padding: 18px 16px;
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.level-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 16px 28px rgba(17, 45, 78, 0.08);
}

.level-code {
  font-size: clamp(2rem, 2vw, 3rem);
  color: var(--navy);
  font-weight: 800;
  line-height: 1;
}

.level-desc {
  color: var(--muted);
  line-height: 1.6;
  margin-top: 10px;
  font-size: 0.92rem;
}

.level-n1 { background: linear-gradient(180deg, #f8fbff, #edf4ff); }
.level-n2 { background: linear-gradient(180deg, #f8fbff, #eef6f3); }
.level-n3 { background: linear-gradient(180deg, #fffaf3, #fff4d8); }
.level-n4 { background: linear-gradient(180deg, #f9f7ff, #f1ecff); }
.level-n5 { background: linear-gradient(180deg, #f6fbf9, #eafaf2); }

.page-intro {
  padding-top: clamp(24px, 2.6vw, 42px);
  max-width: 68ch;
}

/* ...but not when it is the first thing on the page. <main> already carries a
   section's worth of top padding, so the two stacked: 46px from main plus 37
   here put 84px of nothing between the header and the title on Study, Levels
   and About - three times the 28px the exams page uses. This padding is for
   separating the intro from something above it inside main, and when it
   leads there is nothing above it. */
.page-intro:first-child { padding-top: 0; }

/* Optical, not geometric.

   The heading's box now starts 46px below the header, the same as the exams
   hero card - but a card's top edge is its box top, while a line of type
   begins lower than its own box by the distance between the font's ascender
   and its cap height. Measured on Cormorant Garamond that is 9px at this
   size, so the title read as 55px down beside a card at 46. Pulled back up
   by the same amount, expressed in em so it stays right as the clamp scales
   the heading. Only the space above closes; the gap to the paragraph below
   is untouched. */
.page-intro:first-child h1 { margin-top: -0.2em; }

/* The .mode-grid and .mode-card rules that used to sit here belonged to the
   practice page, which was merged into the levels page and deleted. Nothing
   on this stylesheet's pages used them any more - but exam.css styles the
   exam setup screen with the same two class names, and this copy was still
   winning on the properties exam.css did not restate. That is why the two
   mode descriptions on the setup screen were rendering as bold uppercase
   with wide tracking: a whole sentence shouted, from a rule for a page that
   no longer exists. Same failure as the duplicated .btn definition. */

/* ==========================================================================
   Back to top

   Fixed to the bottom-right corner, and out of the way until it is earned:
   nothing appears until you are two screens down, because a button offering
   to take you to the top of a page you are already at the top of is noise.

   It sits above the sticky exam bar in the stacking order but below the
   header, and it is inset with env(safe-area-inset-*) so it clears the home
   indicator on a phone rather than sitting under it.
   ========================================================================== */

.to-top {
  position: fixed;
  right: calc(18px + env(safe-area-inset-right, 0px));
  bottom: calc(18px + env(safe-area-inset-bottom, 0px));
  z-index: 45;

  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  padding: 0;

  background: var(--navy);
  color: #fff;
  border: 0;
  border-radius: 50%;
  box-shadow: 0 10px 26px rgba(17, 45, 78, 0.28);
  cursor: pointer;

  /* Hidden by default without display:none, so the fade has something to
     animate and the button never steals a tab stop while it is invisible. */
  opacity: 0;
  visibility: hidden;
  transform: translateY(10px) scale(0.94);
  transition: opacity 0.22s ease, transform 0.22s ease, visibility 0.22s;
}

.to-top.is-on {
  opacity: 1;
  visibility: visible;
  transform: none;
}

.to-top span {
  font-size: 1.5rem;
  line-height: 1;
  /* The caret glyph sits low on its own baseline; nudge it onto the centre. */
  margin-top: -3px;
}

.to-top:hover { background: var(--blue); }
.to-top:focus-visible {
  outline: 3px solid var(--gold);
  outline-offset: 3px;
}

@media (max-width: 560px) {
  .to-top { width: 44px; height: 44px; right: 14px; bottom: 14px; }
}

/* Below 560px there is not room for the tagline beside the language picker,
   and a truncated "Word and grammar li..." reads as a bug rather than as a
   caption. The header is sticky, so the row it would otherwise need is the
   most expensive space on the page, and the highlighted nav link already
   says which section you are in. */
@media (max-width: 560px) {
  .brand-tag { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .to-top { transition: none; }
}

/* ==========================================================================
   Footer

   It used to be a rule, a copyright line and three links. Every page on the
   site ended by simply stopping, and fifteen destinations - the levels, the
   kanji browser, the guides, the copyright and takedown notice - were
   reachable only from the navigation or from inside another page.

   Dark, because the page needs an end. The site is white panels on pale blue
   from the header down; a navy band under it closes the document the way the
   navy hero opens the exam library, and nothing above it has to change to
   make room.
   ========================================================================== */
.site-footer {
  /* Measured and trimmed: 56/44/36/18-20 was 391px of footer on a desktop
     and 958px on a phone - on a phone, more than a screenful of navigation
     under every page. None of it was doing anything the smaller numbers do
     not. */
  margin-top: 40px;
  padding: 34px 0 0;
  background: var(--navy);
  color: #cdd8e6;
}

/* Brand block first and widest, then three link columns.

   Written out per breakpoint rather than left to auto-fit. auto-fit with a
   minmax(220px, ...) first track cannot shrink that track below 220px, so on
   a 390px phone it laid out 220px + 150px + a 40px gap and pushed 20px of
   the page off the side. Every track here is minmax(0, ...), which can. */
.footer-grid {
  display: grid;
  gap: 26px 40px;
  grid-template-columns: minmax(0, 1fr);
  padding-bottom: 26px;
}

/* Two columns as soon as two will fit, which on a phone is the single
   biggest saving here: one column stacked brand, Practice, Study and This
   site into 710px of grid, and side by side the two short columns cost one
   row instead of two. Below 380px it goes back to one - "Copyright and
   removal requests" in a 145px column is four lines of ragged text. */
@media (min-width: 380px) {
  .footer-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .footer-brand { grid-column: 1 / -1; }
}

@media (min-width: 620px) {
  .footer-brand { grid-column: auto; }
}

@media (min-width: 900px) {
  .footer-grid {
    grid-template-columns: minmax(0, 1.4fr) repeat(3, minmax(0, 1fr));
  }
}

.footer-mark-wrap {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  color: #fff;
  text-decoration: none;
}

/* The header's mark is a blue tile on white. On navy that disappears, so it
   is inverted rather than reused. */
.footer-mark {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: 11px;
  background: #fff;
  color: var(--navy);
  font-weight: 800;
  font-size: 0.9rem;
  letter-spacing: 0.02em;
}

.footer-name { font-weight: 700; font-size: 1.05rem; }

.footer-tagline {
  margin: 14px 0 0;
  max-width: 34ch;
  font-size: 0.9rem;
  line-height: 1.6;
  color: #9fb0c6;
}

/* The five levels, in the brand block.

   That block held a mark, a name and one line of tagline - 75px of content in
   a 197px cell, so the left third of the footer was two-thirds empty navy
   while the Practice column beside it ran full height. This is what fills it,
   and it earns the space: picking a level is the first thing anyone does
   here, and until now the footer could only offer the generic Levels page.

   The site's level colours are chosen against white and several of them -
   N5's slate especially - disappear on navy, so the pill is a translucent
   tile and the colour is carried by the text, lightened for a dark ground. */
.footer-levels {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 18px;
}

.footer-level {
  min-width: 44px;
  padding: 6px 12px;
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.06);
  color: var(--accent, #cdd8e6);
  font-weight: 800;
  font-size: 0.82rem;
  letter-spacing: 0.05em;
  text-align: center;
  text-decoration: none;
  transition: 0.16s ease;
}

.footer-level:hover,
.footer-level:focus-visible {
  border-color: var(--accent, #fff);
  background: rgba(255, 255, 255, 0.12);
}

.footer-level.level-n5 { --accent: #b9c6d8; }
.footer-level.level-n4 { --accent: #c49bf0; }
.footer-level.level-n3 { --accent: #74cfa2; }
.footer-level.level-n2 { --accent: #85b8ec; }
.footer-level.level-n1 { --accent: #e5bd63; }

.footer-col { display: flex; flex-direction: column; align-items: flex-start; }

.footer-head {
  margin: 0 0 14px;
  font-family: var(--font-sans);
  font-size: 0.95rem;
  font-weight: 700;
  color: #fff;
}

/* Padding, not margin, so each link carries its own tap target: these are
   0.9rem text in a column, and the gap between two of them was the only
   thing separating one destination from the next. */
.footer-col a {
  padding: 6px 0;
  color: #cdd8e6;
  font-size: 0.9rem;
  line-height: 1.45;
  text-decoration: none;
}

.footer-col a:hover,
.footer-col a:focus-visible { color: #fff; text-decoration: underline; }

/* The copyright line gets its own strip, a shade deeper than the band.

   White was the obvious alternative - it is what the reference design does -
   and it was tried and rendered before being rejected. The page above the
   footer is #f4f7fb, so a white bar under a navy band leaves the band as a
   stripe between two light areas: the document stops and then starts again
   instead of ending. Going deeper separates the legal line from the
   navigation without reopening anything, and it reads as the floor of the
   page rather than a new section.

   The strip is full width, which is why .footer-bottom is wrapped rather than
   coloured directly - .footer-bottom is the .container, so a background on it
   stops at the text column and floats as a box inside the navy. */
.footer-legal { background: #0c2038; }

.footer-bottom {
  display: flex;
  flex-wrap: wrap;
  /* Centred. It is one short line in a full-width strip, and hung on the
     left edge it read as the start of a column that never arrived. In the
     middle it reads as the end of the page, which is what it is. */
  justify-content: center;
  text-align: center;
  gap: 8px 20px;
  padding: 13px 0;
  font-size: 0.85rem;
  color: #8fa2ba;
}

/* The generator writes one real link per translation into every page. The
   picker in the header is a <select>, which nothing but a person with a
   mouse can follow, so without this row the eleven other language trees are
   reachable only from the sitemap. Kept quiet: it is wayfinding for crawlers
   and for anyone whose language the picker did not guess, not a feature. */
.lang-links {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px 14px;
  padding: 14px 0 16px;
  border-top: 1px solid rgba(255, 255, 255, 0.14);
  font-size: 0.82rem;
  color: #8fa2ba;
}
.lang-links > span { font-weight: 600; color: #fff; }
.lang-links ul {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 14px;
  list-style: none;
  margin: 0;
  padding: 0;
}
/* Sixteen pixels tall was the text, and the text was the whole tap target.
   Padding gives the row something to hit without changing how it reads. */
.lang-links a {
  display: inline-block;
  padding: 4px 2px;
  color: #9fb0c6;
}
.lang-links a:hover { color: #fff; }
.lang-links a:hover,
.lang-links a:focus-visible { color: var(--blue); text-decoration: underline; }

/* Below this width the six nav links no longer fit beside the brand and the
   language picker, and the wrapped header grew to ~130px of sticky chrome.
   Scroll the nav sideways instead so the header stays a single row. */
@media (max-width: 1100px) {
  .main-nav {
    flex-wrap: nowrap;
    overflow-x: auto;
    gap: 6px;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }

  .main-nav::-webkit-scrollbar { display: none; }

  .main-nav a {
    flex: none;
    padding: 8px 11px;
  }
}

/* ==========================================================================
   Responsive ladder

   Desktop is the design target, so the widths below step *down* from it. Four
   things move: how many columns each grid holds, how tall the sticky chrome
   is, how dense the type is, and where the hero stops being two columns.
   Ordered widest to narrowest so the later, narrower rule wins.
   ========================================================================== */

/* -- Large desktops. The container has hit its 1120px ceiling by now and the
      surplus width is deliberately margin, so the extra room goes into the
      cards themselves rather than into more columns or a wider measure. -- */
@media (min-width: 1560px) {
  .level-card { min-height: 226px; }
}

/* -- Laptops below ~1180px. Five level cards across gets cramped here; three
      is the last count that keeps the level code and its description on
      separate, readable lines. -- */
@media (max-width: 1180px) {
  .level-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* -- Below ~1180px the header runs out of room for the brand, five links, the
      language picker and a call to action all on one line. The button is the
      one that can go: the hero repeats it immediately below, and it is the
      only page that carries one. -- */
@media (max-width: 1180px) {
  .nav-tools > .btn { display: none; }
  .language-select > span { display: none; }
  .language-select { padding: 8px 12px; }
}

/* -- The hero stacks here. Once it does the panel goes full width and its rows
      spread out sideways, so the row reads as a progress strip rather than a
      narrow card against a large blank. -- */
@media (max-width: 1040px) {
  .hero-grid {
    grid-template-columns: minmax(0, 1fr);
    align-items: start;
  }
}

/* -- Tablets. Two level cards across, everything else single file. -- */
@media (max-width: 820px) {
  .level-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .level-card { min-height: 150px; }
}

@media (max-width: 760px) {
  .level-header-row {
    flex-direction: column;
    align-items: flex-start;
  }

  /* Stacking the three header parts cost about 155px of a phone screen before
     any content. Wrap instead: brand and language picker share the first row,
     the links get the second and scroll sideways. */
  .nav-wrap {
    flex-wrap: wrap;
    align-items: center;
    row-gap: 8px;
  }

  /* flex-basis 0, not auto. Flexbox breaks lines on an item's content width
     before it ever shrinks it, so the brand's natural width decided whether
     it shared row one with the picker - and that width is set by the tagline,
     which runs from "About" to "Word and grammar lists" and longer again in
     the other eleven languages. Study wrapped to a third row and stood 48px
     taller than About on the same phone. From a basis of 0 the brand always
     fits beside the picker, then grows into what is left, and the tagline
     truncates instead of moving the layout. */
  .brand-wrap { flex: 1 1 0; min-width: 0; }
  .brand-text { min-width: 0; }
  .brand-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .brand-tag { overflow: hidden; text-overflow: ellipsis; }
  .nav-tools { flex: 0 0 auto; }

  .main-nav {
    order: 3;
    flex-basis: 100%;
  }

  /* Keep the nav on one scrollable line: wrapping six links made the sticky
     header tall enough to swallow most of a phone screen. */
  .main-nav {
    width: 100%;
    flex-wrap: nowrap;
    overflow-x: auto;
    gap: 6px;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  /* Line the row up with the header above it. Each link carries its own
     padding, so "Home" began 11px right of the logo's edge while the picker
     above sat flush right; the row is pulled left by that padding so the
     first word aligns with the logo, and stretched to the container's right
     edge with the links spread so the last pill aligns with the picker.
     When a language overflows there is no free space to spread, and the row
     simply scrolls as before. */
  .main-nav {
    --nav-pad: 11px;
    margin-left: calc(-1 * var(--nav-pad));
    width: calc(100% + var(--nav-pad));
    flex-basis: calc(100% + var(--nav-pad));
    justify-content: space-between;
  }

  .main-nav::-webkit-scrollbar { display: none; }

  .main-nav a {
    flex: none;
    padding: 8px 11px;
    font-size: 0.9rem;
  }

  .hero-copy h1 { max-width: 100%; }

  /* Undo the sideways progress strip: three columns of it do not fit a phone. */

}

/* A phone still has room for two of anything small. Dropping the stat and
   level cards to a single column pushed the practice buttons - the reason the
   page exists - most of a screen further down. Only the cards that carry a
   full-width button of their own go one across. */
@media (max-width: 560px) {
  .level-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .level-card { min-height: 128px; padding: 14px; }
  .level-code { font-size: 1.7rem; }
  .level-desc { font-size: 0.84rem; }
}

/* ==========================================================================
   Simplified components (levels, practice, stats)
   ========================================================================== */

.fact-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
}
.fact {
  padding: 12px 14px;
  background: var(--panel-soft);
  border-radius: 12px;
}
.fact span {
  display: block;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 4px;
}
.fact strong {
  font-size: 1.15rem;
  color: var(--navy);
  font-variant-numeric: lining-nums tabular-nums;
}

/* -- practice sections: colour instead of icons -- */

/* -- stats -- */

.level-progress-grid {
  display: grid;
  gap: 14px;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  margin-bottom: 32px;
}
.level-progress-card {
  padding: 20px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 18px;
}
.lp-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
}
.lp-head h3 {
  margin: 0;
  font-family: var(--font-display);
  font-size: 1.7rem;
  color: var(--navy);
}
.lp-head strong {
  font-size: 1.3rem;
  color: var(--success);
  text-align: right;
  font-variant-numeric: lining-nums tabular-nums;
}
.lp-head strong em {
  display: block;
  font-style: normal;
  font-size: 0.64rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--muted);
}
.lp-bar {
  height: 8px;
  border-radius: 999px;
  background: var(--line);
  margin: 12px 0;
  overflow: hidden;
}
.lp-fill { height: 100%; background: var(--success); border-radius: 999px; }
.lp-meta { font-size: 0.82rem; color: var(--muted); }
.lp-actions { display: flex; gap: 8px; margin-top: 16px; flex-wrap: wrap; }
.lp-actions .btn { padding: 9px 16px; font-size: 0.82rem; }

.section-score-grid {
  display: grid;
  gap: 12px;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
.section-score {
  --accent: var(--blue);
  padding: 18px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-top: 3px solid var(--accent);
  border-radius: 14px;
  text-align: center;
}
.section-score .score {
  font-size: 1.9rem;
  font-weight: 800;
  color: var(--accent);
  font-family: var(--font-display);
}
.section-score .label { font-weight: 700; color: var(--navy); font-size: 0.9rem; }
.section-score .meta { font-size: 0.78rem; color: var(--muted); margin-top: 4px; }

.stats-empty {
  padding: 36px;
  text-align: center;
  color: var(--muted);
  background: var(--panel);
  border: 1px dashed var(--line);
  border-radius: 18px;
}

.about-plain {
  max-width: 68ch;
  line-height: 1.8;
  color: var(--muted);
}
.about-plain p { margin: 0 0 16px; }

@media (max-width: 620px) {
  .fact-grid { grid-template-columns: 1fr; }
}

/* ===================================================== brand press + notice */

/* The brand became a link so it can respond to a press. These keep it looking
   exactly like the old markup: the two lines still stack, and it is not
   underlined or recoloured as a link. */
.brand-wrap { text-decoration: none; color: inherit; cursor: pointer; }
.brand-text { display: flex; flex-direction: column; }
.brand-mark { position: relative; overflow: visible; }
.brand-wrap:hover .brand-mark { transform: translateY(-1px); }
.brand-mark, .brand-name {
  transition: transform .28s cubic-bezier(.34, 1.56, .64, 1), color .2s;
}

/* Pressing it: the badge dips, springs back and throws off one ring, and the
   wordmark nudges across. Short enough not to delay the page change. */
.brand-wrap.is-pressed .brand-mark { animation: brand-pop .58s cubic-bezier(.34, 1.56, .64, 1); }
.brand-wrap.is-pressed .brand-name { animation: brand-shift .58s ease; }

.brand-mark::after {
  content: "";
  position: absolute; inset: 0;
  border-radius: 12px;
  border: 2px solid var(--blue);
  opacity: 0;
  pointer-events: none;
}
.brand-wrap.is-pressed .brand-mark::after { animation: brand-ring .58s ease-out; }

@keyframes brand-pop {
  0%   { transform: scale(1) rotate(0deg); }
  28%  { transform: scale(.9) rotate(-7deg); }
  62%  { transform: scale(1.09) rotate(4deg); }
  100% { transform: scale(1) rotate(0deg); }
}
@keyframes brand-shift {
  0%   { transform: translateX(0); }
  32%  { transform: translateX(4px); }
  100% { transform: translateX(0); }
}
@keyframes brand-ring {
  0%   { opacity: .85; transform: scale(1); }
  100% { opacity: 0;   transform: scale(1.85); }
}

/* Leaving for the home page. */
body { transition: opacity .24s ease; }
body.is-leaving { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  .brand-wrap.is-pressed .brand-mark,
  .brand-wrap.is-pressed .brand-name,
  .brand-wrap.is-pressed .brand-mark::after { animation: none; }
  body, body.is-leaving { transition: none; opacity: 1; }
}

/* One line under the page intro. Small, quiet, and out of the way of the
   level tabs below it. */
.intro-guides {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px 14px;
  margin: 14px 0 0;
  font-size: 0.88rem;
  color: var(--muted);
}
.intro-guides a {
  color: var(--blue);
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: rgba(45, 110, 180, 0.35);
}
.intro-guides a:hover { color: var(--navy); text-decoration-color: currentColor; }
.intro-guides em { font-style: normal; font-size: 0.8rem; opacity: 0.75; }

/* The guide links on the study hub. */

/* ---------------------------------------------------------------- guides
   Long-form pages. One column, measured rather than full width: prose is
   read line by line and a 1,100px line makes the eye lose its place coming
   back to the left margin. 68ch is roughly the 75-character line typography
   settles on. */
/* One measure, in rem, used by the prose and by the back chip above it.

   It was 68ch, which is the right way to say "about 75 characters" - but ch
   resolves against each element's own font-size, and the chip's is smaller,
   so the same 68ch came out narrower there and left the button 28px to the
   right of the text it belongs to. rem is root-relative, so both agree.
   43rem is the same 688px the ch value was producing. */
.guide-page { --measure: 43rem; }
/* Centred in the container. Every other page fills the centred container,
   so it reads as centred; a reading column narrower than the container has
   to be centred itself or it sits in the left half with the right half
   empty - at 1512px that was 690px of text and 430px of nothing beside it.
   The back chip above it keeps to the same left edge as the column. */
.guide { max-width: var(--measure); margin-left: auto; margin-right: auto; }
.guide-page .page-back {
  position: relative;
  left: max(0px, calc((100% - var(--measure)) / 2));
}

.guide-kicker {
  margin: 0 0 8px;
  font-size: 0.72rem;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--blue);
}
.guide h1 {
  margin: 0 0 14px;
  font-family: var(--font-display);
  font-size: clamp(2rem, 3.4vw, 2.9rem);
  line-height: 1.08;
  color: var(--navy);
}
/* Under a translated guide's standfirst: where the text came from and that
   no native speaker has read it yet, with the English original one click
   away. Quiet, because it is a caveat, not content. */
.guide-note {
  margin: -14px 0 26px;
  font-size: 0.84rem;
  line-height: 1.55;
  color: var(--muted);
}
.guide-note a { color: var(--blue); font-weight: 600; }

.guide-standfirst {
  margin: 0 0 30px;
  font-size: 1.14rem;
  line-height: 1.65;
  color: var(--ink-soft, #4a5768);
  padding-bottom: 22px;
  border-bottom: 1px solid var(--line);
}
.guide h2 {
  margin: 38px 0 12px;
  font-family: var(--font-display);
  font-size: clamp(1.35rem, 2vw, 1.7rem);
  color: var(--navy);
}
.guide p { margin: 0 0 16px; line-height: 1.75; color: var(--text); }
.guide strong { color: var(--navy); font-weight: 700; }

.guide-list { margin: 0 0 18px; padding-left: 22px; }
.guide-list li { margin-bottom: 9px; line-height: 1.72; }

/* Wide content scrolls inside its own box rather than pushing the page
   sideways - the same rule the exam papers follow. */
.guide-table-wrap { overflow-x: auto; margin: 0 0 22px; }
.guide-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.93rem;
  min-width: 460px;
}
.guide-table th, .guide-table td {
  padding: 10px 14px;
  text-align: left;
  border-bottom: 1px solid var(--line);
  vertical-align: top;
}
.guide-table thead th {
  font-size: 0.74rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  border-bottom-width: 2px;
}
.guide-table td:first-child { font-weight: 700; color: var(--navy); white-space: nowrap; }

.guide-cta {
  margin: 42px 0 0;
  padding: 22px 24px;
  background: var(--panel-2, #f3f7fc);
  border: 1px solid var(--line);
  border-left: 4px solid var(--gold);
  border-radius: 16px;
}
.guide-cta h2 { margin: 0 0 8px; font-size: 1.2rem; }
.guide-cta p { margin: 0 0 16px; font-size: 0.95rem; }
.guide-cta-actions { display: flex; flex-wrap: wrap; gap: 10px; }

/* The guide index is a hub page, not an article: three cards and a call to
   action. It fills the container the way Study and Levels do, with only its
   running text held to a readable width; the three articles keep the
   reading measure above. */
.guide-hub { max-width: none; }
.guide-hub > p:not(.guide-standfirst) { max-width: 68ch; }
.guide-hub .guide-index { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; }
.guide-hub .guide-index a { height: 100%; box-sizing: border-box; padding: 20px 22px; }
.guide-hub .guide-cta {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "h a" "p a";
  align-items: center;
  column-gap: 32px;
}
.guide-hub .guide-cta h2 { grid-area: h; }
.guide-hub .guide-cta p { grid-area: p; margin: 0; }
.guide-hub .guide-cta-actions { grid-area: a; }
@media (max-width: 900px) {
  .guide-hub .guide-index { grid-template-columns: 1fr; }
  .guide-hub .guide-cta { grid-template-columns: 1fr; grid-template-areas: "h" "p" "a"; row-gap: 0; }
  .guide-hub .guide-cta p { margin: 0 0 16px; }
}

/* The guide index: three rows, each its own target. */
.guide-index { margin: 26px 0 0; padding: 0; list-style: none; display: grid; gap: 12px; }
.guide-index li {
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--panel);
  transition: border-color 0.16s ease, box-shadow 0.16s ease;
}
.guide-index li:hover { border-color: var(--blue); box-shadow: 0 8px 20px -12px rgba(17,45,78,0.4); }
.guide-index a { display: block; padding: 16px 18px; }
.guide-index b { display: block; margin-bottom: 4px; font-size: 1.05rem; color: var(--navy); }
.guide-index li:hover b { color: var(--blue); }
.guide-index span { font-size: 0.9rem; line-height: 1.55; color: var(--muted); }

.guide-more { margin-top: 40px; padding-top: 22px; border-top: 1px solid var(--line); }
.guide-more h2 { margin: 0 0 12px; font-size: 1.1rem; }
.guide-more ul { margin: 0; padding-left: 22px; }
.guide-more li { margin-bottom: 7px; }
.guide-more a { color: var(--blue); text-decoration: underline; text-underline-offset: 3px; }
.guide-more a:hover { color: var(--navy); }

@media (max-width: 640px) {
  .guide-cta { padding: 18px; }
  .guide-cta-actions .btn { flex: 1; justify-content: center; }
}

/* --------------------------------------------------------------- notice */

.notice-section { padding: 26px 0 6px; }

.notice-card {
  background: #fff;
  border: 1px solid var(--line, #e3e8ef);
  border-left: 4px solid var(--gold, #d9a63a);
  border-radius: 14px;
  padding: 22px 24px;
  box-shadow: 0 1px 3px rgba(16, 32, 56, .06);
}
.notice-head h2 { margin: 0 0 4px; font-size: 1.12rem; color: var(--navy); }
.notice-head p { margin: 0 0 16px; font-size: .88rem; color: var(--muted); }

.notice-table-wrap { overflow-x: auto; }
.notice-table {
  width: 100%;
  min-width: 560px;
  table-layout: fixed;
  border-collapse: collapse;
  font-size: .9rem;
}
.notice-table th:nth-child(1) { width: 10%; }
.notice-table th:nth-child(2) { width: 24%; }
.notice-table th:nth-child(3) { width: 33%; }
.notice-table th:nth-child(4) { width: 33%; }
.notice-table th, .notice-table td {
  padding: 9px 12px; text-align: left;
  border-bottom: 1px solid var(--line, #e3e8ef);
  white-space: nowrap;
}
.notice-table thead th {
  font-size: .74rem; letter-spacing: .05em; text-transform: uppercase;
  color: var(--muted); font-weight: 700;
}
.notice-table tbody th { font-weight: 800; color: var(--navy); width: 1%; }
.notice-table tbody tr:last-child th,
.notice-table tbody tr:last-child td { border-bottom: 0; }
.notice-table tr.is-empty td { color: var(--muted); font-style: italic; }

.notice-bad  { color: #b4462f; font-weight: 600; }
.notice-part { color: #8a6a1e; font-weight: 600; }
.notice-dim  { color: var(--muted); }

.notice-foot {
  display: grid; gap: 18px;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  margin-top: 20px; padding-top: 18px;
  border-top: 1px solid var(--line, #e3e8ef);
}
.notice-item h3 {
  margin: 0 0 6px; font-size: .8rem; font-weight: 700;
  letter-spacing: .04em; text-transform: uppercase; color: var(--navy);
}
.notice-item p { margin: 0 0 6px; font-size: .85rem; color: var(--muted); line-height: 1.55; }
.notice-item a { color: var(--blue); font-weight: 600; }
.notice-help { padding: 14px 16px; background: #fdf9ef; border-radius: 10px; }
.notice-help h3 { color: #8a6a1e; }

@media (max-width: 700px) {
  .notice-card { padding: 18px 16px; }
  .notice-table th, .notice-table td { padding: 8px 9px; }
}

/* Attribution block on the About page. CC BY-SA requires the JMdict credit to
   be visible on the site itself, not only in a file in the repository. */
/* The rights notice. Set apart from the credits above it and left-aligned
   even on the centred about page: it is a legal statement someone may need
   to read carefully, not a line of prose. */
.about-rights {
  margin-top: 26px;
  padding: 18px 20px;
  background: #fbfcfe;
  border: 1px solid var(--line);
  border-left: 3px solid var(--navy);
  border-radius: 0 12px 12px 0;
  text-align: left;
  font-size: 0.84rem;
  line-height: 1.7;
}
.about-rights h2 {
  margin: 0 0 8px;
  font-size: 0.9rem;
  color: var(--navy);
}
.about-rights p { margin: 0 0 10px; }
.about-rights p:last-child { margin-bottom: 0; }

.about-credits { border-top: 1px solid var(--line, #e3e8ef); padding-top: 20px; }
.about-credits h2 { font-size: .95rem; color: var(--navy); margin: 0 0 8px; }
.about-credits p { font-size: .85rem; color: var(--muted); line-height: 1.6; }

/* ==========================================================================
   Laptop height

   Width is only half the problem on a laptop. A 14" screen is wide enough for
   the full desktop layout but only ~800-900px tall once the browser chrome is
   gone, and the hero as designed for a desktop monitor pushed the level cards
   below the fold on every visit. These rules keep the desktop columns and
   spend the savings vertically: shorter hero, tighter sections, smaller
   display type. They are height-gated, so an external monitor is unaffected.
   ========================================================================== */

@media (min-width: 1024px) and (max-height: 900px) {
  :root {
    --sp-section: 30px;
    --card-pad: 20px;
  }

  .nav-wrap { padding: 11px 0; }
  .brand-mark { width: 36px; height: 36px; }

  .hero-section { padding: 32px 0 24px; }
  .hero-copy h1 {
    margin: 12px 0 10px;
    font-size: clamp(2.1rem, 2.5vw, 2.9rem);
  }
  .hero-copy p { font-size: 1rem; line-height: 1.62; }
  .hero-actions { margin-top: 18px; }

  .section-header h2,
.page-intro h1 { font-size: clamp(1.7rem, 2.2vw, 2.2rem); }

  .level-card { min-height: 164px; padding: 16px; }
  .level-code { font-size: 2.1rem; }

  .notice-section { padding: 20px 0 4px; }
  .notice-card { padding: 18px 20px; }
}

/* Very short laptops (1280x720 after OS scaling, or a window with a bookmarks
   bar and a devtools dock). One more notch of the same idea. */
@media (min-width: 1024px) and (max-height: 760px) {
  :root { --sp-section: 24px; }

  .hero-section { padding: 24px 0 20px; }
  .hero-copy h1 { font-size: 2rem; margin: 8px 0; }
  .hero-copy p { font-size: 0.95rem; }
  .hero-actions { margin-top: 14px; }
  .level-card { min-height: 140px; }
}

/* ==========================================================================
   About page

   Two columns on a desktop: the prose on the left at a reading measure, the
   attribution and the rights notice beside it.

   It used to be one centred column with centred text, on the reasoning that
   every paragraph was one to three lines - short enough for centring to read
   as deliberate. The page has grown since: five body paragraphs, three
   credit lines and a four-paragraph rights notice. Centred body text at that
   length has no left edge to come back to, and one column left half of a
   desktop empty. */

.about-intro { max-width: 64ch; }
.about-plain { text-align: left; }

/* The page reads top to bottom now: two paragraphs of prose, then the fine
   print in two columns. The prose keeps a reading measure; the notices below
   it use the full width, which is what stops a short page from being a tall
   one. */
.about-lede { max-width: 64ch; margin-top: 22px; }

.about-notices {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(20px, 2.4vw, 34px);
  align-items: start;
  margin-top: 34px;
  padding-top: 26px;
  border-top: 1px solid var(--line);
}
.about-notes-col { display: grid; gap: 18px; align-content: start; }

/* The credits are a card in this column, so the rule that used to separate
   them from prose above would be a line across the top of the card. */
.about-notices .about-credits {
  border-top: 0;
  padding: 18px 20px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 12px;
}
.about-notices .about-rights { margin-top: 0; }

/* Where the audio and the diagrams come from. It reads as a caveat, not as
   part of the description of the site, so it sits at the top of the notices
   column in the same fine print as the credits below it. */
.about-hosting {
  padding: 0 20px 0 0;
  font-size: 0.85rem;
  line-height: 1.6;
  color: var(--muted);
}
.about-hosting p { margin: 0; }

@media (max-width: 860px) {
  .about-notices { grid-template-columns: minmax(0, 1fr); }
}

.about-page .about-credits {
  margin-top: 34px;
  padding-top: 24px;
}

/* The coverage table answers "is my level here?", which belongs on the front
   page. The three columns under it - how the audio is hosted, what the word
   meanings cover, what to do when a recording will not play - are support
   notes, and printing them on the landing page buried the level cards under a
   screen of caveats. They fold away instead. */
.notice-more { margin-top: 18px; border-top: 1px solid var(--line); }

.notice-more > summary {
  padding: 14px 0 0;
  cursor: pointer;
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--navy);
  list-style: none;
}
.notice-more > summary::-webkit-details-marker { display: none; }

.notice-more > summary::before {
  content: "+";
  display: inline-block;
  width: 1.1em;
  font-weight: 800;
  color: var(--gold);
}
.notice-more[open] > summary::before { content: "\2212"; }
.notice-more > summary:hover { color: var(--blue); }

.notice-more .notice-foot { margin-top: 14px; padding-top: 0; border-top: 0; }

/* ==========================================================================
   Practice page

   All five levels, two across. A card carries what you need to choose with -
   what the level covers, how you are doing on it, what it asks of you - and
   the four skills plus the whole paper. Nothing here navigates away to find
   more levels, and nothing is disabled: a level with no paper says so when
   you press it.
   ========================================================================== */

.practice-levels {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(14px, 1.4vw, 20px);
}

.plevel {
  display: flex;
  flex-direction: column;
  padding: var(--card-pad);
  background: var(--panel);
  border: 1px solid var(--line);
  border-top: 4px solid var(--accent, var(--blue));
  border-radius: 20px;
  box-shadow: 0 10px 24px rgba(17, 45, 78, 0.04);
  transition: box-shadow 0.2s ease, border-color 0.2s ease;
}

/* The same five level colours the exam library uses. */
.plevel.level-n1 { --accent: var(--gold-ink); }
.plevel.level-n2 { --accent: #2d6eb4; }
.plevel.level-n3 { --accent: #2f7d57; }
.plevel.level-n4 { --accent: #7c3ac8; }
.plevel.level-n5 { --accent: #5c697a; }

.plevel.is-target {
  border-color: var(--accent, var(--blue));
  box-shadow: 0 0 0 3px rgba(45, 110, 180, 0.14), 0 12px 28px rgba(17, 45, 78, 0.10);
}

.plevel-head {
  display: flex;
  /* Tops, not baselines. With baseline alignment the score block's first
     baseline is its "YOUR BEST" label, so the level code lined up with the
     caption and the number it belongs to hung twenty pixels below it. */
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
}

.plevel-code {
  font-family: var(--font-display);
  font-size: clamp(2rem, 2.6vw, 2.7rem);
  font-weight: 700;
  color: var(--accent, var(--navy));
  line-height: 1;
}

.plevel-score { text-align: right; }
/* Same small-caps label the fact grid below it uses, so the score reads as
   one more labelled figure on the card rather than a loose number. */
.plevel-score em {
  display: block;
  margin-bottom: 3px;
  font-style: normal;
  font-size: 0.64rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--muted);
}
.plevel-score strong {
  display: block;
  font-size: 1.3rem;
  line-height: 1;
  color: var(--success);
  font-variant-numeric: lining-nums tabular-nums;
}
.plevel-score span { font-size: 0.74rem; color: var(--muted); }
.plevel-score.is-empty span { font-style: italic; }

.plevel-desc {
  margin: 10px 0 14px;
  color: var(--muted);
  line-height: 1.55;
  min-height: 2.9em;          /* keeps the five cards on a shared baseline */
}

.plevel-bar {
  height: 6px;
  border-radius: 999px;
  background: var(--line);
  overflow: hidden;
  margin-bottom: 14px;
}
.plevel-bar-fill { height: 100%; background: var(--success); border-radius: 999px; }

/* The four facts. They were four boxed chips in a three-wide grid, so the
   fourth sat alone with a hole beside it, and the boxes gave the card a third
   separate visual language on top of the buttons below them. A plain row of
   figures divided by hairlines carries the same information at a fraction of
   the weight, and four of them fit. */
.plevel-facts {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  margin: 0 0 18px;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.plevel-facts div {
  padding: 11px 0 11px 12px;
  border-left: 1px solid var(--line);
}
.plevel-facts div:first-child { padding-left: 0; border-left: 0; }
.plevel-facts dt {
  font-size: 0.6rem;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--muted);
}
.plevel-facts dd {
  margin: 3px 0 0;
  font-weight: 700;
  color: var(--navy);
  font-size: 0.95rem;
  font-variant-numeric: lining-nums tabular-nums;
}

/* Four ways into the same level, so they are one control group of four equal
   things: a single row, one weight, one colour.

   They used to carry a coloured stripe each - blue, gold, green, red - which
   was decoration pretending to be information. Nothing on this site teaches
   that blue means vocabulary, and worse, the stripes reused the five level
   colours: a green Reading bar sat directly under a green N3 heading and a
   gold Listening bar under gold N1, implying a relationship that does not
   exist. They are secondary actions and now look like it. */
.plevel-skills {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 6px;
  margin-bottom: 16px;
}

.skill-btn {
  padding: 10px 6px;
  border: 1px solid transparent;
  /* A pill, like every other chip on the site. It was a 10px rectangle,
     which made the four of them the one control family with a different
     shape from all the rest. */
  border-radius: 999px;
  background: var(--panel-soft);
  color: var(--navy);
  font-weight: 600;
  font-size: 0.82rem;
  cursor: pointer;
  transition: 0.16s ease;
}

.skill-btn:hover {
  background: #e6eff9;
  border-color: #b9d3ec;
  color: var(--blue);
  box-shadow: 0 4px 10px -4px rgba(45, 110, 180, 0.4);
}

.plevel-foot {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: auto;
}
.plevel-foot .btn { font-size: 0.86rem; padding: 10px 16px; }

.plevel-note { margin: 10px 0 0; font-size: 0.76rem; color: var(--muted); }

.plevel-msg {
  margin: 10px 0 0;
  padding: 11px 13px;
  background: #fdf9ef;
  border: 1px solid rgba(217, 166, 58, 0.3);
  border-radius: 10px;
  font-size: 0.83rem;
  color: #7a5f19;
  line-height: 1.5;
}
.plevel-msg a { color: var(--blue); font-weight: 700; text-decoration: underline; }
.plevel-msg.is-flash { animation: msg-in 0.35s ease; }

@keyframes msg-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .plevel-msg.is-flash { animation: none; }
}

@media (max-width: 1040px) {
  .practice-levels { grid-template-columns: minmax(0, 1fr); }
  .plevel-desc { min-height: 0; }
}

@media (max-width: 560px) {
  /* Four across stops being readable at this width for both rows. */
  .plevel-facts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .plevel-facts div:nth-child(odd) { padding-left: 0; border-left: 0; }
  .plevel-facts div:nth-child(n + 3) { border-top: 1px solid var(--line); }
  .plevel-facts dd { font-size: 0.9rem; }
  .plevel-skills { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .plevel-foot .btn { width: 100%; }
}

/* ==========================================================================
   Study lists (N5 / N4 vocabulary and grammar)

   Long lists - N5 alone is 757 words - so the row is built as a grid rather
   than a table: it can re-flow to two lines on a phone, which a table cell
   cannot do without losing its column.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Study page

   No width of its own: the lists sit on the same column as the header, the
   footer and every other page (see --content). The one thing kept from the
   narrower measure is the cap on running text - a paragraph wants ~68
   characters a line whatever the grid around it is doing.
   -------------------------------------------------------------------------- */

/* ==========================================================================
   What the site holds - six figures on the home page

   Number first and large, label under it, one line of detail below that.
   The figure is the argument: "2,211 kanji" persuades where "kanji lists"
   does not, and a number needs no translating.
   ========================================================================== */

.features-section { padding-top: var(--sp-section); }

.feature-stat-grid {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: clamp(10px, 1.1vw, 16px);
}

.feature-stat {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 18px 16px 16px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-top: 3px solid var(--accent, var(--blue));
  border-radius: 16px;
  box-shadow: 0 6px 18px rgba(17, 45, 78, 0.05);
}

.feature-stat strong {
  font-family: var(--font-display);
  font-size: clamp(1.7rem, 2.2vw, 2.3rem);
  line-height: 1;
  color: var(--accent, var(--navy));
  font-variant-numeric: lining-nums tabular-nums;
}

.feature-stat-label {
  margin-top: 6px;
  font-weight: 700;
  font-size: 0.88rem;
  color: var(--navy);
}

.feature-stat-sub {
  font-size: 0.74rem;
  line-height: 1.45;
  color: var(--muted);
}

@media (max-width: 1080px) {
  .feature-stat-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 620px) {
  .feature-stat-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .feature-stat { padding: 14px 13px 13px; }
}

/* The word-meanings line under the coverage table on the home page. Tinted
   rather than plain, because it is the one line on that panel doing sales
   rather than reporting. */
.notice-words {
  margin: 14px 0 0;
  padding: 12px 16px;
  background: #f3f8ff;
  border-left: 3px solid var(--blue);
  border-radius: 0 10px 10px 0;
  font-size: 0.86rem;
  line-height: 1.6;
  color: var(--text);
}
.notice-words strong {
  color: var(--navy);
  font-variant-numeric: lining-nums tabular-nums;
}

/* A pill that goes back one step. Used by the exam library, the kanji page
   and the back link every other page mounts, so it lives here rather than in
   exam.css, which only the exam pages load. */
.chip-back {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 9px 16px 9px 13px;
  margin-right: 4px;
  border: 1px solid #dbe4f0;
  border-radius: 999px;
  background: var(--panel-soft);
  color: var(--navy);
  font-weight: 700;
  font-size: 0.86rem;
  white-space: nowrap;
  box-shadow: 0 1px 3px rgba(17, 45, 78, 0.06);
}
.chip-back:hover {
  background: #fff;
  border-color: var(--blue);
  color: var(--blue);
  box-shadow: 0 6px 14px -5px rgba(45, 110, 180, 0.35);
}

/* The back link the script adds to the top of a page. It is the first thing
   in <main>, above the heading, so it reads as "out of here" rather than as
   part of the content. */
/* The back link is the first thing inside <main>, and <main> already has a
   generous top padding - so its own top margin was added to that and left
   about seventy pixels of nothing above the first control on the page. */
.page-back {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin-top: 0;
}
/* The guide article is the odd one out: this rule was written for a
   .page-intro, and on a guide the chip's next sibling is the <article>, so
   nothing applied and the kicker sat 4px under the button against the 17-19px
   every other page with a chip gives it. */
.page-back + .page-intro,
.page-back + .guide { padding-top: 14px; }

.study-page .page-intro p { max-width: 68ch; }

/* What each level actually contains, and which languages the meanings are
   written in. Answering "is my level covered, and is it in my language?"
   before someone clicks through five tabs to find out. Doubles as the only
   cross-link between the ten list pages. */
.study-coverage { margin-top: clamp(18px, 2vw, 26px); }

.study-coverage h2 {
  margin: 0 0 10px;
  font-size: 0.95rem;
  color: var(--navy);
}

/* The folded version on the hub. Styled to match the heading it replaces, so
   closing the table does not turn its title into a different kind of object -
   it is the same line with a marker on it. */
.coverage-fold > summary {
  display: flex;
  align-items: center;
  gap: 8px;
  width: fit-content;
  margin: 0 0 10px;
  padding: 6px 12px 6px 10px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--panel);
  cursor: pointer;
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--navy);
  list-style: none;
}
.coverage-fold > summary::-webkit-details-marker { display: none; }
.coverage-fold > summary::before {
  content: "";
  width: 7px;
  height: 7px;
  border-right: 2px solid var(--blue);
  border-bottom: 2px solid var(--blue);
  transform: rotate(-45deg) translate(-1px, 1px);
  transition: transform 0.18s ease;
}
.coverage-fold[open] > summary::before {
  transform: rotate(45deg) translate(-2px, -2px);
}
.coverage-fold > summary:hover { border-color: var(--blue); }
.coverage-fold > summary:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .coverage-fold > summary::before { transition: none; }
}

.coverage-wrap {
  overflow-x: auto;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 16px;
}

.coverage-table {
  width: 100%;
  border-collapse: collapse;
  font-variant-numeric: lining-nums tabular-nums;
}

.coverage-table th,
.coverage-table td {
  padding: 11px 14px;
  text-align: left;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}

.coverage-table thead th {
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--muted);
  background: var(--panel-soft);
}

.coverage-table tbody tr:last-child th,
.coverage-table tbody tr:last-child td { border-bottom: 0; }
.coverage-table tbody tr:hover { background: var(--panel-soft); }

.coverage-table a { color: var(--blue); font-weight: 700; }
.coverage-table a:hover { text-decoration: underline; }

/* The row you are already reading is not a link to itself. */
.coverage-table .is-here { color: var(--navy); font-weight: 800; }
/* On the hub nothing in this table is a link, so every number is an .is-here
   and 800 across fifteen cells reads as a wall. It is data there, not a
   current-page marker. */
.study-coverage.is-static .coverage-table .is-here {
  color: var(--ink, #26313f);
  font-weight: 700;
}

.coverage-yes { color: var(--success); font-weight: 700; }
.coverage-part { color: var(--muted); font-size: 0.82rem; }
.coverage-no { color: var(--line); }

/* Three controls that do three different things - filter by level, switch
   list, search - sat 12px apart and read as one long strip of pills. 22px
   between them is enough to tell them apart without labelling each one in
   twelve languages. */
.study-controls {
  display: flex;
  align-items: center;
  gap: 12px 22px;
  flex-wrap: wrap;
  /* The 10px above the pills and the 18px below them used to be margin. They
     are padding now, because the bar is pinned below and a margin sits
     outside the background box: rows travelling through a transparent 18px
     gap between the pinned bar and the pinned column heading read as a
     rendering fault rather than as one surface. The top margin gives the
     10px back, so nothing on the page moves. */
  margin: calc(clamp(18px, 2vw, 28px) - 10px) 0 0;
  padding: 10px 0 18px;
}

/* The bar travels with the list.

   These lists run from 280 rows to 2,211, and the level, the kind and the
   search box were nailed to the top of the document: at row 400 of N4
   vocabulary the only way to N4 kanji was to scroll the whole way back up.
   So the bar stays on screen and the column heading pins directly under it -
   two lines, one stack, reading as a single surface with the list passing
   behind it.

   What deliberately does not travel: the row count, the marking legend and
   the four filter chips. The count is a fact you read once, the legend is an
   instruction you read once, and the chips are a choice you make before you
   start reading rather than while you are reading. Pinning all of it would
   cost 258px of an 800px laptop window - a third of the screen held
   permanently against the thing you came here to read - and the two lines
   that are worth keeping would be buried in it.

   Phones are excluded, further down: there the bar wraps to three rows, and
   that width has already decided (see the site-header rule) that the list
   gets the top of the screen. */
/* The measured height of that bar, from study.js. Nought until the script
   has run: without it the tabs do nothing, the bar is not pinned (see
   .has-bar below), and the column heading has only the site header to
   clear. */
.study-page { --controls-h: var(--controls-raw, 0px); }


/* Where a change of list lands (see returnToListTop in study.js): under the
   pinned pair, not behind them, so the new count and the column heading are
   the first things you see. Kept here rather than added up in the script,
   because it has to agree with the two sticky rules above and below it. */
.study-page #studyContent {
  /* Two pixels beyond the pinned pair rather than two short of it. Short of
     it, the landing left a two-pixel transparent seam between the header and
     the bar with the page showing through; beyond it, the bar is flush and
     the two pixels are hidden behind it, at the blank top edge of a card. */
  scroll-margin-top: calc(var(--header-h, 74px) + var(--controls-h, 0px) - 2px);
}

@media (min-width: 861px) {
  /* has-bar is set by study.js once it has measured the bar. Pinning it
     without the script would be pinning a row of buttons that do nothing
     over a heading it would then cover: the lists are in the page before
     the script runs, and stay readable without it. */
  .study-page.has-bar .study-controls {
    position: sticky;
    top: var(--header-h, 74px);
    z-index: 3; /* over the column heading, which is 2 */
  }

  /* Transparent at the top of the page, where it sits on the page's own
     ground and has nothing underneath it; a surface only once something is
     passing behind. Paint alone changes - no border, no padding - so the
     list does not jump by the height of an edge at the moment it sticks.

     Opaque, and not the frosted glass of the header above it, which was the
     first try: at 90% white with a 12px blur a word passing under the bar
     was still legible through it as a grey smear, which is the fault this
     was meant to fix. The header gets away with it over a page of cards and
     white space; a bar with two hundred rows sliding under it does not. */
  .study-page.has-bar .study-controls.is-stuck {
    background: #fff;
    border-radius: 14px 14px 0 0;
  }

  /* One line, down to the width where the bar stops being one.

     The pills come in a little below 1220px, which is where the search
     box and the Quiz button drop to a second line at full size - measured:
     the two pill groups want 644px, and the search box's 160px floor plus
     the button makes the pair beside them 359px. Tightened, one line holds
     down to about 1000px; below that it wraps however the pills are sized,
     so the search stops being capped and takes the second line whole, with
     the button on its end. A bar whose lower half is empty white reads as a
     mistake; a full-width search field reads as a search field. */
  @media (max-width: 1219px) {
    .study-page .study-controls { gap: 10px 14px; }
    .study-page .study-tab { padding: 9px 13px; }
    .study-page .study-find { flex: 1 1 200px; gap: 10px; max-width: none; }
    .study-page .study-search { min-width: 120px; max-width: none; }
  }
}

/* The five level pills and the three kind pills are fixed-width groups. On a
   320px screen (an SE, or any phone with the text size turned up) they were
   ten pixels wider than the viewport, which scrolls the whole page sideways -
   the one layout failure a phone user cannot ignore. Let the group scroll
   inside itself instead, and stop the pills being squeezed out of shape. */
.study-tabs {
  display: flex;
  max-width: 100%;
  gap: 4px;
  padding: 4px;
  background: var(--panel-soft);
  border: 1px solid var(--line);
  border-radius: 999px;
  overflow-x: auto;
  scrollbar-width: none;
}
.study-tabs::-webkit-scrollbar { display: none; }
.study-tab { flex: none; }

.study-tab {
  padding: 9px 18px;
  border: 1px solid transparent;
  border-radius: 999px;
  background: transparent;
  color: var(--muted);
  font-weight: 700;
  font-size: 0.88rem;
  cursor: pointer;
  transition: 0.16s ease;
}
.study-tab:hover { color: var(--navy); }
.study-tab.is-on {
  background: #fff;
  border-color: var(--line);
  color: var(--navy);
  box-shadow: 0 3px 10px -3px rgba(17, 45, 78, 0.32);
}

.study-search {
  margin-left: auto;
  padding: 11px 18px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: #fff;
  /* Flexible, not fixed: at 24vw it pushed the Quiz button onto a line of
     its own on a 1280px screen. It takes what the row has left, between
     180px and 320px, and the button stays beside it. */
  flex: 1 1 160px;
  min-width: 160px;
  max-width: 320px;
}
/* Search and the Quiz button are one group, so when the row runs out they
   drop to the next line together rather than the button alone. */
.study-find {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-left: auto;
  flex: 1 1 320px;
  max-width: 460px;
}
.study-find .study-search { margin-left: 0; }

/* The row count. It used to be a bare grey line floating in the gap between
   the tabs and the list, belonging to neither: no container, no alignment to
   the table header under it, and a full 10px of nothing on each side of it.
   Attach it to the list instead - it is a caption for the table, so it sits
   on the list's own top edge, in the same tinted band as the column headings,
   and reads as part of the thing it counts. */
.study-count {
  /* The caption and the cover switch share this line. On anything wider than
     a phone the right two thirds of it were empty, and the switch belongs
     with the list it acts on rather than in the marks bar, which only word
     lists have. */
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px 16px;
  margin: 0;
  padding: 10px 18px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-bottom: 0;
  border-radius: 17px 17px 0 0;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--muted);
  font-variant-numeric: lining-nums tabular-nums;
}

/* A caveat that belongs to the list, so it sits between the caption and the
   rows rather than floating above both. Only the grammar lists use it. */
.study-note {
  margin: 0;
  padding: 0 18px 12px;
  background: var(--panel);
  border-inline: 1px solid var(--line);
  font-size: 0.78rem;
  line-height: 1.6;
  color: var(--muted);
}
.study-count + .study-note { border-top: 0; }

/* The list now starts under that caption, so it squares off its own top. */
.study-count + .study-list,
.study-note + .study-list {
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}
.study-count + .study-list .study-row.is-head,
.study-note + .study-list .study-row.is-head { border-radius: 0; }

/* No overflow:hidden here. It would make this element the scroll container
   for the sticky heading row inside it, which parks the heading below the
   first word instead of above it. The first and last rows round their own
   corners instead. */
.study-list {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 18px;
}

.study-row {
  display: grid;
  /* A fourth, empty track. The meaning column used to be the 1fr, so on a
     desktop it took every pixel the other two did not: 670px of column for a
     median meaning that wants 62px and a longest that wants 309. The label
     sat 630px from the word it belongs to and the three columns read as
     three separate lists. Capped, the meanings sit beside their readings and
     the leftover becomes card, not column.

     Words only. The kanji rows below override this - their meanings really
     do run to 683px at the median - and so do the grammar rows, whose third
     column holds example sentences. */
  grid-template-columns: 190px 190px minmax(0, 24rem) minmax(0, 1fr);
  gap: 16px;
  padding: 12px 18px 12px 56px;
  border-top: 1px solid var(--line);
  align-items: baseline;
}

/* The row's place in the list.

   These lists run from 280 rows to 2,211, and a reader working through one
   has no way of telling whether they are a tenth or two thirds of the way
   down: every row looks like every other row. The count at the top says how
   many there are; this says which one you are looking at.

   A CSS counter rather than a number written into each row, for two reasons.
   The rows are built twice - once here at build time and again by study.js
   whenever a filter changes - so a written number would have to be produced
   and renumbered in both places, and it would be wrong in the filtered view
   the moment it was not. And 2,211 numbers in the markup is weight on a page
   that already carries eleven thousand elements.

   It sits in the row's own left padding rather than in a grid track, because
   there are three different row layouts here - words, kanji and grammar,
   each with its own columns and its own phone override - and this way none
   of them has to know about it. Tabular figures so 1, 10 and 100 line up on
   their right edge instead of drifting. */
.study-list { counter-reset: study-row; }

.study-row:not(.is-head) { counter-increment: study-row; }

.study-row:not(.is-head)::before {
  content: counter(study-row);
  /* Centred against the first cell's line box rather than hung from the top
     of the row. Sharing a `top` was not the same as sharing a baseline: the
     digits are 0.78rem and the word is 1.05rem, so from the same top edge the
     number sat about five pixels high and read as a superscript. Worse, when
     a row slid under the pinned heading the number was sliced through the
     middle while the word beside it was still whole, which looked like a
     rendering fault rather than a row passing behind something.

     13px and 25px are measured, not guessed, and they are the same on all
     three row types: words, kanji and grammar all set their first cell 13px
     down, and the two text ones give it a 25px line box. The kanji tile is
     taller, so there the number aligns with the top of the character - which
     is where the eye starts on that row anyway. */
  position: absolute;
  left: 0;
  top: 13px;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  width: 44px;
  height: 25px;
  padding-right: 12px;
  color: #94a3b5;
  font-size: 0.78rem;
  font-variant-numeric: tabular-nums;
}

.study-row { position: relative; }
.study-row:first-child { border-top: 0; }
/* A word list is two to three thousand rows, eleven thousand elements, and
   the browser laid every one of them out before it painted the heading:
   1.8 s of style and layout on a phone. With content-visibility the rows
   below the fold are skipped until they scroll near; the intrinsic size is
   the placeholder height for the scrollbar until a row has been laid out
   once, after which "auto" remembers its real height. The head row keeps
   normal rendering - it is always on screen first. */
.study-row:not(.is-head) {
  content-visibility: auto;
  /* Measured: a word row is 65px on a desktop, 83px on a phone, and kanji
     and grammar rows are 83px everywhere. */
  contain-intrinsic-size: auto 65px;
}
.study-row.is-kanji:not(.is-head),
.study-row.is-grammar:not(.is-head) { contain-intrinsic-size: auto 83px; }
@media (max-width: 820px) {
  .study-row:not(.is-head) { contain-intrinsic-size: auto 83px; }
}
.study-row.is-grammar { grid-template-columns: 200px minmax(0, 1fr) minmax(0, 1.2fr); }

/* Sticky so the columns stay named while you scroll a few hundred rows. */
.study-row.is-head {
  position: sticky;
  /* Under the site header and under the pinned bar of tabs. The bar's height
     is measured by the script, not assumed: the search box drops to a second
     line around 900px, and the three kind labels are long enough in
     Vietnamese and Nepali to wrap it well before that. */
  top: calc(var(--header-h, 74px) + var(--controls-h, 0px));
  z-index: 2;
  background: var(--panel-soft);
  /* Rows pass behind this, and without an edge the topmost one just ended in
     mid-air: same flat colour above and below the cut, so it read as content
     being clipped rather than travelling underneath. The shadow is what makes
     it a surface. Small enough that it is not a band in its own right when
     the list is at the top of the page and nothing is under it yet. */
  box-shadow: 0 6px 12px -9px rgba(17, 45, 78, 0.5);
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

.study-row:not(.is-head):hover { background: var(--panel-soft); }

.study-row.is-head { border-radius: 17px 17px 0 0; }
.study-row:last-child { border-radius: 0 0 17px 17px; }

.study-jp {
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--navy);
}

.study-reading { color: var(--muted); font-size: 0.92rem; }
.study-reading em {
  display: block;
  font-style: normal;
  font-size: 0.76rem;
  /* Was 0.75 opacity, which read as #858f9b on white: 3.3:1 at 12px. The
     smaller size already sets it apart from the kana. */
  color: var(--muted);
}

.study-en { color: var(--text); line-height: 1.5; }
.study-ne {
  display: block;
  font-style: normal;
  font-size: 0.88rem;
  color: var(--muted);
  margin-top: 2px;
}

.study-example b { display: block; font-weight: 600; color: var(--navy); }
.study-example em {
  display: block;
  font-style: normal;
  font-size: 0.85rem;
  color: var(--muted);
  margin-top: 2px;
}

@media (max-width: 820px) {
  .study-search { margin-left: 0; width: auto; }
  /* Narrower gutter for the number: 56px of a 390px screen is a seventh of
     the row given to four characters. */
  .study-row { padding-left: 42px; }
  .study-row:not(.is-head)::before { width: 32px; padding-right: 8px; font-size: 0.72rem; }
  .study-row,
  .study-row.is-grammar {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr);
    gap: 10px 14px;
  }
  /* The reading belongs under its word, not in a column of its own. */
  .study-row .study-reading { grid-column: 1; margin-top: -6px; }
  .study-row .study-en { grid-column: 2; grid-row: 1 / span 2; }
  .study-row.is-grammar .study-example { grid-column: 1 / -1; }
  .study-row.is-head .study-reading,
  .study-row.is-head span:nth-child(2) { display: none; }
  .study-row.is-head { grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr); }
}

/* On a phone the site header is two rows tall, and with the list's own
   heading pinned under it that is 149px of an 844px screen held permanently
   against a list of several hundred rows. The exam page already makes this
   trade at this width and for this reason: the header scrolls away and the
   thing you actually need while scanning - the column names - keeps the top
   of the screen to itself. The way back up is the button in the corner. */
@media (max-width: 860px) {
  /* Not the kanji browser, which shares the body class but has no list: it
     is a single character with its stroke order, and there is nothing there
     that scrolling past should cost you the navigation for. */
  .study-page:not(.kanji-page) .site-header { position: static; }

  /* The bar travels here too, and everything on it stays in sight.

     The first try put the two pill groups on one line that scrolled
     sideways, which made the pinned bar 126px instead of 190px. It also hid
     three of the five levels off the left edge, and a control you have to go
     looking for sideways is worse than a control that costs a few pixels:
     on a phone there is no hover, no scrollbar and no second chance to
     notice it is there.

     So all three rows stay - levels, kinds, search - and the height comes
     out of the rows themselves instead. Tighter pills, a tighter field and
     tighter gaps bring 190px down to about 165, which with the column
     heading under it is a quarter of an 844px screen. The site header has
     already stood down at this width, so that is everything being held. */
  .study-page .study-controls {
    gap: 8px 12px;
    padding-top: 8px;
    padding-bottom: 12px;
  }
  .study-page .study-tab { padding: 7px 12px; }
  .study-page .study-tabs { padding: 3px; }
  .study-page .study-search { padding: 9px 15px; }
  .study-page .study-quiz { padding: 9px 18px; }
  .study-page .study-find { width: 100%; max-width: none; margin-left: 0; }

  /* Edge to edge.

     The list is the page at this width, and it was a card floating in an
     18px gutter: a tenth of a 390px screen spent on nothing, and the column
     that paid for it was the meaning - "to brush teeth, to polish" wrapping
     to two lines with 36px of empty gutter beside it. The rows keep their
     own padding, so no text touches the glass; only the card does. The bar
     pads itself back in, so the pills stay where they were and it is just
     its white that reaches the edges - the same width as the header above
     it. */
  .study-page .study-controls {
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter);
  }
  .study-page .study-count,
  .study-page .study-note,
  .study-page .study-marks,
  .study-page .study-list {
    margin-inline: calc(var(--gutter) * -1);
    border-inline: 0;
    border-radius: 0;
  }
  .study-page .study-row.is-head,
  .study-page .study-row:last-child { border-radius: 0; }

  .study-page.has-bar .study-controls {
    position: sticky;
    top: 0;
    z-index: 3;
  }
  .study-page.has-bar .study-controls.is-stuck { background: #fff; }

  /* Under the bar, and under nothing else - the site header is in the flow
     at this width. */
  .study-page .study-row.is-head { top: var(--controls-h, 0px); }
  .study-page #studyContent {
    scroll-margin-top: calc(var(--controls-h, 0px) - 2px);
  }
}

/* ------------------------------------------------------------------------
   Know / don't know - the marks on each word row and the filters above them.

   Desktop word rows already have a fourth, empty track (see .study-row); the
   two buttons sit in it, so a row is the same height with them as without.
   On a phone the row gains a narrow third column, reserved in the CSS
   whether or not the script has drawn the buttons yet, and the two stack
   vertically inside it - 56px, shorter than the word-and-reading column
   beside them. Either way the first script render moves nothing.
   ------------------------------------------------------------------------ */
.study-mark {
  display: inline-flex;
  gap: 6px;
  align-self: center;
  justify-self: start;
}
.study-mark button {
  width: 28px;
  height: 28px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--panel);
  color: var(--muted);
  font: 700 0.9rem/1 var(--font-sans);
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.study-mark button:hover { border-color: var(--navy); color: var(--navy); }
.study-mark button:focus-visible { outline: 2px solid var(--navy); outline-offset: 2px; }
.study-mark button[data-m="k"][aria-pressed="true"] {
  background: #1f7a4d; border-color: #1f7a4d; color: #fff;
}
.study-mark button[data-m="d"][aria-pressed="true"] {
  background: var(--danger, #c84a52); border-color: var(--danger, #c84a52); color: #fff;
}
/* A marked row shows its state along the left edge: a known word steps
   back, a word still being learnt stays as bold as the rest. */
.study-row.is-known { box-shadow: inset 3px 0 0 #1f7a4d; }
.study-row.is-known .study-jp { color: var(--muted); }
.study-row.is-learning { box-shadow: inset 3px 0 0 var(--danger, #c84a52); }

.study-marks {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px 18px;
  padding: 0 18px 12px;
  background: var(--panel);
  border-inline: 1px solid var(--line);
}
.study-count + .study-marks { border-top: 0; }
.study-marks + .study-list {
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}
.study-marks + .study-list .study-row.is-head { border-radius: 0; }
.study-marks-legend {
  margin: 0;
  flex: 1 1 260px;
  font-size: 0.78rem;
  line-height: 1.6;
  color: var(--muted);
}
.study-marks-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}
.study-filter {
  padding: 6px 12px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--panel);
  color: var(--muted);
  font: 600 0.76rem/1.2 var(--font-sans);
  cursor: pointer;
}
/* Cover answers: the same chip as the filters above the list, because it is
   the same kind of thing - a switch that changes how the list reads. */
.study-cover {
  padding: 6px 12px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--panel);
  color: var(--muted);
  font: 600 0.76rem/1.2 var(--font-sans);
  cursor: pointer;
}
.study-cover:hover { color: var(--navy); border-color: var(--navy); }

/* The mask.

   Transparent text on a tinted tile rather than a blur: a two-character
   reading stays perfectly legible through any blur soft enough to look
   deliberate. The tile keeps the cell's own size, so switching it on and off
   moves nothing - the row is the same height covered as uncovered, and a
   list you are halfway down does not jump under you.

   The word itself is never covered. It is the question. Nor is a reading
   that only repeats it: see is-echo in study.js. */
.study-list.is-covered .study-row:not(.is-head):not(.is-shown)
  :is(.study-reading:not(.is-echo), .kanji-readings, .study-en, .study-example),
.study-list.is-covered .study-row:not(.is-head):not(.is-shown)
  :is(.study-reading:not(.is-echo), .kanji-readings, .study-en, .study-example) * {
  color: transparent;
}

.study-list.is-covered .study-row:not(.is-head):not(.is-shown)
  :is(.study-reading:not(.is-echo), .kanji-readings, .study-en, .study-example) {
  background: var(--panel-soft);
  border-radius: 8px;
  box-shadow: inset 0 0 0 1px var(--line);
  cursor: pointer;
  /* Or a drag across the row would reveal it through the selection. */
  -webkit-user-select: none;
  user-select: none;
}

.study-list.is-covered .study-row:not(.is-head):not(.is-shown)
  :is(.study-reading:not(.is-echo), .kanji-readings, .study-en, .study-example):hover {
  background: #e2ebf6;
}

.study-filter.is-on { background: var(--navy); border-color: var(--navy); color: #fff; }
.study-filter b { margin-left: 5px; font-weight: 700; font-variant-numeric: tabular-nums; }
.study-marks-quiz {
  margin-left: 6px;
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--navy);
  text-decoration: none;
  white-space: nowrap;
}
.study-marks-quiz:hover { text-decoration: underline; }
.study-marks + .stats-empty {
  margin: 0;
  padding: 26px 18px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-top: 0;
  border-radius: 0 0 17px 17px;
}

@media (max-width: 820px) {
  .study-row:not(.is-grammar):not(.is-kanji) {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr) 28px;
  }
  .study-row .study-mark {
    grid-column: 3;
    grid-row: 1 / span 2;
    flex-direction: column;
    align-self: start;
    justify-self: end;
  }
  .study-marks { padding-bottom: 14px; }
}

/* ------------------------------------------------------------------------
   The home page's review strip.

   One row: what the queue has for you today and the way into it. Its height
   is fixed rather than left to the sentence inside it, because that sentence
   is the one thing the script changes after the page has painted - a strip
   that grew by a line when the count arrived would push the whole page down.
   ------------------------------------------------------------------------ */
.review-strip-wrap { padding-top: clamp(20px, 2.4vw, 32px); }
.review-strip {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px 24px;
  min-height: 92px;
  padding: 18px 24px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-left: 4px solid var(--gold);
  border-radius: 16px;
}
.review-strip-text { min-width: 0; }
.review-strip h2 { margin: 0 0 4px; font-size: 1.1rem; color: var(--navy); }
.review-strip p {
  margin: 0;
  color: var(--muted);
  font-size: 0.92rem;
  line-height: 1.55;
  /* Exactly two lines, never one and never three: this sentence is the one
     thing the script rewrites after the page has painted, and "5 due today"
     is a line shorter than the line it replaces. Clamped at the top so a
     long translation cannot spill, floored at the bottom so a short count
     cannot shrink the strip and lift the whole page under it. */
  min-height: 3.1em;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.review-strip .btn { flex: none; }
@media (max-width: 620px) {
  .review-strip { flex-direction: column; align-items: stretch; }
  .review-strip .btn { justify-content: center; }
}

/* Progress page: one line per level with marks. */
.stats-know-list {
  list-style: none;
  margin: 8px 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 6px 18px;
  font-size: 0.92rem;
  color: var(--text);
}
.stats-know-list li b { color: var(--navy); margin-right: 6px; }
.stats-know-list a { color: var(--navy); text-decoration: none; font-weight: 600; }
.stats-know-list a:hover { text-decoration: underline; }

/* ==========================================================================
   Levels comparison table

   Five level cards, each carrying what the level asks of you and the buttons
   that start a drill. This used to be a comparison table on one page and the
   same facts as cards on another; see renderLevels() in site.js.
   ========================================================================== */

/* One chip per level, in that level's colour, as everywhere else. */
.lv-chip {
  display: inline-grid;
  place-items: center;
  min-width: 46px;
  padding: 5px 12px;
  border-radius: 999px;
  background: var(--accent, var(--blue));
  color: #fff;
  font-weight: 800;
  font-size: 0.84rem;
  letter-spacing: 0.05em;
}
.lv-chip.level-n1 { --accent: var(--gold-ink); }
.lv-chip.level-n2 { --accent: #2d6eb4; }
.lv-chip.level-n3 { --accent: #2f7d57; }
.lv-chip.level-n4 { --accent: #7c3ac8; }
.lv-chip.level-n5 { --accent: #5c697a; }

/* Where the numbers come from, and which of them are estimates. Quiet, but
   present: a site that states a pass mark should say who says so. */
.levels-source {
  margin: 10px 0 0;
  font-size: 0.76rem;
  line-height: 1.6;
  color: var(--muted);
}
.levels-source a { color: var(--blue); font-weight: 600; }
.levels-source a:hover { text-decoration: underline; }

.levels-note {
  margin: 18px 0 0;
  padding: 14px 16px;
  background: #fdf9ef;
  border-left: 3px solid var(--gold);
  border-radius: 0 10px 10px 0;
  font-size: 0.86rem;
  line-height: 1.6;
  color: #7a5f19;
  max-width: 78ch;
}

/* ==========================================================================
   Phone: the three places the desktop layout does not survive a 390px screen
   ========================================================================== */

/* -- 1. The nav row scrolls sideways. Fade the edge that has more behind it,
      so a half-cut word reads as "there is more" rather than as breakage. -- */
.main-nav.can-scroll:not(.at-end) {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
          mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
}
.main-nav.can-scroll:not(.at-start):not(.at-end) {
  -webkit-mask-image: linear-gradient(to right, transparent, #000 24px,
                                      #000 calc(100% - 28px), transparent);
          mask-image: linear-gradient(to right, transparent, #000 24px,
                                      #000 calc(100% - 28px), transparent);
}
.main-nav.can-scroll.at-end:not(.at-start) {
  -webkit-mask-image: linear-gradient(to right, transparent, #000 24px);
          mask-image: linear-gradient(to right, transparent, #000 24px);
}

/* Buy back some of the overflow before relying on the fade at all. Six
   English links measure 360px of words at this size; the padding is what
   decided whether they fit. At 7px they fit a 390px phone and at 5px a
   360px one, so English never scrolls and "Home" is never half cut. The
   longer languages (Vietnamese, Filipino) still scroll, with the fade. */
@media (max-width: 430px) {
  .main-nav a { padding: 8px 7px; font-size: 0.86rem; }
  .main-nav { gap: 2px; --nav-pad: 7px; }
}
@media (max-width: 380px) {
  .main-nav a { padding: 8px 5px; }
  .main-nav { gap: 0; --nav-pad: 5px; }
}

/* ==========================================================================
   Kanji

   Three columns like the other two lists, but the first one carries the
   character itself at reading size: a kanji at 0.95rem is a smudge, and the
   whole point of the page is seeing the shape.
   ========================================================================== */

.study-row.is-kanji { grid-template-columns: 116px 210px minmax(0, 1fr); }

.kanji-cell { display: flex; flex-direction: column; gap: 4px; }

/* A link now, not a button - it opens the character's own page. It keeps the
   button's shape because the shape is what says "this is tappable". */
.kanji-char {
  display: grid;
  place-items: center;
  width: 62px;
  height: 62px;
  padding: 0;
  background: var(--panel-soft);
  border: 1px solid var(--line);
  border-radius: 14px;
  font-size: 2.1rem;
  line-height: 1;
  color: var(--navy);
  cursor: pointer;
  transition: 0.15s ease;
}
.kanji-char:hover,
.kanji-char:focus-visible {
  background: #fff;
  border-color: var(--blue);
  box-shadow: 0 0 0 3px rgba(45, 110, 180, 0.14);
}

.kanji-strokes {
  font-size: 0.7rem;
  color: var(--muted);
  font-variant-numeric: lining-nums tabular-nums;
}

.kanji-readings { font-size: 0.9rem; color: var(--text); line-height: 1.7; }
.kanji-readings b {
  display: inline-block;
  min-width: 1.5em;
  color: var(--muted);
  font-size: 0.74rem;
}
/* The okurigana half of a kun reading: the part you write in kana, not in
   the character. Dimmed so the boundary is visible at a glance. */
.kanji-okuri { font-style: normal; color: var(--muted); }

.kanji-exs {
  display: block;
  margin-top: 7px;
  font-size: 0.82rem;
  line-height: 1.6;
  color: var(--muted);
}
.kanji-ex { display: block; }
.kanji-ex b { color: var(--navy); font-weight: 700; margin-right: 2px; }
.kanji-ex i { font-style: normal; color: var(--blue); margin-right: 6px; }

/* --------------------------------------------------- opened in the list ---
   The row's own drawer. It is a sibling of the row rather than a child so it
   can span the full width of the list: the row is a three-column grid, and
   anything inside it would be trapped in one of those columns.
   ------------------------------------------------------------------------ */

.study-row.is-kanji.is-open { background: var(--panel-soft); }
.study-row.is-kanji.is-open .kanji-char {
  background: #fff;
  border-color: var(--blue);
  color: var(--blue);
}

.kanji-open {
  border-top: 1px solid var(--line);
  background: linear-gradient(180deg, var(--panel-soft), var(--panel));
  animation: kanji-open 0.2s ease;
}

@keyframes kanji-open {
  from { opacity: 0; transform: translateY(-5px); }
}

.kanji-open-inner {
  position: relative;
  display: grid;
  grid-template-columns: 172px minmax(0, 1fr);
  gap: clamp(14px, 2vw, 26px);
  align-items: start;
  padding: 18px 18px 20px;
}

.kanji-open-draw {
  display: grid;
  place-items: center;
  aspect-ratio: 1;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 16px;
}

.kanji-open-body { min-width: 0; }
.kanji-open-body h3 {
  margin: 0 0 10px;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--muted);
}

/* The steps run in one line and scroll sideways if the character has many:
   a wrapping grid would make the drawer taller than the screen for a
   twenty-stroke N1 kanji. */
.kanji-open-steps {
  display: flex;
  gap: 8px;
  margin: 0 0 14px;
  padding: 0 0 4px;
  list-style: none;
  overflow-x: auto;
}
.kanji-open-steps li {
  position: relative;
  flex: 0 0 auto;
  width: 58px;
  padding: 4px;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 10px;
}
.kanji-open-steps svg { display: block; width: 100%; }
.kanji-open-steps b {
  position: absolute;
  top: 2px;
  left: 5px;
  font-size: 0.6rem;
  color: var(--muted);
  font-variant-numeric: lining-nums tabular-nums;
}

.kanji-open-actions { display: flex; flex-wrap: wrap; gap: 10px; }
.kanji-open-actions .btn { font-size: 0.84rem; padding: 9px 16px; }

.kanji-open-close {
  position: absolute;
  top: 8px;
  right: 10px;
  width: 30px;
  height: 30px;
  padding: 0;
  background: none;
  border: 0;
  border-radius: 8px;
  font-size: 1.3rem;
  line-height: 1;
  color: var(--muted);
  cursor: pointer;
}
.kanji-open-close:hover { background: #fff; color: var(--navy); }

@media (prefers-reduced-motion: reduce) {
  .kanji-open { animation: none; }
}

@media (max-width: 700px) {
  .kanji-open-inner { grid-template-columns: minmax(0, 1fr); padding: 14px; }
  .kanji-open-draw { max-width: 190px; }
  .kanji-open-actions .btn { flex: 1 1 auto; text-align: center; }
}

/* ------------------------------------------------------- stroke order ---
   Its own page rather than a panel under the list. A character being studied
   gets the screen: the animation large enough to follow a brush direction,
   the finished sequence printed underneath the way a textbook does it, and
   the words that use it below that.
   ------------------------------------------------------------------------ */

.kanji-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding-top: clamp(18px, 2vw, 30px);
}

.kanji-steps-nav { display: flex; align-items: center; gap: 10px; }

.kanji-nav {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 7px 14px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: 1.05rem;
  color: var(--navy);
  font-weight: 700;
}
.kanji-nav:hover { border-color: var(--blue); color: var(--blue); }
.kanji-nav span { color: var(--muted); font-size: 0.85rem; }
.kanji-pos {
  font-size: 0.78rem;
  color: var(--muted);
  font-variant-numeric: lining-nums tabular-nums;
}

.kanji-hero {
  display: grid;
  grid-template-columns: minmax(0, 340px) minmax(0, 1fr);
  gap: clamp(20px, 3vw, 44px);
  align-items: center;
  margin-top: 18px;
  padding: var(--card-pad);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 22px;
  box-shadow: 0 10px 24px rgba(17, 45, 78, 0.05);
}

.kanji-draw-box {
  display: grid;
  place-items: center;
  aspect-ratio: 1;
  background: var(--panel-soft);
  border-radius: 18px;
}

.kanji-svg { width: 100%; height: 100%; }

/* The quarter lines of a squared practice sheet, which is what a learner
   copies onto. Dashed and pale: a guide, not part of the character. */
.kanji-guide line {
  stroke: rgba(17, 45, 78, 0.16);
  stroke-width: 1;
  stroke-dasharray: 4 4;
}

.kanji-ink path {
  fill: none;
  stroke: var(--navy);
  stroke-width: 5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

@keyframes kanji-draw { to { stroke-dashoffset: 0; } }

.kanji-facts h1 {
  margin: 0;
  font-size: clamp(3.4rem, 7vw, 5rem);
  line-height: 1;
  color: var(--navy);
  font-family: var(--font-sans);
}
.kanji-facts-en {
  margin: 8px 0 16px;
  font-size: 1.05rem;
  color: var(--text);
}

.kanji-readings-list { margin: 0 0 18px; }
.kanji-readings-list .kr {
  display: grid;
  grid-template-columns: 62px minmax(0, 1fr);
  gap: 10px;
  padding: 8px 0;
  border-top: 1px solid var(--line);
}
.kanji-readings-list dt {
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--muted);
}
.kanji-readings-list dd { margin: 0; color: var(--text); }

/* The okurigana half of a kun reading: the part written in kana, not in the
   character itself. */
.kanji-okuri { font-style: normal; color: var(--muted); }

/* Every stroke again, small and in order - the sequence at a glance once the
   animation has run, the way a textbook prints it. The newest stroke of each
   step is picked out so the eye knows what was added. */
.kanji-steps { margin-top: clamp(22px, 2.6vw, 34px); }
.kanji-steps h2,
.kanji-words h2 {
  margin: 0 0 12px;
  font-size: 0.95rem;
  color: var(--navy);
}
.kanji-steps ol {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(74px, 1fr));
  gap: 10px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.kanji-steps li {
  position: relative;
  padding: 6px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 12px;
}
.kanji-steps svg { display: block; width: 100%; }
.kanji-steps b {
  position: absolute;
  top: 4px;
  left: 7px;
  font-size: 0.66rem;
  color: var(--muted);
  font-variant-numeric: lining-nums tabular-nums;
}
.kanji-ink.is-static path { stroke: rgba(17, 45, 78, 0.28); stroke-width: 5; }
.kanji-ink.is-static path.is-new { stroke: var(--blue); }

.kanji-words { margin-top: clamp(22px, 2.6vw, 34px); }
.kanji-words ul {
  margin: 0;
  padding: 0;
  list-style: none;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 16px;
}
.kanji-words li {
  display: grid;
  grid-template-columns: 150px 150px minmax(0, 1fr);
  gap: 14px;
  align-items: baseline;
  padding: 12px 18px;
  border-top: 1px solid var(--line);
}
.kanji-words li:first-child { border-top: 0; }
.kanji-words b { font-size: 1.15rem; color: var(--navy); }
.kanji-words i { font-style: normal; color: var(--muted); font-size: 0.9rem; }

/* Somebody who has turned animation off still needs the finished character,
   so the strokes are simply drawn complete. */
@media (prefers-reduced-motion: reduce) {
  .kanji-ink path { animation: none !important; stroke-dashoffset: 0 !important; }
}

@media (max-width: 860px) {
  .kanji-hero { grid-template-columns: minmax(0, 1fr); }
  .kanji-draw-box { max-width: 320px; margin-inline: auto; }
  .kanji-facts { text-align: center; }
  .kanji-readings-list .kr { text-align: left; }
  .kanji-words li { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .kanji-words li span { grid-column: 1 / -1; }
}

@media (max-width: 700px) {
  .study-row.is-kanji,
  .study-row.is-head.is-kanji {
    grid-template-columns: 78px minmax(0, 1fr);
  }
  .study-row.is-kanji .kanji-readings { grid-column: 2; }
  .study-row.is-kanji .study-en { grid-column: 1 / -1; }
  .study-row.is-head.is-kanji span:nth-child(2),
  .study-row.is-head.is-kanji span:nth-child(3) { display: none; }
  .kanji-char { width: 54px; height: 54px; font-size: 1.8rem; }
}

/* A word written with a wave dash is an affix, not a standalone word. Say so,
   quietly, because its English gloss describes the root it attaches to. */
.study-affix {
  display: inline-block;
  margin-left: 7px;
  padding: 2px 7px;
  border-radius: 999px;
  background: var(--panel-soft);
  border: 1px solid var(--line);
  color: var(--muted);
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  vertical-align: middle;
}

/* ======================================================= buttons on the hero

   .btn-primary is a navy-to-blue gradient, which is exactly what the hero
   behind it is painted with - so the site's most important button was the
   one thing on the page you could not see. On a dark ground the primary
   action inverts: solid white, navy text, the same shape and the same
   motion. Everything else about the tier is unchanged. */
.hero-section .btn-primary {
  background: #fff;
  color: var(--navy);
  box-shadow: 0 8px 22px -6px rgba(0, 0, 0, 0.45),
              inset 0 -1px 0 rgba(17, 45, 78, 0.08);
}
.hero-section .btn-primary:hover {
  background: #fff;
  color: var(--navy-2, #0d2440);
  box-shadow: 0 14px 30px -8px rgba(0, 0, 0, 0.5),
              inset 0 -1px 0 rgba(17, 45, 78, 0.1);
}
.hero-section .btn-primary:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 3px;
}

/* ================================================== scores: where they live

   The controls that make a browser-only score portable: save it, load it,
   clear it. Set apart at the foot of the statistics page because they act on
   your data rather than reporting it. */
.stats-data {
  margin-top: clamp(24px, 3vw, 40px);
  padding: clamp(18px, 2vw, 26px);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 16px;
}
.stats-data h2 {
  margin: 0 0 8px;
  font-size: 1rem;
  color: var(--navy);
}
.stats-data > p {
  margin: 0;
  max-width: 74ch;
  font-size: 0.85rem;
  line-height: 1.7;
  color: var(--muted);
}
.stats-data-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 16px;
}
.stats-data-actions .btn { font-size: 0.84rem; }

/* The file picker is the label; the input itself is never seen. */
.stats-import { position: relative; overflow: hidden; cursor: pointer; }
.stats-import input {
  position: absolute;
  inset: 0;
  opacity: 0;
  cursor: pointer;
  font-size: 0;
}

/* 0.4 alpha put this edge at 1.76:1 - the same invisible-outline bug. */
.btn-ghost.is-danger { color: var(--danger); border-color: var(--danger); }
.btn-ghost.is-danger:hover {
  color: #fff;
  background: var(--danger);
  border-color: var(--danger);
}

.stats-data-msg {
  margin: 12px 0 0;
  min-height: 1.2em;
  font-size: 0.84rem;
  font-weight: 600;
  color: var(--success);
}

/* A 360px phone (Galaxy S20 and most Androids) has 324px of header. The
   select sizes itself to its widest option - "Português (Brasil)", 140px -
   and with the brand mark and name that is 336px, so the picker sat on top
   of the name. Capped, the closed select shows its own value ("English",
   "日本語") in full and the brand keeps its 150px. */
@media (max-width: 400px) {
  .language-select { padding: 8px 10px; }
  .language-select select { width: 100%; max-width: 96px; text-overflow: ellipsis; }
}

/* ==========================================================================
   Quiz and mistake notebook. One card at a time, four answers, a line of
   feedback. The answer buttons follow the exam player's .choice - number in
   a ring, text beside it - but live here because exam.css is not loaded on
   these pages.
   ========================================================================== */
/* The bar's 18px below the pills is padding rather than margin, so that the
   study page can paint it while it is pinned; here there is nothing to paint
   and a 22px margin of its own, so it takes the padding back off. */
.quiz-controls { margin-bottom: 22px; padding-bottom: 0; }
.study-quiz { flex: none; white-space: nowrap; }
@media (max-width: 820px) { .study-find { max-width: none; margin-left: 0; width: 100%; } .study-find .study-search { max-width: none; } }

/* The card is drawn after the list has been fetched; until then the space
   is held, so the footer does not move when it appears (a shift of 0.08
   measured live without this). */
.quiz-root { max-width: 720px; min-height: 240px; }
/* The review card carries more than the quiz's: a breakdown line, the count,
   the sentence about the intervals, the button and the way to empty the
   queue. Reserve what it actually needs, or the footer sits inside the first
   screen and drops when the card arrives. */
.review-page .quiz-root { min-height: 340px; }
.quiz-card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 20px;
  padding: 26px 28px 24px;
  box-shadow: 0 10px 30px rgba(17, 45, 78, 0.06);
}
.quiz-intro, .quiz-end { text-align: left; }
.quiz-kicker {
  margin: 0 0 12px;
  font-size: 0.74rem;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}
.quiz-kicker .quiz-right { color: var(--success); letter-spacing: 0; }
.quiz-kicker .quiz-from { text-transform: none; letter-spacing: 0; font-weight: 600; }
.quiz-ask { margin: 0 0 10px; font-family: var(--font-sans); font-size: 1.02rem; font-weight: 600; color: var(--muted); }
.quiz-shown {
  margin: 0 0 20px;
  font-size: 1.6rem;
  font-weight: 700;
  line-height: 1.35;
  color: var(--navy);
}
.quiz-shown.is-ja { font-size: 2.1rem; }
/* The reading sits under its word, smaller and dimmed, the way the study
   lists set it - not run on to the end of the kanji at the same size, where
   台所 and だいどころ read as one seven-character word. Both tags, so a
   caller reaching for either gets the same result. */
.quiz-shown small,
.quiz-shown em {
  display: block;
  margin-top: 4px;
  font-size: 0.95rem;
  font-weight: 500;
  font-style: normal;
  line-height: 1.4;
  color: var(--muted);
}
.quiz-note { margin: 0 0 16px; color: var(--muted); font-size: 0.95rem; }
.quiz-score { margin: 0 0 10px; font-family: var(--font-display); font-size: 2rem; color: var(--navy); }
.quiz-list-link {
  display: inline-block; margin: 14px 0 0 2px;
  background: none; border: 0; padding: 0; cursor: pointer;
  font: inherit; font-size: 0.92rem; font-weight: 600; color: var(--blue);
}
.quiz-list-link.is-danger { color: var(--danger, #c84a52); margin-left: 18px; }
.quiz-intro .btn { margin-top: 6px; }

.quiz-options { display: grid; gap: 10px; }
@media (min-width: 640px) { .quiz-options { grid-template-columns: 1fr 1fr; } }
.quiz-choice {
  display: flex; align-items: center; gap: 12px;
  width: 100%; padding: 12px 14px;
  text-align: left; font: inherit; font-size: 1.02rem; color: var(--text);
  background: var(--panel); border: 1.5px solid var(--line); border-radius: 14px;
  cursor: pointer; transition: border-color .15s ease, background .15s ease, transform .1s ease;
}
.quiz-choice:hover:not(:disabled) { border-color: var(--blue); background: var(--panel-soft); }
.quiz-choice:active:not(:disabled) { transform: scale(0.99); }
.quiz-choice:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }
.quiz-choice:disabled { cursor: default; }
.quiz-num {
  flex: none; width: 28px; height: 28px; border-radius: 50%;
  display: grid; place-items: center;
  font-size: 0.82rem; font-weight: 800; color: var(--muted);
  background: var(--panel-soft); border: 1.5px solid var(--line);
}
.quiz-choice.is-correct { border-color: var(--success); background: rgba(47, 125, 87, 0.1); }
.quiz-choice.is-correct .quiz-num { background: var(--success); border-color: var(--success); color: #fff; }
.quiz-choice.is-wrong { border-color: var(--danger, #c84a52); background: rgba(200, 74, 82, 0.09); }
.quiz-choice.is-wrong .quiz-num { background: var(--danger, #c84a52); border-color: var(--danger, #c84a52); color: #fff; }

.quiz-feedback:empty { display: none; }
.quiz-feedback {
  margin-top: 18px; padding: 14px 16px; border-radius: 14px;
  font-size: 0.95rem; line-height: 1.55;
}
.quiz-feedback.is-ok { background: rgba(47, 125, 87, 0.08); border: 1px solid rgba(47, 125, 87, 0.25); }
.quiz-feedback.is-bad { background: rgba(200, 74, 82, 0.07); border: 1px solid rgba(200, 74, 82, 0.25); }
.quiz-feedback strong { display: block; margin-bottom: 4px; color: var(--navy); }
.quiz-feedback p { margin: 4px 0 0; }
.quiz-feedback .quiz-ex { color: var(--muted); font-size: 0.9rem; }
.quiz-feedback .btn { margin-top: 12px; }

.quiz-only {
  display: flex; align-items: flex-start; gap: 10px; margin: 0 0 16px;
  font-size: 0.92rem; line-height: 1.5; color: var(--text); text-align: left; cursor: pointer;
}
.quiz-only input { margin: 4px 0 0; width: 18px; height: 18px; flex: none; accent-color: var(--navy); }
.quiz-mark-missed { margin-top: 12px; }
.quiz-missed { margin: 18px 0 6px; }
.quiz-missed h3 { margin: 0 0 8px; font-size: 0.95rem; color: var(--muted); font-weight: 700; }
.quiz-missed ul { margin: 0; padding: 0; list-style: none; display: grid; gap: 6px; }
.quiz-missed li { padding: 8px 12px; background: var(--panel-soft); border-radius: 10px; font-size: 0.95rem; }
.quiz-missed li span { color: var(--muted); }
.quiz-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 18px; }
.quiz-intro-actions { align-items: center; gap: 10px 22px; margin-top: 6px; }
.quiz-intro-actions .btn { margin-top: 0; }
.quiz-intro-actions .quiz-list-link { margin: 0; }

.review-instruction { margin: 0 0 10px; font-size: 0.88rem; color: var(--muted); }
.review-passage { margin: 0 0 14px; }
.review-passage summary { cursor: pointer; font-weight: 600; color: var(--blue); font-size: 0.92rem; }
.review-passage > div { margin-top: 10px; padding: 14px 16px; background: var(--panel-soft); border-radius: 12px; line-height: 1.9; }
.review-prompt { margin: 0 0 18px; font-size: 1.12rem; line-height: 1.85; color: var(--navy); }
.review-prompt img { max-width: 100%; height: auto; }

/* Stats page: the notebook card. */
.stats-mistakes {
  display: flex; align-items: center; justify-content: space-between; gap: 18px;
  margin: 0 0 26px; padding: 20px 24px;
  background: var(--panel); border: 1px solid var(--line); border-left: 4px solid var(--gold); border-radius: 16px;
}
.stats-mistakes h2 { margin: 0 0 4px; font-size: 1.1rem; }
.stats-mistakes p { margin: 0; color: var(--muted); font-size: 0.92rem; }
/* One line reserved: the count arrives from the script, and an empty
   paragraph that grows to a line moved the whole card (a shift of 0.48 on
   a phone, where the button stacks below and started visible too). */
.stats-mistakes-count { margin-top: 6px !important; min-height: 1.45em; font-weight: 700; color: var(--navy) !important; }
.stats-mistakes .btn { flex: none; }
@media (max-width: 640px) { .stats-mistakes { flex-direction: column; align-items: stretch; } }

/* Exam setup: a listening section with no recording says so on its card. */
.part-card.is-silent .part-sub em { color: var(--danger, #c84a52); font-style: normal; font-weight: 700; }

/* ==========================================================================
   One page per paper - /exam/n3-2019-07.html and its 1,091 siblings.

   These are landing pages, not the papers: what a sitting is, how it is
   made up, what can be heard, and the way into it. See paper_body in
   tools/build_static.py for why the questions are not on them.
   ========================================================================== */
.paper-page { max-width: 760px; }
.paper-kicker {
  margin: 0 0 6px;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--muted);
}
.paper-kicker a { color: var(--navy); text-decoration: none; }
.paper-kicker a:hover { text-decoration: underline; }
.paper-kicker span { margin: 0 6px; }
.paper-page h1 { margin: 0 0 6px; }
.paper-lead {
  margin: 0 0 20px;
  color: var(--muted);
  font-variant-numeric: lining-nums tabular-nums;
}
.paper-actions { display: flex; flex-wrap: wrap; gap: 10px; margin: 0 0 26px; }

.paper-parts {
  width: 100%;
  margin: 0 0 20px;
  border-collapse: collapse;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 14px;
  overflow: hidden;
}
.paper-parts th,
.paper-parts td {
  padding: 11px 16px;
  text-align: left;
  border-top: 1px solid var(--line);
}
.paper-parts thead th {
  border-top: 0;
  background: var(--panel-soft);
  /* Upper-cased by the stylesheet rather than in the string, so the twelve
     translations stay the words their own languages use - and so the ones
     written in scripts that have no case are simply left alone. */
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: 0.7rem;
  color: var(--muted);
}
.paper-parts td:last-child,
.paper-parts th:last-child {
  text-align: right;
  font-variant-numeric: lining-nums tabular-nums;
}
.paper-parts tfoot td { font-weight: 700; color: var(--navy); }

.paper-notes { margin: 0 0 26px; padding-left: 20px; color: var(--muted); }
.paper-notes li { margin: 4px 0; }

.paper-near { display: flex; flex-wrap: wrap; gap: 8px 18px; font-size: 0.9rem; }
.paper-near a { color: var(--navy); text-decoration: none; font-weight: 600; }
.paper-near a:hover { text-decoration: underline; }

/* The plain index of every paper, under the library table. See paper_index
   in tools/build_static.py: the table above it is drawn by a script and so
   is invisible to a crawler, and these links are the route into the 91
   paper pages. Quiet, because it is a directory rather than the feature. */
.paper-index {
  margin-top: 44px;
  padding-top: 26px;
  border-top: 1px solid var(--line);
}
.paper-index-level { margin-bottom: 18px; }
.paper-index-level h3 {
  margin: 0 0 8px;
  font-family: var(--font-sans);
  font-size: 0.72rem;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}
.paper-index-level div { display: flex; flex-wrap: wrap; gap: 6px 14px; }
.paper-index-level a {
  color: var(--navy);
  font-size: 0.88rem;
  text-decoration: none;
  white-space: nowrap;
}
.paper-index-level a:hover { text-decoration: underline; }

/* The facts table on the levels page. site.js replaces it with the five
   interactive cards on load; this is what is in the markup until then, and
   what a reader without scripts gets. See levels_table in build_static.py. */
.levels-facts {
  width: 100%;
  border-collapse: collapse;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 14px;
  overflow: hidden;
}
.levels-facts th,
.levels-facts td {
  padding: 11px 14px;
  border-top: 1px solid var(--line);
  text-align: right;
  font-variant-numeric: lining-nums tabular-nums;
}
.levels-facts thead th {
  border-top: 0;
  background: var(--panel-soft);
  text-transform: uppercase;
  letter-spacing: 0.07em;
  font-size: 0.68rem;
  color: var(--muted);
}
.levels-facts th:first-child,
.levels-facts thead th:first-child { text-align: left; }
.levels-facts tbody th a { color: var(--navy); font-weight: 800; }

/* The two blocks that make a paper page its own rather than a template with
   the numbers changed: which 問題 the sitting holds and how many questions
   are in each, and the vocabulary it draws on. See paper_facts in
   tools/build_static.py. */
.paper-h2 {
  margin: 26px 0 10px;
  font-family: var(--font-sans);
  font-size: 0.72rem;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}
.paper-mondai { margin: 0; padding: 0; list-style: none; }
.paper-mondai li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px 14px;
  padding: 9px 0;
  border-top: 1px solid var(--line);
}
.paper-mondai li:first-child { border-top: 0; }
.paper-mondai em {
  flex: 0 0 auto;
  min-width: 150px;
  font-style: normal;
  font-weight: 700;
  color: var(--navy);
}
.paper-mondai div { display: flex; flex-wrap: wrap; gap: 6px 12px; }
.paper-mondai span {
  font-size: 0.86rem;
  color: var(--muted);
  font-variant-numeric: lining-nums tabular-nums;
}
.paper-mondai span b { font-weight: 600; color: var(--text); }

.paper-vocab { margin: 0 0 8px; font-variant-numeric: lining-nums tabular-nums; }
.paper-vocab strong { font-size: 1.15rem; color: var(--navy); }
.paper-lvs { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 8px; }
.paper-lv {
  padding: 4px 10px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--panel);
  font-size: 0.8rem;
  color: var(--muted);
  font-variant-numeric: lining-nums tabular-nums;
}
.paper-lv b { font-weight: 800; color: var(--navy); }
