@import url('https://fonts.googleapis.com/css2?family=Nunito:wght@700;800;900&display=swap');

:root {
  --cream: #FBF5E9;
  --ink: #4A3F35;
  --vocab: #DCEBF7;
  --vocab-accent: #5B8DBE;
  --story: #FCEFE1;
  --story-accent: #E08A3C;
  --quiz: #FBF3D0;
  --quiz-accent: #C9A227;
  --grammar: #E3F0E3;
  --grammar-accent: #5C9A63;
  --review: #F6E6EE;
  --review-accent: #C0779C;
  /* Select-to-ask. Amber, not the old lavender: white on #8B7FC7 measured
     3.52:1 — the button's own label failed contrast. Ink on amber is 6.55:1. */
  --ai: #FFF6DC;
  --ai-accent: #FFC72C;
  --ai-ink: #7A6106;
  --ink-soft: #55483d;
  --ink-faint: #8a7c68;
  --divider: #E6DCC8;
  --chip-bg: #F6F1E4;
  /* Text-only variants of the section accents. The bright accents stay on borders,
     icons and fills where 3:1 is the bar; anything the learner has to *read* uses
     these, which clear 4.5:1 on that section's own background. The originals were
     2.2-2.9:1 — the vocab word, of all things, was the least legible text on the page. */
  --vocab-ink: #2F5F8C;
  --story-ink: #A55712;
  --quiz-ink: #7A6106;
  --grammar-ink: #3E7645;
  --review-ink: #9B4E77;
  --placeholder-fg: #b9aee0;
  --placeholder-bg-1: #f3effa;
  --placeholder-bg-2: #eae3f6;
  --radius: 18px;

  /* Type scale. One fluid root (16.5px on a phone -> 19px on a desktop) and every
     size below is a rem multiple of it, so the whole page grows together instead of
     staying frozen at phone sizes on a 1280px screen — which is what it did before.
     --fs-note is the floor for anything a learner reads; captions are for chrome
     (footer, chips, slot names), never for teaching content. */
  font-size: clamp(17px, 15.9px + 0.35vw, 19px);
  --fs-caption: 0.8rem;
  --fs-note: 0.95rem;
  --fs-body: 1.05rem;
  --fs-lead: 1.15rem;
  --fs-word: 1.65rem;
  --fs-h2: 1.35rem;
  /* CJK needs more leading than Latin at the same size */
  --lh-cjk: 1.85;
}

* { box-sizing: border-box; }

/* The `hidden` attribute is how every toggle in client.js shows/hides things
   (modal, AI popup, quiz slides). A class that declares its own `display`
   outranks the UA stylesheet's `[hidden] { display: none }`, so `.journey-modal
   { display: flex }` left the modal permanently on screen with its close button
   silently working. Assert it once here rather than remembering to write
   `display: none` into every such component. */
[hidden] { display: none !important; }
.ico { width: 1em; height: 1em; vertical-align: -0.15em; display: inline-block; }

body {
  margin: 0;
  background: var(--cream);
  color: var(--ink);
  font-family: -apple-system, "Segoe UI", "PingFang TC", "Noto Sans TC", sans-serif;
  line-height: 1.7;
}

/* ---- Journey header: horizontal day strip + status modal ---- */
.journey {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 8px 10px;
  background: #fff;
  border-bottom: 3px dashed var(--divider);
}

.journey-arrow {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: none;
  font-size: 1.4rem;
  line-height: 1;
  color: var(--ink);
  padding: 4px 6px;
  cursor: pointer;
}

.journey-track {
  flex: 1;
  display: flex;
  gap: 8px;
  overflow-x: auto;
  scroll-behavior: smooth;
  scrollbar-width: none;
  padding: 2px;
}
.journey-track::-webkit-scrollbar { display: none; }

.day-chip {
  flex: none;
  width: 50px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 6px 2px;
  border-radius: 14px;
  border: 2px solid transparent;
  background: var(--chip-bg);
  font-family: 'Nunito', -apple-system, sans-serif;
  font-weight: 800;
  color: var(--ink);
  cursor: pointer;
}
.day-chip .chip-icon { font-size: 1.05rem; line-height: 1; }
/* The letter is the slot's identity now (A~Z, one per pack), so it carries the
   weight the day number used to — not a caption under the icon. */
.day-chip .chip-label { font-size: 0.9rem; font-weight: 900; letter-spacing: 0.02em; }

.day-chip[data-status="today"] {
  background: #fff;
  border-color: var(--vocab-accent);
  box-shadow: 0 0 0 2px rgba(91, 141, 190, 0.2);
}
.day-chip[data-done="true"] { background: var(--vocab); border-color: var(--vocab-accent); }
.day-chip[data-status="locked"] { opacity: 0.4; cursor: default; }

.journey-jump { flex: none; display: flex; align-items: center; gap: 4px; }
.journey-jump input {
  width: 62px;
  text-align: center;
  text-transform: uppercase;
  border: 1px solid var(--divider);
  border-radius: 999px;
  padding: 5px 8px;
  font-size: 0.78rem;
}
.journey-jump button {
  /* A text label, not another chevron: sitting next to the track's scroll arrow,
     a second round arrow button read as a duplicate control. */
  flex: none;
  border: none;
  background: var(--ink);
  color: #fff;
  border-radius: 999px;
  padding: 6px 14px;
  min-height: 32px;
  font-family: inherit;
  font-size: var(--fs-caption);
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
}

.journey-modal {
  position: fixed;
  inset: 0;
  background: rgba(74, 63, 53, 0.4);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 90;
  padding: 20px;
}
.journey-modal-card {
  background: #fff;
  border-radius: 22px;
  padding: 22px 20px;
  width: 100%;
  max-width: 320px;
  text-align: center;
  position: relative;
}
.journey-modal-close {
  position: absolute;
  top: 10px;
  right: 12px;
  display: inline-flex;
  border: none;
  background: none;
  font-size: 0.9rem;
  color: var(--ink-faint);
  cursor: pointer;
  padding: 4px;
}
.journey-modal-day {
  font-family: 'Nunito', -apple-system, sans-serif;
  font-weight: 800;
  color: var(--vocab-accent);
  font-size: 1.05rem;
}
.journey-modal-title { margin: 6px 0 16px; font-size: 0.95rem; color: var(--ink-soft); }
.journey-modal-stats { display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px 10px; margin-bottom: 16px; }
.jm-stat { display: flex; flex-direction: column; align-items: center; }
.jm-stat-num { font-family: 'Nunito', -apple-system, sans-serif; font-weight: 900; font-size: 1.5rem; color: var(--ink); }
.jm-stat-label { font-size: 0.72rem; color: var(--ink-faint); }

.journey-modal-heatmap {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  justify-content: center;
  margin-bottom: 18px;
  padding: 10px;
  background: var(--cream);
  border-radius: 12px;
}
.jm-day-cell { width: 18px; height: 18px; border-radius: 5px; background: var(--chip-bg); }
.jm-day-cell.done { background: var(--vocab-accent); }
.jm-day-cell.locked { background: transparent; border: 1.5px dashed var(--divider); }
.journey-modal-cta {
  border: none;
  background: var(--vocab-accent);
  color: #fff;
  font-weight: 700;
  font-size: 0.9rem;
  padding: 10px 22px;
  border-radius: 999px;
  cursor: pointer;
}

.mark-complete {
  display: flex;
  align-items: center;
  gap: 6px;
  width: fit-content;
  margin: 4px auto 0;
  border: 1.5px solid var(--vocab-accent);
  background: #fff;
  color: var(--vocab-accent);
  font-weight: 700;
  font-size: 0.85rem;
  padding: 8px 20px;
  border-radius: 999px;
  cursor: pointer;
}
.mark-complete.done { background: var(--vocab-accent); color: #fff; }

.pack-header {
  /* Grid, not space-between: with flex the title drifts off centre as soon as the
     badge and the note differ in width, which they always do. */
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 8px;
  padding: 14px 18px;
  background: #fff;
  border-bottom: 3px dashed var(--divider);
}

.pack-header .day-badge {
  background: var(--vocab-accent);
  color: #fff;
  font-weight: 800;
  font-family: 'Nunito', -apple-system, "PingFang TC", "Noto Sans TC", sans-serif;
  padding: 4px 14px;
  border-radius: 999px;
  font-size: 0.9rem;
  white-space: nowrap;
}

.pack-header .pack-title {
  font-weight: 800;
  font-family: 'Nunito', -apple-system, "PingFang TC", "Noto Sans TC", sans-serif;
  font-size: 1.2rem;
}

.pack-header .pack-title { text-align: center; }
.pack-header .day-badge { justify-self: start; }
.pack-header .cycle-note {
  font-size: var(--fs-caption);
  color: var(--ink-soft);
  justify-self: end;
  text-align: right;
}
@media (max-width: 560px) {
  /* Narrow: badge and title share the top row, the note drops beneath rather than
     being squeezed to two words per line. */
  .pack-header { grid-template-columns: auto 1fr; row-gap: 2px; }
  .pack-header .cycle-note { grid-column: 1 / -1; justify-self: start; text-align: left; }
}


.collapsible-toggle {
  border: none;
  background: none;
  font: inherit;
  font-weight: 700;
  color: inherit;
  cursor: pointer;
  padding: 0;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.collapsible-body { margin-top: 8px; }
.collapsible-body.is-collapsed { display: none; }

/* section tabs — progressive enhancement, works fine with JS off (all sections just stack) */
.section-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
}
/* `margin-left: auto` only pushes the PDF button to the far right when there's
   room left on the line; once the row wraps it lands on its own line instead of
   being scrolled off the edge. The row used to be a nowrap scroller, which hid it. */

.section-nav button {
  border: none;
  background: #fff;
  padding: 8px 14px;
  border-radius: 999px;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
}

.section-nav button.active { background: var(--ink); color: #fff; }

/* Objectives notice — read once, then out of the way.
   Scoped under .section-nav on purpose: `.section-nav button` (0,1,1) outranks a
   bare `.notice-btn` (0,1,0), so the tab styling silently won and this came out
   as a plain white pill. */
.section-nav .notice-btn {
  flex: none;
  width: 38px;
  min-width: 38px;
  height: 38px;
  padding: 0;
  border-radius: 999px;
  border: 1.5px solid var(--quiz-ink);
  background: var(--quiz);
  color: var(--quiz-ink);
  /* the glyph matches the tab labels beside it — at 1.2em in a 38px circle the
     icon's own outline read as a heavy dark blob */
  font-size: var(--fs-caption);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.section-nav .notice-btn .ico { width: 1em; height: 1em; flex: none; }
.section-nav .notice-btn:hover { background: #f7e9b8; }
.section-nav a.download-pdf { margin-left: auto; }

.notice-modal {
  position: fixed;
  inset: 0;
  background: rgba(74, 63, 53, 0.4);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 95;
  padding: 20px;
}
.notice-card {
  position: relative;
  background: #fff;
  border-radius: 22px;
  padding: 22px 24px;
  max-width: 520px;
  width: 100%;
  max-height: 80vh;
  overflow-y: auto;
}
.notice-title {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 800;
  font-size: var(--fs-h2);
  color: var(--ink);
  margin: 0 32px 12px 0;
}
.notice-list { margin: 0; padding-left: 22px; }
.notice-list li {
  font-size: var(--fs-body);
  line-height: var(--lh-cjk);
  color: var(--ink-soft);
  margin-bottom: 8px;
}
.notice-close {
  position: absolute;
  top: 14px;
  right: 14px;
  width: 34px;
  height: 34px;
  border-radius: 999px;
  border: none;
  background: var(--chip-bg);
  color: var(--ink);
  font-size: 1rem;
  cursor: pointer;
}

main { padding: 10px 14px 40px; max-width: 680px; margin: 0 auto; }

/* Each section is its own rounded, color-bordered frame (matches the original
   workbook page's boxed sections), with a numbered circle + icon + big title */
.section {
  margin-bottom: 22px;
  background: #fff;
  border-radius: 22px;
  border: 2px solid var(--section-accent, var(--vocab-accent));
  padding: 16px 16px 20px;
}
#vocab { --section-accent: var(--vocab-accent); }
#story { --section-accent: var(--story-accent); }
#quiz { --section-accent: var(--quiz-accent); }
#grammar { --section-accent: var(--grammar-accent); }
#review { --section-accent: var(--review-accent); }

.section h2 {
  font-size: var(--fs-h2);
  font-weight: 800;
  margin: 0 0 14px;
  display: flex;
  align-items: center;
  gap: 8px;
}

.section-num {
  flex: none;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--section-accent);
  color: #fff;
  font-size: 0.95rem;
  font-weight: 800;
  font-family: 'Nunito', -apple-system, sans-serif;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* vocab cards — each card stacks: word → IPA → pos+meaning → illustration →
   collocations, centered. The column count is not a set of breakpoints: `auto-fill`
   with a 290px floor lets the card decide. A phone gets one column, a laptop three,
   a wide screen four — and the card never drops below the width its own text needs.
   The old `flex: 1 1 calc(50% - 5px)` forced two columns at every size, which on a
   390px phone left ~150px of text and broke "abandon hope 放棄希|望" mid-word. */
.vocab-grid {
  display: grid;
  grid-template-columns: 1fr;
  /* Every card the same size, not just the ones sharing a row: `1fr` on
     grid-auto-rows makes all rows take the tallest row's height, so a card with
     nine fields and one with four still line up as a single tidy deck. */
  grid-auto-rows: 1fr;
  gap: 12px;
}
@media (min-width: 640px) { .vocab-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .vocab-grid { grid-template-columns: repeat(3, 1fr); } }

.vocab-card {
  background: var(--vocab);
  border: 1.5px solid var(--vocab-accent);
  border-radius: var(--radius);
  padding: 16px 14px 18px;
  box-sizing: border-box;
  text-align: center;
  display: flex;
  flex-direction: column;
}

.illus {
  width: 100%;
  aspect-ratio: 4 / 3;
  margin-left: auto;
  margin-right: auto;
  border-radius: 12px;
  overflow: hidden;
  background: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 8px;
}
.illus-img { width: 100%; height: 100%; object-fit: cover; }
.illus-placeholder {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  gap: 4px;
  align-items: center;
  justify-content: center;
  font-size: 1.6rem;
  color: var(--placeholder-fg);
  background: repeating-linear-gradient(45deg, var(--placeholder-bg-1), var(--placeholder-bg-1) 10px, var(--placeholder-bg-2) 10px, var(--placeholder-bg-2) 20px);
}
/* The awaited filename, shown inside the empty slot. Small and muted: it's a build
   note for whoever drops the picture in, not content for the learner. */
.illus-slot-name {
  font-size: 0.62rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  line-height: 1.2;
  max-width: 92%;
  overflow-wrap: anywhere;
  text-align: center;
  opacity: 0.85;
}

/* Cap the picture so the card stays mostly words. At one column on a phone a
   full-width 4:3 slot was 290px tall — half the card — and pushed the
   collocations and example a screenful apart. */
.vocab-card .illus { max-width: 230px; }

.word-row { display: flex; align-items: center; justify-content: center; gap: 6px; flex-wrap: wrap; }
.vocab-card .word { font-size: var(--fs-word); font-weight: 800; line-height: 1.25; font-family: 'Nunito', -apple-system, "PingFang TC", "Noto Sans TC", sans-serif; color: var(--vocab-ink); }

.speak-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: rgba(0, 0, 0, 0.07);
  border-radius: 999px;
  width: 32px;
  height: 32px;
  min-width: 32px;
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
}
.speak-btn:active { background: rgba(0, 0, 0, 0.18); }
.story-block .en .speak-btn { margin-right: 6px; vertical-align: middle; }
.vocab-card .meta { font-size: var(--fs-note); color: var(--ink-soft); margin-top: 2px; }
.vocab-card .pos-meaning { font-size: var(--fs-lead); font-weight: 600; margin: 4px 0 10px; }
/* Below the picture the card carries four different kinds of thing, and they
   were all one colour at one weight — so the card read as a wall. The order the
   learner should feel is: the collocation, then the example, then the note.
   English is the material; the Chinese beside it is a gloss. */
.vocab-card .collo { font-size: var(--fs-body); text-align: left; }
.collo-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 8px;
  line-height: 1.5;
  margin-bottom: 5px;
}
.collo-en { color: var(--ink); font-weight: 600; }
.collo-zh { color: var(--ink-soft); font-size: 0.9em; }

.vocab-card .example {
  font-size: var(--fs-body);
  text-align: left;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px dashed rgba(0, 0, 0, 0.12);
}
.example-en { display: block; color: var(--ink); line-height: 1.55; }
.vocab-card .example-zh { display: block; color: var(--ink-soft); font-size: 0.9em; line-height: var(--lh-cjk); }
/* Notes sit under the material, not beside it: smaller, softer, and led by a
   label so you can skip them at a glance and come back when you want them. */
.vocab-card .usage-note,
.vocab-card .related-words {
  font-size: var(--fs-note);
  color: var(--ink-soft);
  text-align: left;
  margin-top: 8px;
  line-height: 1.65;
}
.note-tag {
  display: inline-block;
  margin-right: 6px;
  color: var(--vocab-ink);
  font-weight: 700;
  font-size: 0.88em;
  letter-spacing: 0.02em;
}
.rw-en { color: var(--ink); font-weight: 600; }
/* --ink-faint is 3.34:1 on the card — chrome only, never text the learner reads */
.rw-pos { color: var(--ink-soft); }
.comparison { margin-top: 10px; padding-top: 10px; border-top: 1px dashed rgba(0, 0, 0, 0.1); text-align: left; font-size: var(--fs-note); line-height: var(--lh-cjk); }
.comparison-row { margin-bottom: 4px; }
.comparison-tag {
  display: inline-block;
  background: var(--vocab-accent);
  color: #fff;
  border-radius: 999px;
  padding: 1px 8px;
  font-size: 0.7rem;
  margin-right: 6px;
}

.extra-vocab { margin-top: 14px; padding: 12px 14px; background: #fff; border-radius: 12px; font-size: var(--fs-note); line-height: var(--lh-cjk); }
.extra-vocab-label { font-weight: 700; color: var(--vocab-ink); margin-right: 8px; }
.extra-vocab-item { margin-right: 12px; color: var(--ink-soft); }

/* Desktop: original design was a 4-column workbook grid — echo that once there's room */
@media (min-width: 720px) {
  .vocab-card { flex: 1 1 calc(25% - 8px); }
}

.story-block { background: var(--story); border: 1.5px solid var(--story-accent); border-radius: var(--radius); padding: 16px; }
.story-title { font-weight: 800; font-size: var(--fs-h2); text-align: center; margin-bottom: 14px; color: var(--story-ink); }
.story-hero-illus { max-width: 240px; margin: 0 auto 12px; aspect-ratio: 4 / 3; }
/* Prose gets a measure. At 1240px the story would otherwise run ~110 characters
     per line — the eye loses the line it came from on the return sweep. */
.story-block .en { font-size: var(--fs-lead); line-height: 1.75; margin: 0 auto 14px; max-width: 62ch; }
.story-block .zh { font-size: var(--fs-body); line-height: var(--lh-cjk); color: var(--ink-soft); margin: 0 auto; max-width: 40em; }
/* Target vocabulary in the passage. White-on-orange looked emphatic but measured
   2.67:1 — the most important words on the page were the hardest to read. A tint
   of the same orange with ink text reads as highlighter and clears 7.7:1. */
.story-block mark {
  background: #F9DCB5;
  color: var(--ink);
  padding: 1px 5px;
  border-radius: 4px;
  box-shadow: inset 0 -2px 0 var(--story-accent);
}

.quiz-item { background: var(--quiz); border: 1.5px solid var(--quiz-accent); border-radius: var(--radius); padding: 14px 16px; margin-bottom: 10px; }
.quiz-item .q { font-size: var(--fs-lead); font-weight: 700; line-height: 1.65; margin-bottom: 12px; }
.quiz-item .options { display: flex; flex-wrap: wrap; gap: 10px; font-size: var(--fs-body); }
.quiz-item .options span { background: #fff; border-radius: 10px; padding: 4px 10px; }

/* card-style quiz: progress dots, tappable options, inline feedback, completion screen */
.quiz-progress { display: flex; justify-content: center; gap: 6px; margin-bottom: 14px; }
.quiz-dot { width: 12px; height: 12px; border-radius: 50%; border: none; background: var(--chip-bg); padding: 0; cursor: pointer; }
.quiz-dot.active { background: var(--quiz-accent); }

.quiz-option {
  background: #fff;
  border: 1.5px solid transparent;
  border-radius: 10px;
  /* 44px minimum tap target — an option that's easy to read is no use if it's
     hard to hit. Same floor applies to the speak buttons. */
  padding: 11px 16px;
  min-height: 44px;
  font-size: var(--fs-body);
  font-family: inherit;
  color: var(--ink);
  cursor: pointer;
}
.quiz-option:disabled { cursor: default; }
.quiz-option.correct { background: var(--vocab); border-color: var(--vocab-accent); font-weight: 700; }
.quiz-option.wrong { background: #fbe3db; border-color: var(--story-accent); text-decoration: line-through; opacity: 0.8; }

.quiz-feedback {
  margin-top: 10px;
  padding: 10px 12px;
  border-radius: 10px;
  background: #fff;
  font-size: 0.88rem;
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}
.quiz-explain { flex-basis: 100%; color: var(--ink-soft); margin-top: 4px; }

.quiz-nav { text-align: center; margin-top: 12px; }
.quiz-nav button {
  border: none;
  background: var(--quiz-accent);
  color: #fff;
  font-weight: 700;
  padding: 9px 22px;
  border-radius: 999px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.quiz-done { text-align: center; padding: 24px 0; }
.quiz-done-icon { font-size: 2rem; color: var(--quiz-accent); margin-bottom: 8px; }
.quiz-done-text { font-family: 'Nunito', -apple-system, sans-serif; font-weight: 800; font-size: 1.1rem; }
.quiz-score { font-family: 'Nunito', -apple-system, sans-serif; font-weight: 900; font-size: 1.6rem; color: var(--ink); margin-top: 4px; }

.answer-key { background: #fff; border-radius: var(--radius); padding: 12px 18px; font-size: var(--fs-note); line-height: var(--lh-cjk); color: var(--ink-soft); }
.answer-key-list { margin: 6px 0 0; padding-left: 18px; }
.answer-key-list li { margin-bottom: 4px; }

.grammar-block { background: var(--grammar); border: 1.5px solid var(--grammar-accent); border-radius: var(--radius); padding: 16px; }
.transform { display: flex; flex-direction: column; gap: 6px; background: #fff; border-radius: 12px; padding: 14px 16px; margin: 10px 0; font-size: var(--fs-lead); }
.transform-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.transform .arrow { color: var(--grammar-accent); font-weight: 700; }
.transform-zh { font-size: var(--fs-body); line-height: var(--lh-cjk); color: var(--ink-soft); margin-top: 4px; }
.practice-list { padding-left: 22px; margin: 0; }
@media (min-width: 760px) {
  .practice-list { columns: 2; column-gap: 28px; }
  .practice-list li { break-inside: avoid; }
}
.practice-list li { margin-bottom: 14px; }
.practice-q { display: block; line-height: 1.6; }
.practice-blank {
  display: block;
  height: 1.9em;
  margin-top: 4px;
  border-bottom: 2px solid var(--grammar-accent);
  opacity: 0.55;
}

/* ---- Grammar unit ----
   The section already sits on a green field, so a white card is what reads as
   "this is a distinct idea". The pale-green fill (--grammar) is reserved for
   *labels* — it's what tells you which box you're looking at, which is the job
   the original printed card gave it. */
.concept-grid { display: grid; gap: 12px; align-items: stretch; }
@media (min-width: 620px) { .concept-grid { grid-template-columns: repeat(2, 1fr); } }

.concept-card {
  display: flex;
  flex-direction: column;
  background: #fff;
  border: 1.5px solid var(--grammar-accent);
  border-radius: 14px;
  padding: 0 0 12px;
  overflow: hidden;
}
.concept-label {
  background: var(--grammar);
  color: var(--grammar-ink);
  font-weight: 800;
  font-size: var(--fs-body);
  padding: 8px 14px;
  margin: 0 0 10px;
}
/* Two cards sit side by side in the left column, so each is ~290px — not enough
   to put the picture beside the text without squeezing "Learning English is fun."
   into three lines. The picture goes underneath, sized as a spot illustration. */
.concept-body { padding: 0 14px; display: flex; flex-direction: column; flex: 1; }
.concept-text { min-width: 0; }
.concept-illus { width: 220px; aspect-ratio: 4 / 3; margin: 10px auto 0; }
@media (min-width: 620px) {
  /* Two cards side by side: tucking the spot illustration into the bottom-right
     corner keeps the text block flush left instead of leaving a gutter each side. */
  .concept-illus { margin-right: 0; }
}
.concept-text { margin-bottom: auto; }

.pattern-line {
  background: #fff;
  border: 1.5px dashed var(--grammar-accent);
  border-radius: 12px;
  padding: 10px 14px;
  margin: 10px 0 14px;
  font-size: var(--fs-body);
  line-height: var(--lh-cjk);
}
.pattern-gloss { color: var(--ink-soft); margin-left: 6px; }
.pattern-tag {
  display: inline-block;
  background: var(--grammar);
  color: var(--grammar-ink);
  font-weight: 800;
  font-size: var(--fs-caption);
  border-radius: 999px;
  padding: 2px 10px;
  margin-right: 8px;
}
.block-heading {
  font-weight: 800;
  font-size: var(--fs-h2);
  color: var(--grammar-ink);
  margin: 22px 0 10px;
}
.block-label {
  display: inline-block;
  background: var(--grammar);
  color: var(--grammar-ink);
  font-weight: 800;
  font-size: var(--fs-note);
  border-radius: 999px;
  padding: 4px 14px;
  margin-bottom: 10px;
}

/* before -> after, the one worked comparison */
.transform-featured {
  display: grid;
  grid-template-columns: 1fr;
  gap: 8px;
  align-items: stretch;
  margin-bottom: 16px;
}
.tf-side { background: #fff; border: 1.5px solid var(--grammar-accent); border-radius: 12px; overflow: hidden; }
.tf-label {
  background: var(--grammar);
  color: var(--grammar-ink);
  font-weight: 700;
  font-size: var(--fs-note);
  text-align: center;
  padding: 6px 10px;
}
.tf-body { padding: 12px 14px 6px; font-size: var(--fs-lead); line-height: 1.6; }
.tf-note {
  padding: 0 14px 12px;
  font-size: var(--fs-note);
  line-height: var(--lh-cjk);
  color: var(--ink-soft);
}
/* The chunk that moved to the end of the sentence — the rule in one mark. */
.moved {
  font-style: normal;
  font-weight: 700;
  color: var(--story-ink);
  background: #F9DCB5;
  border-radius: 4px;
  padding: 0 4px;
}
.tf-arrow { color: var(--grammar-accent); font-size: 1.5rem; font-weight: 700; text-align: center; line-height: 1; }
.tf-zh { font-size: var(--fs-body); line-height: var(--lh-cjk); color: var(--ink-soft); }
@media (min-width: 720px) {
  .transform-featured { grid-template-columns: 1fr auto 1fr; gap: 10px; }
  .tf-arrow { align-self: center; }
  .tf-zh { grid-column: 1 / -1; }
}

.examples-split { margin-bottom: 4px; }
/* The two halves are a matched pair — the rule's shape and the rule applied —
   so they read as one row, not two cards of different heights. */
.examples-split > * { height: 100%; }
.more-examples { background: #fff; border: 1.5px solid var(--grammar-accent); border-radius: 12px; padding: 12px 14px; }
.more-list { list-style: none; margin: 0; padding: 0; }
.more-row {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: baseline;
  gap: 8px;
  padding: 10px 0;
  font-size: var(--fs-body);
  line-height: 1.55;
  border-top: 1px dashed var(--divider);
}
.more-row:first-child { border-top: none; padding-top: 0; }
.more-arrow { color: var(--grammar-accent); font-weight: 700; }
.more-zh { grid-column: 1 / -1; color: var(--ink-soft); font-size: var(--fs-note); line-height: var(--lh-cjk); }
/* The list now lives in the narrower half, so before/after stack instead of
   sitting side by side — a two-column split at this width broke every sentence
   across three lines and stranded the speak button on its own. Stacked, the
   arrow points down the way the rewrite reads. */
.more-row { grid-template-columns: 1fr; }
.more-arrow { justify-self: start; display: inline-block; transform: rotate(90deg); }
@media (min-width: 1400px) {
  .more-row { grid-template-columns: 1fr auto 1fr; }
  .more-arrow { transform: none; }
}

/* ---- Sentence-shape diagram ---- */
.structure-box {
  background: #fff;
  border: 1.5px solid var(--grammar-accent);
  border-radius: 12px;
  padding: 12px 14px 16px;
  text-align: center;
}
.structure-box .block-label { display: inline-block; margin-bottom: 12px; }
.sd-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 6px;
}
.sd-chip {
  display: inline-block;
  background: var(--grammar);
  border: 1.5px solid var(--grammar-accent);
  border-radius: 10px;
  padding: 6px 12px;
  font-size: var(--fs-note);
  font-weight: 700;
  white-space: nowrap;
}
.sd-chip.is-real { background: #fff; border-style: dashed; }
.sd-plus { color: var(--ink-soft); font-weight: 700; }
.sd-down { color: var(--grammar-accent); font-size: 1.4rem; line-height: 1.2; margin: 4px 0; }
.sd-real { display: flex; flex-direction: column; align-items: center; gap: 4px; }
.sd-caption { font-size: var(--fs-caption); color: var(--story-ink); font-weight: 700; }

/* Lexi's aside. Dashed border marks it as commentary rather than another rule. */
.tip-card {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  background: #fff;
  border: 2px dashed var(--grammar-accent);
  border-radius: 14px;
  padding: 14px;
  margin-top: 16px;
}
.tip-body { flex: 1 1 auto; min-width: 0; }
.tip-body p { margin: 0; font-size: var(--fs-note); line-height: var(--lh-cjk); color: var(--ink-soft); }
.tip-label { font-weight: 800; color: var(--grammar-ink); font-size: var(--fs-body); margin-bottom: 6px; }
/* 4:3 to match the delivered file — at 1:1 the sides of grammar-tip.png were
   being cropped away by object-fit: cover */
.tip-illus { flex: none; width: 132px; aspect-ratio: 4 / 3; margin-bottom: 0; }

/* ---- Sub-blocks below the rule: the skit, then the practice ----
   Each is its own act, so each gets its own frame rather than sharing a column. */
.unit-block {
  background: #fff;
  border: 1.5px solid var(--grammar-accent);
  border-radius: 14px;
  padding: 0 0 16px;
  margin-top: 18px;
  overflow: hidden;
}
.unit-head {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--grammar);
  color: var(--grammar-ink);
  font-weight: 800;
  font-size: var(--fs-body);
  padding: 10px 16px;
  margin-bottom: 14px;
}
.unit-block > *:not(.unit-head) { margin-left: 16px; margin-right: 16px; }
.unit-block > .mini-comic { margin-left: 16px; margin-right: 16px; }

/* ---- Mini comic: picture + speech bubble per panel ----
   Every panel is built to the same spec — same bubble height, same square
   picture, same caption row — so the strip reads as a sequence of equal beats.
   Letting each panel size to its own content made a four-word line and a
   twelve-word line produce visibly different cards, which read as clutter. */
.mini-comic {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  grid-auto-rows: 1fr;
  gap: 12px;
  margin: 0;
}
/* Six panels is a strip, not a grid — it runs as one row once there's width for
   it, the way the printed card reads. Three-up is the halfway house so a tablet
   gets two tidy rows rather than four-plus-two. */
@media (min-width: 700px) {
  .mini-comic { grid-template-columns: repeat(3, 1fr); }
  .comic-bubble { min-height: 4.6em; }
}
@media (min-width: 1100px) {
  .mini-comic { grid-template-columns: repeat(6, 1fr); }
  .comic-bubble { min-height: 5.4em; }
}

.comic-panel {
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.comic-bubble {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Reserve the worst case at each column count so a four-word line and a
     twelve-word line produce identical bubbles: three lines when the panels are
     two-up on a phone, two lines when they're four-up and wider. Paired with
     grid-auto-rows:1fr on the strip, every panel comes out the same card. */
  min-height: 5.4em;
  background: var(--chip-bg);
  border-radius: 12px;
  padding: 8px 12px;
  font-size: var(--fs-note);
  line-height: 1.5;
  text-align: center;
}
.comic-bubble::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -7px;
  transform: translateX(-50%);
  border: 7px solid transparent;
  border-top-color: var(--chip-bg);
  border-bottom: 0;
}
/* One bubble colour for the whole strip. Tinting the mistake and the correction
   made Bearry's two lines look like two different characters speaking; the ✗/✓
   beside the speaker carries that distinction now. */
.comic-illus { aspect-ratio: 1 / 1; margin-bottom: 0; }
.comic-speaker {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  font-weight: 800;
  font-size: var(--fs-note);
  color: var(--grammar-ink);
}
.comic-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.35em;
  height: 1.35em;
  border-radius: 999px;
  font-size: 0.85em;
  line-height: 1;
}
.comic-mark.is-wrong { background: #FBE4CE; color: var(--story-ink); }
.comic-mark.is-right { background: var(--grammar); color: var(--grammar-ink); }

/* ---- Story scene strip: the story's illustration_scenes as an ordered filmstrip ---- */
.scene-strip {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
  margin-top: 14px;
}
.scene-card {
  margin: 0;
  background: #fff;
  border: 1px dashed var(--divider);
  border-radius: 12px;
  padding: 8px;
}
.scene-card .illus { margin-bottom: 6px; }
.scene-card figcaption {
  font-size: var(--fs-note);
  color: var(--ink-soft);
  line-height: 1.6;
}
.scene-num {
  color: var(--story-ink);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  margin-right: 6px;
  border-radius: 999px;
  background: var(--story);
  font-family: 'Nunito', sans-serif;
  font-weight: 800;
  font-size: 0.7rem;
  vertical-align: 1px;
}

/* ---- Review section (spaced repetition) ---- */
.review-block { background: #fff; border-radius: 12px; padding: 14px; }
.ratio-bar {
  display: flex;
  height: 26px;
  border-radius: 999px;
  overflow: hidden;
  font-size: 0.72rem;
  font-weight: 700;
  color: #fff;
  white-space: nowrap;
}
.ratio-bar span { display: flex; align-items: center; justify-content: center; }
.ratio-new { background: var(--vocab-accent); }
.ratio-old { background: var(--review-accent); }
.review-label {
  margin: 16px 0 10px;
  font-size: var(--fs-note);
  font-weight: 700;
  color: var(--review-ink);
}
.review-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.review-chip {
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 6px 8px 6px 16px;
  border-radius: 999px;
  background: var(--review);
  color: var(--ink);
  font-size: var(--fs-body);
  white-space: nowrap;
}
.review-note {
  margin: 14px 0 0;
  font-size: var(--fs-note);
  color: var(--ink-soft);
  line-height: var(--lh-cjk);
}

/* ---- Two-column split: reading matter left, pictures/practice right ----
   One rule set, used by both the story (passage | scene strip) and the grammar
   unit (teaching | skit + practice). Below 900px it collapses to a single column
   in source order, so the phone still reads top to bottom. */
.split { display: grid; grid-template-columns: 1fr; gap: 16px; }
.split-aside { min-width: 0; }
.split-main { min-width: 0; }

@media (min-width: 900px) {
  .split { grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); gap: 22px; align-items: start; }
  /* the list carries more text than the diagram, so it gets the wider half */
  .examples-split { grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr); gap: 14px; align-items: stretch; }
  /* The passage sets its own measure; inside a column it no longer needs the
     auto margins that centred it across the full width. */
  .story-block .en, .story-block .zh { margin-left: 0; margin-right: 0; }
}

.aside-head {
  display: flex;
  align-items: center;
  gap: 6px;
  font-weight: 800;
  font-size: var(--fs-note);
  color: var(--grammar-ink);
  margin: 0 0 8px;
}
.split-aside .aside-head + .mini-comic { margin-top: 0; }
.split-aside .mini-comic { margin-bottom: 18px; }
.split-aside .scene-strip { margin-top: 0; }

/* Desktop: a little breathing room, still single-column reading width (RWD, not multi-column) */
@media (min-width: 720px) {
  main, .pack-list { max-width: 1240px; }
  /* the strip now lives in the right column, so two across is the right density */
  .scene-strip { grid-template-columns: repeat(2, 1fr); }
}

.story-sentence {
  cursor: pointer;
  /* Faint enough to read as texture, not emphasis — the marks above own the
     emphasis. This only has to say "tappable". */
  border-bottom: 1px dotted rgba(224, 138, 60, 0.35);
}
.story-sentence:active { background: rgba(224, 138, 60, 0.15); }

/* ---- Select-to-ask AI (word/phrase/sentence explainer) ---- */
.ai-trigger {
  position: absolute;
  z-index: 60;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  /* the amber is only 1.44:1 against the cream page, so the border is what makes
     it read as a button rather than a patch of highlighter */
  border: 1.5px solid var(--ai-ink);
  background: var(--ai-accent);
  color: var(--ink);
  padding: 6px 13px;
  border-radius: 999px;
  font-size: 0.85rem;
  font-weight: 700;
  box-shadow: 0 3px 8px rgba(74, 63, 53, 0.25);
  cursor: pointer;
}
.ai-trigger:hover { background: #ffd45c; }

.ai-popup {
  position: absolute;
  z-index: 61;
  width: 260px;
  max-width: calc(100vw - 24px);
  background: #fff;
  border-radius: 16px;
  padding: 14px 16px 12px;
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.2);
}

.ai-popup-selected {
  font-weight: 700;
  color: var(--ai-ink);
  font-size: 0.95rem;
  margin: 0 20px 6px 0;
}

.ai-popup-body {
  font-size: 0.9rem;
  line-height: 1.55;
  color: var(--ink);
  max-height: 160px;
  overflow-y: auto;
}

.ai-popup-body.loading { color: var(--ink-soft); }

.ai-popup-close {
  position: absolute;
  top: 8px;
  right: 10px;
  border: none;
  background: none;
  font-size: 0.8rem;
  color: var(--ink-soft);
  cursor: pointer;
  padding: 4px;
}

footer.site-footer {
  text-align: center;
  font-size: 0.85rem;
  color: var(--ink-faint);
  padding: 20px;
}

a.download-pdf {
  /* lives inside the tab row now, so no margin of its own */
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--ink);
  color: #fff;
  text-decoration: none;
  padding: 9px 16px;
  min-height: 38px;
  border-radius: 999px;
  font-size: var(--fs-caption);
  font-weight: 700;
  white-space: nowrap;
}

/* ---- Home / pack list page ---- */
.pack-list { display: flex; flex-direction: column; gap: 10px; padding: 14px; max-width: 640px; margin: 0 auto; }
.pack-list a.pack-link {
  display: flex;
  justify-content: space-between;
  align-items: center;
  background: #fff;
  border-radius: var(--radius);
  padding: 14px 16px;
  text-decoration: none;
  color: var(--ink);
}
.pack-list .pack-link .badge { background: var(--vocab-accent); color: #fff; border-radius: 999px; padding: 3px 10px; font-size: 0.8rem; }


/* ---- Pattern stage: abstract slots on top, the real sentence moving below ---- */
.pattern-stage {
  display: flex;
  flex-direction: column;
  background: #fff;
  border: 1.5px solid var(--grammar-accent);
  border-radius: 12px;
  padding: 12px 14px 14px;
  text-align: center;
}
/* when the pair is stretched to equal height, the controls sit at the bottom
   rather than floating in the middle of the leftover space */
.pattern-stage .rw-controls { margin-top: auto; }
.ps-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 6px;
  margin-bottom: 12px;
}
.ps-chip {
  display: inline-block;
  background: var(--grammar);
  border: 1.5px solid var(--grammar-accent);
  border-radius: 10px;
  padding: 5px 11px;
  font-size: var(--fs-note);
  font-weight: 700;
  white-space: nowrap;
  transition: background 300ms ease, box-shadow 300ms ease;
}
/* the slot the current step is talking about */
.ps-chip.lit {
  background: #F9DCB5;
  border-color: var(--story-accent);
  box-shadow: 0 0 0 3px rgba(224, 138, 60, 0.18);
}
.ps-plus { color: var(--ink-soft); font-weight: 700; }
.ps-rule { height: 1px; background: var(--divider); margin: 0 0 14px; }

.rw {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: center;
  gap: 4px 8px;
  min-height: 5.4em;
  font-size: var(--fs-lead);
  line-height: 1.4;
  margin-bottom: 10px;
}
.rw-tok {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  border-radius: 8px;
  padding: 3px 5px;
  transition: background 300ms ease, color 300ms ease;
}
/* Every word names the slot it fills — that label is the diagram, attached to
   the thing it describes instead of floating in a box of its own. */
.rw-role {
  font-size: 0.62em;
  font-weight: 700;
  line-height: 1.2;
  color: var(--ink-faint);
  white-space: nowrap;
}
.rw-subject .rw-word { font-weight: 700; }
.rw-formal .rw-word { font-weight: 800; }
/* The full stop hangs off whichever token currently ends the sentence. As its
   own flex item it wrapped onto a line of its own the moment the sentence got
   long enough — and a period alone on line two is not a sentence. */
.rw[data-stage="0"] .rw-rest .rw-word::after,
.rw[data-stage="1"] .rw-rest .rw-word::after,
.rw[data-stage="2"] .rw-subject .rw-word::after { content: var(--rw-end, "."); }

/* stage 0-1: the original sentence; `It` is not in play yet */
.rw[data-stage="0"] .rw-formal,
.rw[data-stage="1"] .rw-formal { opacity: 0; width: 0; padding: 0; overflow: hidden; }
.rw[data-stage="0"] .rw-subject { order: 1; }
.rw[data-stage="1"] .rw-subject { order: 1; background: #F9DCB5; color: var(--story-ink); }
.rw[data-stage="0"] .rw-be, .rw[data-stage="1"] .rw-be { order: 2; }
.rw[data-stage="0"] .rw-rest, .rw[data-stage="1"] .rw-rest { order: 3; }
/* in the original the long phrase IS the subject, so say so */
.rw[data-stage="0"] .rw-subject .rw-role,
.rw[data-stage="1"] .rw-subject .rw-role { color: var(--story-ink); }

/* stage 2: the phrase has moved to the end, `it` holds the subject slot */
.rw[data-stage="2"] .rw-formal { order: 0; opacity: 1; background: var(--vocab); color: var(--vocab-ink); }
.rw[data-stage="2"] .rw-be { order: 1; }
.rw[data-stage="2"] .rw-rest { order: 2; }
.rw[data-stage="2"] .rw-subject { order: 3; background: #F9DCB5; color: var(--story-ink); }
.rw[data-stage="2"] .rw-subject .rw-word::first-letter { text-transform: lowercase; }

.rw-caption {
  min-height: 4.4em;
  font-size: var(--fs-note);
  line-height: var(--lh-cjk);
  color: var(--ink-soft);
  text-align: left;
}
.rw-controls {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 8px;
}
.rw-step {
  width: 34px;
  height: 34px;
  border: 1.5px solid var(--grammar-accent);
  border-radius: 999px;
  background: #fff;
  color: var(--grammar-ink);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.rw-step:disabled { opacity: 0.35; cursor: default; }
.rw-dots { display: inline-flex; gap: 6px; }
.rw-dot {
  width: 10px;
  height: 10px;
  padding: 0;
  border: none;
  border-radius: 999px;
  background: var(--divider);
  cursor: pointer;
}
.rw-dot.active { background: var(--grammar-accent); }
.rw-replay {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 14px;
  min-height: 34px;
  border: 1.5px solid var(--grammar-accent);
  border-radius: 999px;
  background: #fff;
  color: var(--grammar-ink);
  font-family: inherit;
  font-size: var(--fs-caption);
  font-weight: 700;
  cursor: pointer;
}

/* ---- Practice input ---- */
.practice-item { margin-bottom: 20px; }
.practice-answer { display: flex; gap: 8px; margin-top: 8px; }
.practice-input {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 44px;
  padding: 9px 14px;
  border: 1.5px solid var(--divider);
  border-radius: 10px;
  background: #fff;
  font-family: inherit;
  font-size: var(--fs-body);
  color: var(--ink);
}
.practice-input:focus { outline: 2px solid var(--grammar-accent); outline-offset: 1px; }
.practice-check {
  flex: none;
  min-height: 44px;
  padding: 9px 18px;
  border: none;
  border-radius: 10px;
  background: var(--grammar-ink);
  color: #fff;
  font-family: inherit;
  font-size: var(--fs-caption);
  font-weight: 700;
  cursor: pointer;
}
.practice-item[data-state="correct"] .practice-input { border-color: var(--grammar-accent); background: var(--grammar); }
.practice-item[data-state="wrong"] .practice-input { border-color: var(--story-accent); }
.practice-result {
  margin-top: 8px;
  font-size: var(--fs-note);
  line-height: var(--lh-cjk);
}
.practice-result.is-correct { color: var(--grammar-ink); font-weight: 700; }
.practice-result.is-wrong { color: var(--story-ink); }
.practice-result.is-shown { color: var(--ink-soft); }
.pr-label {
  display: inline-block;
  background: var(--chip-bg);
  border-radius: 999px;
  padding: 1px 10px;
  margin-right: 6px;
  font-weight: 700;
}
.practice-reveal {
  border: none;
  background: none;
  padding: 0 2px;
  color: var(--grammar-ink);
  font-family: inherit;
  font-size: inherit;
  font-weight: 700;
  text-decoration: underline;
  cursor: pointer;
}
.practice-input.shake { animation: shake 320ms ease-in-out; }
/* the written blank is for paper only */
.practice-item .practice-blank { display: none; }

/* ================= Motion =================
   Every animation here reports something: an answer landed, a section changed,
   a pack got marked done, the page is talking. None of it is ambient. All of it
   is off under prefers-reduced-motion, at the bottom of this block. */

/* Section change: the new panel arrives rather than snapping in, so the eye
   knows the content under the tabs was replaced. */
@keyframes panel-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}
main .section { animation: panel-in 260ms ease-out both; }

/* A wrong answer: a short, small shake. Long or wide reads as punishment. */
@keyframes shake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-5px); }
  45% { transform: translateX(4px); }
  70% { transform: translateX(-2px); }
}
.quiz-option.shake { animation: shake 320ms ease-in-out; }

/* A right answer settles into place instead of just recolouring. */
@keyframes pop {
  0% { transform: scale(1); }
  45% { transform: scale(1.06); }
  100% { transform: scale(1); }
}
.quiz-option.correct { animation: pop 300ms ease-out; }
.quiz-feedback:not([hidden]) { animation: panel-in 240ms ease-out both; }
.quiz-done:not([hidden]) .quiz-done-icon { animation: pop 500ms 80ms ease-out both; }

/* Read-aloud: the speaking button pulses for as long as its own queue runs.
   Before this there was no sign a tap had registered. */
@keyframes speaking {
  0%, 100% { box-shadow: 0 0 0 0 rgba(91, 141, 190, 0.5); }
  50% { box-shadow: 0 0 0 6px rgba(91, 141, 190, 0); }
}
.speak-btn.is-speaking {
  background: var(--vocab);
  color: var(--vocab-ink);
  animation: speaking 1.1s ease-out infinite;
}

/* Marking a pack done: the reward lands on the chip that changed. */
@keyframes chip-done {
  0% { transform: scale(1); }
  35% { transform: scale(1.25) rotate(-6deg); }
  70% { transform: scale(0.96) rotate(3deg); }
  100% { transform: scale(1); }
}
.day-chip.just-done { animation: chip-done 620ms ease-out; }

/* Modals scale up from the control that opened them. */
@keyframes modal-in {
  from { opacity: 0; transform: scale(0.94) translateY(10px); }
  to   { opacity: 1; transform: none; }
}
.journey-modal:not([hidden]) .journey-modal-card,
.notice-modal:not([hidden]) .notice-card { animation: modal-in 220ms cubic-bezier(0.2, 0.9, 0.3, 1) both; }

/* The skit plays in, panel by panel — it is a strip of beats, so it arrives as
   one. Six short steps, done in under a second. */
@keyframes panel-drop {
  from { opacity: 0; transform: translateY(10px) scale(0.97); }
  to   { opacity: 1; transform: none; }
}
.comic-panel { animation: panel-drop 320ms ease-out both; }
.comic-panel:nth-child(1) { animation-delay: 60ms; }
.comic-panel:nth-child(2) { animation-delay: 150ms; }
.comic-panel:nth-child(3) { animation-delay: 240ms; }
.comic-panel:nth-child(4) { animation-delay: 330ms; }
.comic-panel:nth-child(5) { animation-delay: 420ms; }
.comic-panel:nth-child(6) { animation-delay: 510ms; }

/* Pointer affordances — hover only, so touch never gets a stuck state. */
@media (hover: hover) {
  .vocab-card, .quiz-option, .day-chip, .scene-card {
    transition: transform 160ms ease-out, box-shadow 160ms ease-out;
  }
  .vocab-card:hover { transform: translateY(-3px); box-shadow: 0 6px 18px rgba(74, 63, 53, 0.10); }
  .quiz-option:not(:disabled):hover { transform: translateY(-2px); }
  .day-chip:not([data-status="locked"]):hover { transform: translateY(-2px); }
  .scene-card:hover { transform: translateY(-2px); }
}

/* Sound toggle */
.section-nav .sound-btn {
  flex: none;
  width: 38px;
  min-width: 38px;
  height: 38px;
  padding: 0;
  border-radius: 999px;
  border: 1.5px solid var(--vocab-ink);
  background: var(--vocab);
  color: var(--vocab-ink);
  font-size: var(--fs-caption);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.section-nav .sound-btn .ico { width: 1em; height: 1em; flex: none; }
.section-nav .sound-btn .sound-off { display: none; }
.section-nav .sound-btn[data-on="false"] {
  border-color: var(--ink-faint);
  background: var(--chip-bg);
  color: var(--ink-faint);
}
.section-nav .sound-btn[data-on="false"] .sound-on { display: none; }
.section-nav .sound-btn[data-on="false"] .sound-off { display: inline-flex; }

/* Someone who asks for less motion gets none of it — including the hover lifts
   and the smooth scroll on the journey strip, which was an open item until now. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  .vocab-card:hover, .quiz-option:hover, .day-chip:hover, .scene-card:hover { transform: none; }
}

/* ================= Print / PDF =================
   Rendered by headless Chrome against this same stylesheet, so nothing here
   re-implements a layout — it only removes what paper can't use, compacts the
   type, and controls where pages break. The old weasyprint fallbacks (inline-block
   grids, fixed illustration heights) are gone with the engine that needed them.

   Ink-saving: section and card fills go white, structure is carried by borders
   and headings. On a home printer the tinted blocks were the whole page. */
@media print {
  :root {
    font-size: 10.5pt;
    /* every tint resolves to white; borders and text keep their hue */
    --cream: #fff;
    --vocab: #fff;
    --story: #fff;
    --quiz: #fff;
    --grammar: #fff;
    --review: #fff;
    --chip-bg: #fff;
    --placeholder-bg-1: #fff;
    --placeholder-bg-2: #f4f4f4;
  }

  @page { size: A4; margin: 14mm; }
  body { background: #fff; orphans: 2; widows: 2; }
  .pack-header { padding: 8px 12px; border-bottom-width: 2px; }
  .pack-header .pack-title { font-size: 1.05rem; }

  /* Screen-only chrome */
  .journey, .journey-modal, .notice-modal, .section-nav, a.download-pdf,
  .mark-complete, .ai-trigger, .ai-popup, .speak-btn, footer.site-footer,
  #review { display: none !important; }

  /* Interactive parts of the material that mean nothing on paper */
  .rw, .rw-caption, .rw-controls { display: none !important; }  /* the動畫 itself —
      the before/after comparison above it already says what changed */
  .practice-answer, .practice-result { display: none !important; }
  .practice-item .practice-blank { display: block !important; }
  .collapsible-body.is-collapsed { display: block !important; }  /* answers print open */

  /* One section per page, and each section sized to actually fit one. */
  main { max-width: none; padding: 0; }
  .section {
    display: block !important;
    break-inside: auto;
    border-width: 1px;
    border-radius: 10px;
    padding: 10px 12px;
    margin: 0;
  }
  .section + .section { break-before: page; }
  .section h2 { font-size: 1.1rem; margin-bottom: 6px; }
  .section-num { width: 22px; height: 22px; font-size: 0.85rem; }

  /* Vocab: nine cards, three across, on one page. The screen card is built for
     a phone column; on paper it can be much tighter without losing anything. */
  .vocab-grid { grid-template-columns: repeat(3, 1fr) !important; gap: 5px; grid-auto-rows: auto; }
  /* No break-inside:avoid here on purpose. Chrome fragments a grid conservatively:
     with it set, a row that would fit in the 91mm still free at the foot of the
     page got pushed to the next sheet anyway. Without it all nine land on one
     page, which is the goal — and since they fit, nothing actually gets cut.
     If a future pack's entries run much longer, this section takes two pages. */
  .vocab-card { padding: 5px 7px 6px; border-width: 1px; }
  .vocab-card .word { font-size: 1.05rem; }
  .vocab-card .meta { font-size: 0.78rem; }
  .vocab-card .pos-meaning { font-size: 0.9rem; margin: 2px 0 4px; }
  .vocab-card .collo,
  .vocab-card .example { font-size: 0.8rem; line-height: 1.5; }
  .vocab-card .example { margin-top: 5px; padding-top: 5px; }
  .vocab-card .usage-note,
  .vocab-card .related-words,
  .comparison { font-size: 0.75rem; line-height: 1.5; margin-top: 5px; }
  /* The vocab card's picture is dropped on paper, and only here. Nine cards with
     an image do not fit an A4 page at a readable size — measured: letterboxing it
     to 4:1 still spilled a card onto a second sheet, and only removing it landed
     the section on one page. It is also the most ink-hungry element on the sheet.
     Every other illustration (story scenes, grammar, the skit) still prints. */
  .vocab-card .illus { display: none; }
  .illus-slot-name { display: none; }
  .extra-vocab { margin-top: 8px; padding: 6px 8px; font-size: 0.78rem; }

  /* Story: passage and scenes share the page. */
  .split { grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr) !important; gap: 12px; }
  .split > * { height: auto !important; }   /* screen-only equal-height pairing */
  .story-block { padding: 10px 12px; }
  .story-block .en { font-size: 0.98rem; line-height: 1.6; max-width: none; }
  .story-block .zh { font-size: 0.88rem; line-height: 1.7; max-width: none; }
  .story-sentence { border-bottom: none; }  /* the dotted rule meant "tap to hear" */
  .scene-strip { grid-template-columns: repeat(2, 1fr); gap: 6px; }
  .scene-card { padding: 5px; break-inside: avoid; }
  .scene-card figcaption { font-size: 0.72rem; line-height: 1.45; }

  /* Quiz: all four questions on one page, options inline. */
  .quiz-progress, .quiz-nav, .quiz-done, .quiz-feedback { display: none !important; }
  .quiz-item { display: block !important; break-inside: avoid; padding: 8px 10px; margin-bottom: 7px; border-width: 1px; }
  .quiz-item .q { font-size: 0.95rem; margin-bottom: 6px; }
  .quiz-item .options { display: block !important; font-size: 0.85rem; }
  .quiz-option {
    display: inline-block !important;
    width: auto !important;
    min-height: 0 !important;
    padding: 3px 9px !important;
    margin: 0 6px 4px 0 !important;
    border-width: 1px;
  }
  .answer-key { padding: 7px 10px; font-size: 0.78rem; }
  .answer-key .collapsible-toggle { font-size: 0.8rem; }

  /* Grammar: teaching on one page, the skit and the practice on the next —
     both are whole units, so the break goes between them rather than through one. */
  .concept-card, .tip-card, .unit-block, .more-examples,
  .transform-featured, .pattern-stage { break-inside: avoid; }
  .unit-block { break-before: page; margin-top: 0; border-width: 1px; }
  .unit-block + .unit-block { break-before: auto; }  /* skit and practice share a page */
  .concept-grid { gap: 8px; }
  .concept-card { border-width: 1px; }
  .concept-label, .unit-head, .tf-label, .block-label { font-size: 0.85rem; }
  .concept-illus { width: 110px; }
  /* the aside is three lines; at its screen size it fell just short of the page
     and took a whole sheet to itself */
  .tip-card { padding: 8px 10px; gap: 8px; margin-top: 10px; border-width: 1.5px; }
  .tip-illus { width: 76px; }
  .tip-body p { font-size: 0.82rem; }
  .tip-label { font-size: 0.88rem; margin-bottom: 3px; }
  .concept-example, .tf-body, .pattern-line { font-size: 0.92rem; }
  .more-row { grid-template-columns: 1fr auto 1fr; font-size: 0.85rem; padding: 6px 0; }
  .more-arrow { transform: none; }
  .pattern-stage .ps-row { margin-bottom: 0; }
  .pattern-stage .ps-rule { display: none; }
  .practice-list { columns: 2; column-gap: 20px; font-size: 0.88rem; }
  .practice-list li { break-inside: avoid; margin-bottom: 10px; }

  /* Skit: six panels across two rows at a readable size. */
  .mini-comic { grid-template-columns: repeat(3, 1fr) !important; gap: 8px; grid-auto-rows: auto; }
  .comic-panel { break-inside: avoid; }
  .comic-bubble { min-height: 0 !important; border: 1px solid var(--divider); font-size: 0.8rem; }
  /* same 1:1 as the screen — object-fit:cover would otherwise crop the top and
     bottom off every character when the sheet is printed */
  .comic-illus { aspect-ratio: 1 / 1; }
  .comic-speaker { font-size: 0.8rem; }

  /* Highlights: keep the emphasis, drop the solid fills. */
  .story-block mark, .moved, .rw-subject {
    background: transparent !important;
    box-shadow: none !important;
    border-bottom: 2px solid var(--story-accent);
    font-weight: 700;
    padding: 0 1px;
  }
}
