  :root {
    --paper:        #f6ecd5;
    --paper-2:      #fbf5e6;
    --paper-edge:   #ead9b2;
    --paper-shadow: rgba(100,72,30,0.10);

    --ink:    #3a2818;
    --ink-2:  #6b5740;
    --ink-3:  #9a8869;
    --ink-4:  #c2b294;

    --gold:       #b48a4a;
    --gold-dark:  #8d6630;
    --gold-soft:  #d9c191;
    --sage:       #7a8f6e;
    --sage-soft:  #b9c4a6;
    --rose:       #c08a8a;

    --accent:      var(--gold);
    --accent-soft: var(--gold-soft);
    /* The hinomaru vermilion (the logo's sun). 18 declarations referenced
       this token before it was ever defined — they silently fell back to
       #c34b30 fallbacks or currentColor. Defined here so they finally
       resolve as designed. */
    --accent-vermilion: #c34b30;

    --serif-jp:   'Shippori Mincho', 'Noto Serif JP', 'Hiragino Mincho Pro', serif;
    /* Role-based JP font tokens — let each typography slot be themed
       independently. Defaults are deliberate: brush for titles (calligraphic
       page headers), mincho for menus + body (legible at small sizes),
       Garamond italic for English menu sub-labels. */
    --font-title:    'Yuji Syuku', 'Kaisei Decol', 'Hina Mincho', 'Shippori Mincho', serif;
    --font-menu-ja:  var(--serif-jp);
    --font-menu-en:  'EB Garamond', Georgia, serif;
    --font-body:     var(--serif-jp);
  }
  /* Body font theme — switches `--serif-jp` (the body / "rest of the
     site" stack) based on user choice. */
  html[data-font-body="gothic"]   { --serif-jp: 'Zen Kaku Gothic New', 'Hiragino Sans', 'Yu Gothic', sans-serif; }
  html[data-font-body="kyokasho"] { --serif-jp: 'Klee One', 'Shippori Mincho', 'Hiragino Mincho Pro', serif; }
  html[data-font-body="brush"]    { --serif-jp: 'Yuji Syuku', 'Kaisei Decol', 'Hina Mincho', 'Shippori Mincho', serif; }
  html[data-font-body="mincho"]   { --serif-jp: 'Shippori Mincho', 'Noto Serif JP', 'Hiragino Mincho Pro', serif; }
  html[data-font-body="noto"]     { --serif-jp: 'Noto Sans JP', 'Hiragino Sans', system-ui, sans-serif; }
  /* Per-role font themes — title / menu-ja / menu-en each have their own
     attribute. menu-en falls back to Latin font choices since it sets
     English label typography. */
  html[data-font-title="mincho"]   { --font-title: 'Shippori Mincho', 'Noto Serif JP', serif; }
  html[data-font-title="gothic"]   { --font-title: 'Zen Kaku Gothic New', 'Hiragino Sans', sans-serif; }
  html[data-font-title="kyokasho"] { --font-title: 'Klee One', 'Shippori Mincho', serif; }
  html[data-font-title="brush"]    { --font-title: 'Yuji Syuku', 'Kaisei Decol', 'Hina Mincho', 'Shippori Mincho', serif; }
  html[data-font-title="noto"]     { --font-title: 'Noto Sans JP', 'Hiragino Sans', system-ui, sans-serif; }
  html[data-font-menu-ja="mincho"]   { --font-menu-ja: 'Shippori Mincho', 'Noto Serif JP', serif; }
  html[data-font-menu-ja="gothic"]   { --font-menu-ja: 'Zen Kaku Gothic New', 'Hiragino Sans', sans-serif; }
  html[data-font-menu-ja="kyokasho"] { --font-menu-ja: 'Klee One', 'Shippori Mincho', serif; }
  html[data-font-menu-ja="brush"]    { --font-menu-ja: 'Yuji Syuku', 'Kaisei Decol', 'Hina Mincho', serif; }
  html[data-font-menu-ja="noto"]     { --font-menu-ja: 'Noto Sans JP', 'Hiragino Sans', system-ui, sans-serif; }
  html[data-font-menu-en="garamond"] { --font-menu-en: 'EB Garamond', Georgia, serif; }
  html[data-font-menu-en="sans"]     { --font-menu-en: 'Inter', 'Helvetica Neue', system-ui, sans-serif; }
  html[data-font-menu-en="caveat"]   { --font-menu-en: 'Caveat', cursive; }
  html[data-font-menu-en="mincho"]   { --font-menu-en: 'Shippori Mincho', 'Noto Serif JP', serif; }
  html[data-font-menu-en="noto"]     { --font-menu-en: 'Noto Sans', 'Helvetica Neue', system-ui, sans-serif; }
  /* Backward-compat alias: legacy `data-font` attribute (used by the
     older single-knob settings) still themes the body stack. */
  html[data-font="gothic"]   { --serif-jp: 'Zen Kaku Gothic New', 'Hiragino Sans', 'Yu Gothic', sans-serif; }
  html[data-font="kyokasho"] { --serif-jp: 'Klee One', 'Shippori Mincho', 'Hiragino Mincho Pro', serif; }
  html[data-font="brush"]    { --serif-jp: 'Yuji Syuku', 'Kaisei Decol', 'Hina Mincho', 'Shippori Mincho', serif; }
  html[data-font="noto"]     { --serif-jp: 'Noto Sans JP', 'Hiragino Sans', system-ui, sans-serif; }
  :root {
    --sans-jp:    'Zen Kaku Gothic New', 'Hiragino Sans', sans-serif;
    --serif:      'EB Garamond', Georgia, serif;
    --hand:       'Caveat', cursive;

    --pad-page: 56px;
    --content-max: 980px;
  }

  *, *::before, *::after { box-sizing: border-box; }

  /* ─── Global scrollbar — paper/parchment book style ──────────────────
     The same thin gold-on-cream scrollbar that ships on the in-card
     panels is the site-wide default — applies to every scrollable
     element, on every page, by default. Individual components can still
     override the width or color if they need a custom feel. Covers both
     Firefox (scrollbar-*) and Chromium/Safari (::-webkit-scrollbar). */
  * {
    scrollbar-width: thin;
    scrollbar-color: var(--paper-edge) transparent;
  }
  ::-webkit-scrollbar { width: 8px; height: 8px; }
  ::-webkit-scrollbar-track { background: transparent; }
  ::-webkit-scrollbar-thumb {
    background: var(--paper-edge);
    border-radius: 4px;
  }
  ::-webkit-scrollbar-thumb:hover { background: var(--gold-soft); }
  ::-webkit-scrollbar-corner { background: transparent; }

  html, body {
    margin: 0; padding: 0; min-height: 100%;
    background: var(--paper);
    color: var(--ink);
    font-family: var(--serif);
    font-size: 16px;
    line-height: 1.5;
    background-image:
      radial-gradient(circle at 20% 20%, rgba(180,138,74,0.06), transparent 60%),
      radial-gradient(circle at 80% 80%, rgba(122,143,110,0.05), transparent 60%);
    background-attachment: fixed;
  }
  body {
    background-image:
      radial-gradient(circle at 20% 20%, rgba(180,138,74,0.06), transparent 60%),
      radial-gradient(circle at 80% 80%, rgba(122,143,110,0.05), transparent 60%);
  }
  img { display: block; max-width: 100%; }

  /* ─── layout shell ──────────────────────────────────────────────────── */
  .app {
    display: grid;
    grid-template-columns: 188px 1fr;
    min-height: 100vh;
    position: relative; z-index: 1;
  }
  .main {
    padding: var(--pad-page);
    min-width: 0;
    overflow-x: hidden;
    position: relative;   /* anchor the ::before context-bg layer below */
  }
  /* Context background — when a section (e.g. vocab/eating-out) has its
     own bg folder, JS sets --ctx-bg-url on .main with the picked image.
     The ::before paints it as a soft atmospheric layer at the back of
     the main content, multiply-blended into the paper so only the
     illustration's pigments register (white field dissolves). 70%
     opacity tones it down further so the cards above stay primary.
     When --ctx-bg-url is unset (most pages), the ::before stays
     transparent and contributes nothing.

     The element box (::before) still fills .main so the bg is CLIPPED
     to the content column (sidebar stays clean). But the bg IMAGE is
     sized + anchored to the viewport via `background-attachment: fixed`,
     so long scrollable pages don't stretch the image vertically — it
     stays at viewport size with proper aspect ratio, parked behind the
     content while it scrolls past. The illustration is always visible
     at its intended size, never cropped to a thin band by tall content. */
  .main::before {
    content: "";
    position: absolute;
    inset: 0;
    background-image: var(--ctx-bg-url, none);
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
    background-attachment: fixed;
    mix-blend-mode: multiply;
    opacity: 0.55;
    pointer-events: none;
    z-index: 0;
    transition: opacity .4s ease;
  }
  .main > * { position: relative; z-index: 1; }
  .main-inner {
    max-width: var(--content-max);
    margin: 0 auto;
  }

  /* Stepper host — sits as a sibling of .main-inner, INSIDE .main, as a
     regular block (no max-width, no flex container). The inner .stepper
     paints its own paper backdrop and uses position: sticky to keep the
     nav visible while content below scrolls. Only used on vocab pages;
     :empty collapses the space for every other section. */
  .main-stepper:empty { display: none; }

  /* Vocab: drop the .main top padding so the stepper hugs the viewport
     top. .main-inner still does its own block-level centering (max-width
     + margin: 0 auto), so the content below stays in its 980px lane
     while the stepper above takes the full chrome width.
     EXCEPTION: when the current vocab page has NO stepper (multi-chain
     hubs like yatai, isFoodGallery books, isExperience launchers), the
     #main-stepper is empty and would otherwise leave the content flush
     against the viewport top. The :has(:empty) selector restores
     normal top padding for those pages. */
  .app.show-vocab-sidebar .main { padding-top: 0; }
  .app.show-vocab-sidebar .main:has(#main-stepper:empty) {
    padding-top: var(--pad-page);
  }
  @media (max-width: 760px) {
    .app { grid-template-columns: 1fr; }
    .sidebar { position: sticky; top: 0; width: 100%; min-height: 0; padding: 12px; z-index: 20; }
    .sidebar-inner { flex-direction: row !important; align-items: center; gap: 8px !important; }
    .brand { display: none; }
    .nav-list { display: flex !important; flex-direction: row !important; gap: 6px !important; flex: 1; overflow-x: auto; }
    .nav-list .bookmark { min-width: 96px; flex: 0 0 auto; }
    .sidebar-foot { display: none; }
    :root { --pad-page: 24px; }
  }

  /* ─── sidebar ───────────────────────────────────────────────────────── */
  .sidebar {
    /* Paper gradient stays as the base color. We paint bg-side.png as a
       separate ::after layer below so we can control its opacity
       independently from the background-blend-mode chain (which only
       gives 0/100% per-image visibility, not intermediate values). The
       layer sits BEHIND the sidebar-inner content (z-index 0 vs the
       content's z-index 1) but ABOVE the gradient. */
    background: linear-gradient(180deg, var(--paper-edge) 0%, color-mix(in srgb, var(--paper-edge) 80%, var(--paper) 20%) 100%);
    border-right: 1px solid rgba(141,102,48,0.22);
    box-shadow: inset -10px 0 24px -16px rgba(100,72,30,0.18);
    padding: 24px 14px 24px 18px;
    position: sticky; top: 0; height: 100vh;
    overflow-y: auto;
    overflow-x: hidden;     /* keep the bg layer clipped to the column */
    isolation: isolate;     /* contain the blend mode inside the sidebar */
  }
  /* Sidebar bg layer — full sidebar height (so the gradient fade has
     room to breathe), bottom-anchored illustration scaled to the column
     width. The mask-image alpha gradient fades the layer from fully
     opaque at the BOTTOM (image fully visible) to fully transparent at
     the TOP (no bg interference behind the menu items). `darken` blend
     keeps the DARKER pixel between the image and the paper at each
     point: cream backgrounds in the png dissolve into the paper
     gradient (they're lighter, so paper wins), and only the inked
     strokes (river, blossoms, moon outline) register on top. */
  .sidebar::after {
    content: "";
    position: absolute;
    inset: 0;
    background: url('images/bg/sidebar/bg-side.webp') center bottom / 100% auto no-repeat;
    mix-blend-mode: darken;
    -webkit-mask-image: linear-gradient(to top, #000 0%, transparent 100%);
            mask-image: linear-gradient(to top, #000 0%, transparent 100%);
    pointer-events: none;
    z-index: 0;
  }
  .sidebar-inner { position: relative; z-index: 1; }
  .sidebar-inner { display: flex; flex-direction: column; height: 100%; gap: 24px; }
  .brand {
    display: flex; align-items: center; justify-content: flex-start;
    padding: 0 6px 14px;
    border-bottom: 1px dashed rgba(141,102,48,0.3);
  }
  .brand-logo {
    /* Full-bleed inside the brand block — the K-and-sun mark fills the
       narrow sidebar column. Width caps at the column itself so the mark
       never overflows. Multiply blend so the cream around the logo
       dissolves into the sidebar's paper gradient instead of painting a
       hard rectangle. */
    width: 100%;
    max-width: 140px;
    height: auto;
    display: block;
    mix-blend-mode: multiply;
    cursor: help;     /* signals there's something to discover on hover */
  }

  /* ─── Brand tooltip ─────────────────────────────────────────────────
     Reveals after a deliberate 1.5s hover delay so it doesn't pop on
     casual mouseover — only for the curious lingerer. Hides immediately
     on mouse-leave (no exit delay) so it gets out of the way fast.
     Floats to the RIGHT of the sidebar (the brand sits at the very top-
     left of the viewport; tooltip below would crowd the menu, tooltip
     above would clip the window edge).
     Uses position: fixed so it ESCAPES the sidebar's overflow:hidden
     clip (which is needed there for the bg layer). Fixed coords are
     stable because the sidebar is position: sticky pinned at top:0. */
  .brand {
    position: relative;
    cursor: help;
  }
  .brand-tooltip {
    position: fixed;
    /* Default: only the primary 188px sidebar is on-screen, so the
       tooltip floats just past it with the arrow notch pointing back
       at the logo. */
    left: 202px;
    top: 28px;            /* matches the brand block's natural top */
    width: 320px;
    padding: 16px 18px;
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 8px;
    box-shadow: 0 10px 30px rgba(60,40,10,0.16);
    color: var(--ink-2);
    text-align: left;
    /* Above every sidebar (.sidebar z-index 20, .vocab-books-sidebar 5,
       and every grid-item stacking context). 200 is well clear. */
    z-index: 200;
    opacity: 0;
    visibility: hidden;
    transform: translateX(-6px);
    pointer-events: none;  /* informational only — don't catch clicks */
    /* On hover-OUT: fade opacity quickly, then snap visibility off so
       focus returns to the page. On hover-IN: long 1.5s delay before
       anything starts. */
    transition: opacity .25s ease,
                visibility 0s linear .25s,
                transform .25s ease;
  }
  /* Reveal — 1.5s delay before the fade-in starts. Both :hover and
     :focus-within fire so keyboard users get the tooltip too. */
  .brand:hover .brand-tooltip,
  .brand:focus-within .brand-tooltip {
    opacity: 1;
    visibility: visible;
    transform: translateX(0);
    transition: opacity .3s ease 1.5s,
                visibility 0s linear 1.5s,
                transform .3s ease 1.5s;
  }
  /* When any secondary sidebar is open (flashcards, vocab, writing,
     particles), it lives between the primary sidebar and the main
     content — the default left:202px would place the tooltip
     directly behind it. Slide the tooltip past every known secondary
     sidebar so the content stays readable. The widest secondary is
     160px (flash-sidebar), so 188+160+14 = 362px is the floor. */
  .app.show-flash-sidebar .brand-tooltip,
  .app.show-vocab-sidebar .brand-tooltip,
  .app.show-vocab-books-sidebar .brand-tooltip,
  .app.show-writing-sidebar .brand-tooltip,
  .app.show-library-sidebar .brand-tooltip,
  .app.show-particles-sidebar .brand-tooltip {
    left: 362px;
  }
  /* Arrow pointing at the logo, paper-2 fill so the tooltip reads as a
     soft paper bubble notched into the sidebar edge. Hidden when the
     tooltip has been pushed past a secondary sidebar — the arrow would
     otherwise float in dead space far from the logo it pretends to
     point at. */
  .brand-tooltip::before {
    content: '';
    position: absolute;
    left: -7px; top: 22px;
    width: 12px; height: 12px;
    background: var(--paper-2);
    border-left: 1px solid var(--paper-edge);
    border-bottom: 1px solid var(--paper-edge);
    transform: rotate(45deg);
  }
  .app.show-flash-sidebar .brand-tooltip::before,
  .app.show-vocab-sidebar .brand-tooltip::before,
  .app.show-vocab-books-sidebar .brand-tooltip::before,
  .app.show-writing-sidebar .brand-tooltip::before,
  .app.show-library-sidebar .brand-tooltip::before,
  .app.show-particles-sidebar .brand-tooltip::before {
    display: none;
  }
  .brand-tooltip-head {
    display: flex; align-items: center; gap: 12px;
    padding-bottom: 10px;
    border-bottom: 1px dashed rgba(141,102,48,0.20);
    margin-bottom: 12px;
  }
  .brand-tooltip-glyph {
    font-family: var(--font-title);
    font-size: 44px;
    font-weight: 600;
    line-height: 1;
    color: var(--ink);
  }
  .brand-tooltip-meaning {
    display: flex; flex-direction: column;
    gap: 2px;
    min-width: 0;
  }
  .brand-tooltip-meaning .kw {
    font-family: var(--serif); font-style: italic;
    font-size: 13px;
    color: var(--ink);
    line-height: 1.3;
  }
  .brand-tooltip-meaning .reading {
    font-family: var(--serif-jp);
    font-size: 11px;
    color: var(--ink-3);
    letter-spacing: 0.04em;
  }
  /* Composition row: 日 + 月 → 明, with the sun radical in red */
  .brand-tooltip-compose {
    display: flex; align-items: center; justify-content: center;
    gap: 8px;
    padding: 6px 0 12px;
    font-family: var(--font-title);
    font-size: 22px;
    color: var(--ink-2);
  }
  .brand-tooltip-radical { font-weight: 600; color: var(--ink); }
  .brand-tooltip-radical-sun { color: #c43a4a; }   /* vermilion — same hue as the logo's brushed sun */
  .brand-tooltip-plus,
  .brand-tooltip-arrow {
    font-family: var(--serif);
    font-size: 14px;
    color: var(--ink-4);
  }
  .brand-tooltip-result {
    font-weight: 600;
    color: var(--ink);
  }
  .brand-tooltip p {
    margin: 0 0 8px;
    font-family: var(--serif);
    font-size: 13px;
    line-height: 1.5;
    color: var(--ink-2);
  }
  .brand-tooltip p:last-child { margin-bottom: 0; }
  .brand-tooltip strong {
    font-family: var(--font-title);
    font-weight: 600;
    color: var(--ink);
  }
  .brand-tooltip em {
    font-style: italic;
    color: var(--ink);
  }
  /* On narrow viewports the sidebar collapses to a top strip — the
     tooltip would float into nowhere, so just disable it there. */
  @media (max-width: 760px) {
    .brand-tooltip { display: none; }
  }
  /* Legacy brand-mark/-name still defined for any consumer that didn't
     migrate to the image yet. Hidden by default; the new .brand-logo
     replaces them. */
  .brand-mark {
    font-family: var(--font-menu-ja); font-weight: 800; font-size: 38px;
    color: var(--gold-dark); letter-spacing: -0.02em; line-height: 1;
  }
  .brand-name {
    font-family: var(--font-menu-en); font-style: italic; font-size: 18px;
    color: var(--ink-2);
  }
  .brand-sub {
    font-family: var(--hand); font-size: 15px; color: var(--ink-3);
    margin-top: 2px;
  }

  .nav-list { display: flex; flex-direction: column; gap: 4px; list-style: none; padding: 0; margin: 0; }
  .nav-section-label {
    font-family: var(--font-menu-en); font-style: italic; font-size: 12px;
    color: var(--ink-3); letter-spacing: 0.06em; text-transform: lowercase;
    padding: 6px 8px 4px;
  }
  .bookmark {
    position: relative; display: flex; align-items: center; gap: 12px;
    padding: 10px 12px 10px 14px;
    border: none; background: transparent; cursor: pointer;
    color: var(--ink-2); width: 100%; text-align: left;
    font-family: var(--serif); font-size: 15px; line-height: 1.1;
    border-radius: 0 8px 8px 0;
    transition: background .15s, color .15s, transform .15s;
  }
  .bookmark .glyph {
    font-family: var(--font-menu-ja); font-weight: 600; font-size: 18px;
    width: 28px; height: 28px; display: flex; align-items: center; justify-content: center;
    color: var(--gold-dark);
    background: var(--paper-2);
    border-radius: 4px;
    border: 1px solid rgba(141,102,48,0.22);
    flex: 0 0 auto;
  }
  .bookmark .label {
    display: flex; flex-direction: column; gap: 2px;
    flex: 1; min-width: 0;
  }
  .bookmark .label .ja {
    font-family: var(--font-menu-ja); font-size: 14px; color: var(--ink-2); line-height: 1.1;
  }
  .bookmark .label .en {
    font-family: var(--font-menu-en); font-size: 13px; color: var(--ink-3);
    font-style: italic;
  }
  .bookmark { position: relative; }   /* anchor for the active brush */
  .bookmark:hover { background: rgba(180,138,74,0.10); color: var(--ink); }
  /* Active state — subtle paper tint only, no gold left stroke, no
     hairline halo. The brush brush (.active-brush below) is the visual
     anchor that signals "you're here." */
  .bookmark.active {
    background: rgba(180,138,74,0.10);
    color: var(--ink);
  }
  .bookmark.active .label .en { color: var(--ink-2); font-style: normal; }

  /* ─── Active-item brush brush ───────────────────────────────────────
     A hand-painted brush mark anchored to the RIGHT side of the active
     bookmark/cat-item, vertically centered. Allowed to overflow the row
     (and slightly the column) — gives the calligraphy a physical feel
     instead of a clean cell border. One of four brushes is picked at
     random on each render: two are LINE shapes (horizontal/vertical
     brushstrokes, reveal animates right→left), two are CIRCLE shapes
     (ensos, reveal animates counter-clockwise via conic-gradient mask).
     mix-blend-mode: multiply lets the brush's white halo dissolve into
     the sidebar paper, so only the ink registers. */
  .active-brush {
    position: absolute;
    right: -10px;             /* slight horizontal spill, organic feel */
    top: 50%;
    transform: translateY(-50%);
    width: 64px;
    height: 64px;
    object-fit: contain;
    pointer-events: none;
    z-index: 2;               /* above the row's bg tint */
    mix-blend-mode: multiply;
    opacity: 0.85;
  }
  /* Slightly larger brush in the secondary sidebars (cat-item rows are
     a touch shorter, so the brush can run taller to compensate). */
  .cat-item .active-brush { width: 70px; height: 70px; right: -12px; }
  /* Tier-3 brushes (deeper sub-sidebars) center on the sidebar's right
     edge — half inside the row, half spilling into the content area
     beyond. The brushstroke marks the selection clearly without ever
     blocking the row label. position: fixed escapes the sub-sidebar's
     overflow:hidden so the right half is actually visible (the
     ancestor sidebars have overflow-y:auto which forces overflow-x
     to clip too; only fixed positioning escapes). JS — see
     repositionTier3Brushes — sets left/top to track the active row. */
  .cat-item .active-brush.tier-3 {
    position: fixed;
    right: auto;
    top: auto;
    transform: none;
    /* Initial off-screen position so a non-yet-positioned brush doesn't
       flash at (0,0). JS overrides as soon as it can measure. */
    left: -9999px;
  }

  /* ─── Brush-bg-behind-text variant ─────────────────────────────────
     A different tier-3 treatment: instead of the small brushmark
     parked on the row's right edge, a wide brush stroke is painted
     behind the text AND breaks out of the column entirely, trailing
     rightward into the content area. The text crossfades from ink →
     white as the brush wipes in left-to-right. Both layers share the
     same bezier and the same animation-delay (passed via the
     --bg-brush-delay custom property on the parent button), so they
     animate in perfect lock.

     STACKING ─ the brush needs to paint over .main where it overflows
     the sidebar column. .main is in normal flow and .particles-sidebar
     comes BEFORE .main in the DOM, so without explicit z-index .main
     would paint over the sidebar. Lifting particles-sidebar to z:5
     (below .main's :before bg layer if any, above its content) fixes
     this. The lift is on the WHOLE sidebar (not just the active row)
     so the sub-sidebar always paints above .main — visually identical
     to today for non-brush states, since the sub-sidebar has its own
     opaque bg-color and doesn't compete with .main visually.

     The .has-bg-brush class on the parent button:
       - kills the gold-tint active background (the brush IS the tint)
       - lifts the text into a positive z-index above the brush
       - creates a local stacking context so the fixed brush descendant
         and the text spans stack predictably (text z:2 above brush z:1) */
  .particles-sidebar,
  .vocab-books-sidebar { z-index: 5; }
  .cat-item.has-bg-brush {
    background: transparent;
    position: relative;
    /* NO isolation: isolate, NO z-index here. The previous version
       isolated the cat-item's stacking context which meant the brush's
       mix-blend-mode: multiply only had the cat-item's own (transparent)
       background to multiply against — white halos from the brush PNG
       had nothing to dissolve into and stayed visible.
       Removing the isolation lets the multiply reach the sub-sidebar's
       paper backdrop, which is exactly what we want — white pixels go
       transparent against the cream paper, only the dark sumi ink
       registers. Stacking is still well-defined: brush at z:1 inside
       the cat-item, text spans at z:2, both painted inside the
       sub-sidebar's own z:5 stacking context which sits above .main. */
  }
  /* Within the particles-sidebar variant, the cat-item carries its own
     overflow:hidden (to clip the colored accent stripe ::before). The
     fixed-positioned brush escapes that clip naturally because fixed
     descendants ignore ancestor overflow when no transformed ancestor
     intervenes, so no override is needed here. */
  .cat-item.has-bg-brush .cat-glyph,
  .cat-item.has-bg-brush .cat-ja,
  .cat-item.has-bg-brush .cat-en {
    position: relative;
    z-index: 2;
    /* Sumi-e bezier — see the .active-brush-bg rule below for the
       reasoning. Duration is var()-driven so JS can pass a slightly
       drifted value per stroke and the text crossfade tracks the brush
       wipe exactly. */
    animation: brush-bg-text-fade
      var(--bg-brush-dur, 0.42s)
      cubic-bezier(0.92, 0, 0.18, 0.95) both;
    animation-delay: var(--bg-brush-delay, 0s);
  }
  @keyframes brush-bg-text-fade {
    to { color: #fff; }
  }
  /* The brush image — position:fixed so it escapes every overflow
     ancestor (the sub-sidebar's overflow-y:auto would otherwise clip
     the spilling stroke).

     SIZING ─ width, height, left, and top are ALL set by JS each
     frame from the host sub-sidebar's actual rendered width — see
     repositionBgBrushes. The formula:
       brushWidth  = sidebar.width × 1.20      (sidebar + 10% each side)
       brushHeight = brushWidth / image.aspect (preserves native aspect)
       left        = sidebar.left − sidebar.width × 0.10  (10% spill)
       top         = row.top + (row.height − brushHeight) / 2
     This adapts automatically when the brush image changes (new
     aspect → new height) or when the menu width changes (responsive
     grid → re-measured on resize). The CSS values below are just
     sensible initial fallbacks before JS measures.

     object-fit:contain preserves the artist's brush shape regardless
     of how the JS-computed box happens to compare to the image's
     native aspect — no horizontal stretching ever. mix-blend-mode
     multiply dissolves the paper halo so only the sumi pigments
     register on the sidebar paper. */
  .active-brush-bg {
    position: fixed;
    width: 200px;
    height: 100px;
    object-fit: contain;
    object-position: left center;
    pointer-events: none;
    z-index: 1;
    mix-blend-mode: multiply;
    /* Off-screen until JS measures the parent row and positions the
       brush. Without this the brush would flash at (0,0) on the first
       frame before reposition runs. */
    left: -9999px;
    top: -9999px;
    /* Sumi-e bezier — calibrated to match the physics of a real
       brushstroke gesture:
         (0.92, 0)    very slow press-in (brush touching paper, ink
                      starts flowing; the first ~15% of time produces
                      only ~2% of the visible stroke)
         (0.18, 0.95) fast sweep through the middle, with a soft
                      deceleration at the end (the brush lifts gently
                      off the paper rather than stopping dead)
       This curve is informed by ease-in-out-quart and quint and tuned
       further for the "dextrous, fast, accurate" sumi-e gesture. Pairs
       with a short base duration (~420ms) — sumi strokes are quick.

       Duration is var()-driven so JS can apply small per-instance drift
       (±~8%) and every stroke feels its own gesture rather than a
       mechanical loop. The text crossfade uses the SAME var, so brush
       and text stay in perfect lockstep regardless of which instance
       picked which duration. */
    animation: brush-bg-wipe
      var(--bg-brush-dur, 0.42s)
      cubic-bezier(0.92, 0, 0.18, 0.95) both;
  }
  @keyframes brush-bg-wipe {
    from { clip-path: inset(0 100% 0 0); }
    to   { clip-path: inset(0 0 0 0); }
  }

  /* ─── Particle-brush variant ─────────────────────────────────────────
     A smaller cousin of the bg-brush, scoped to the individual particle
     list items in the particles sub-sidebar (は が を に etc.). The
     brush overlaps the colored glyph on the left of the row but does
     NOT cover the romaji + EN labels on the right, so the labels stay
     readable in their default color while only the glyph fades to
     white during the wipe.

     Same stacking strategy as has-bg-brush (z:50 + isolate + transparent
     bg). Same sumi-e bezier, same lockstep duration via --bg-brush-dur.
     The two differences are which texts animate and the brush sizing
     (the JS measures sub-sidebar.width × 0.5 instead of × 1.2). */
  .cat-item.has-particle-brush {
    background: transparent;
    position: relative;
    /* See .has-bg-brush above — same reasoning. No isolation, no
       z-index here, so the brush's multiply blend can reach the
       sub-sidebar's paper backdrop and dissolve any remaining
       white halos in the brush PNG. */
  }
  .cat-item.has-particle-brush .cat-glyph {
    position: relative;
    z-index: 2;
    transform-origin: center center;
    /* Two parallel animations on the glyph, declared via LONGHAND so the
       comma-separated lists pair element-by-element with no ambiguity:
         [0] brush-bg-text-fade — color goes ink/particle → white, same
             bezier + duration as the brush wipe so the glyph whitens
             in lockstep with the stroke painting behind it.
         [1] particle-glyph-emerge — the glyph grows ~22%, tilts 3deg
             counter-clockwise, drifts 2px to the left. Slower than the
             brush (0.6s vs ~0.42s) with a heavy ease-out so the motion
             lingers and settles JUST barely after the brush finishes —
             the glyph "lands" into its new pose right as the stroke
             completes painting.
       Both animations share the cascade delay (from the parent button's
       inline --bg-brush-delay) so they start together; only their end
       times differ. Using longhand instead of the shorthand because
       Chrome/Firefox parse `animation-delay: <single>` AFTER a multi-
       animation shorthand inconsistently — explicit per-animation
       longhand sidesteps that. */
    animation-name:            brush-bg-text-fade,            particle-glyph-emerge;
    animation-duration:        var(--bg-brush-dur, 0.42s),    0.6s;
    animation-timing-function: cubic-bezier(0.92, 0, 0.18, 0.95),
                               cubic-bezier(0.16, 1, 0.3, 1);
    animation-delay:           var(--bg-brush-delay, 0s),     var(--bg-brush-delay, 0s);
    animation-fill-mode:       both,                          both;
  }
  @keyframes particle-glyph-emerge {
    from {
      transform: translate(0, 0) rotate(0deg) scale(1);
    }
    to {
      transform: translate(-2px, 0) rotate(-3deg) scale(1.22);
    }
  }
  /* Labels stay BROWN/INK — explicit color override with !important
     to block any cascading rule (from .has-bg-brush or other shared
     selectors) that might reach them. animation: none also explicit
     so no keyframe sneaks color: #fff in. The romaji label uses ink
     (inherited from .cat-item.active default), the EN gloss uses
     ink-2 (its .active state color). */
  .cat-item.has-particle-brush .cat-ja {
    position: relative;
    z-index: 2;
    animation: none !important;
    color: var(--ink) !important;
  }
  .cat-item.has-particle-brush .cat-en {
    position: relative;
    z-index: 2;
    animation: none !important;
    color: var(--ink-2) !important;
  }
  /* The particle brush image — same fixed-positioning + wipe-animation
     strategy as .active-brush-bg, but JS sizes it to half the sidebar
     width instead of 1.2× full width. */
  .active-brush-particle {
    position: fixed;
    width: 80px;
    height: 80px;
    object-fit: contain;
    object-position: center;
    pointer-events: none;
    z-index: 1;
    mix-blend-mode: multiply;
    left: -9999px;
    top: -9999px;
    animation: brush-bg-wipe
      var(--bg-brush-dur, 0.42s)
      cubic-bezier(0.92, 0, 0.18, 0.95) both;
  }

  /* @property registration lets us animate the conic-gradient stop —
     CSS only interpolates registered custom properties. Without this,
     the circle brush would snap from invisible to fully visible. */
  @property --brush-conic {
    syntax: '<angle>';
    inherits: false;
    initial-value: 360deg;
  }

  /* LINE reveal — clip-path animates from "fully clipped from
     bottom" (only the top edge of the box is visible — 0px high)
     to "no clip" (full image visible). The top edge of the brush
     stays anchored while the visible region grows DOWNWARD — that's
     the top-to-bottom sweep, the way a real sumi-e kanji stroke is
     drawn vertically down the paper. The brush assets are already
     tall (35×65, 40×72, etc.) so this reads as the natural
     drawing direction without rotation gymnastics. */
  .active-brush.is-line {
    /* `both` = forwards + backwards: holds the "from" keyframe during
       any animation-delay (so the brush stays invisible until its
       turn in the cascade), and the "to" keyframe after the animation
       finishes (so the brush stays visible).

       Bezier curve = sumi-e gesture, same family as the bg-brush curve
       (0.92, 0, 0.18, 0.95):
         (0.92, 0)    — very slow press-in. The brush touches paper,
                       ink starts flowing; the first ~15% of time
                       produces only ~2% of the visible stroke.
         (0.18, 0.95) — fast sweep through the middle with a soft
                       deceleration at the end (the brush lifts gently
                       off the paper rather than stopping dead).
       This curve replaces the old ease-out-quart, which felt like a
       quick flick — the new curve reads as a deliberate gesture from
       a confident hand. Pairs with a slightly longer duration (.85s,
       up from .55s) so the slow press-in has room to breathe.

       Duration .85s must match BRUSH_LINE_DUR in JS — the cascade
       math uses it to decide when tier-3 starts. */
    animation: brush-line-sweep .85s cubic-bezier(0.92, 0, 0.18, 0.95) both;
  }
  @keyframes brush-line-sweep {
    /* TOP-TO-BOTTOM reveal in the brush's local coordinate frame.
       inset(<top> <right> <bottom> <left>) — starting with 100% inset
       from the BOTTOM means the visible region is 0 height at the
       TOP edge. As the animation progresses, the bottom inset
       decreases from 100% to 0, so the visible region grows
       DOWNWARD from the top edge. Combined with the natively tall
       brush PNGs (35×65, 40×72, etc.) this reads as a sumi-e stroke
       being painted top-to-bottom, the way a vertical kanji stroke
       is drawn. */
    from { clip-path: inset(0 0 100% 0); }
    to   { clip-path: inset(0 0 0 0); }
  }

  /* CIRCLE reveal — a conic-gradient mask whose visible arc starts at
     12 o'clock and grows COUNTER-CLOCKWISE (toward 9 o'clock first,
     then 6, then 3, closing back at 12). Replicates the way an enso is
     painted in one continuous counter-clockwise sweep. */
  .active-brush.is-circle {
    --brush-conic: 0deg;
    /* `from 60deg` rotates the conic origin 60° clockwise from 12 o'clock,
       so the brush starts/ends in the upper-right (~2 o'clock) — that's
       where a traditional sumi-e enso's opening sits, the gap left by
       the brush as it enters and exits the loop. The visible arc still
       grows counter-clockwise from that anchor through the rest of the
       circle. */
    -webkit-mask-image: conic-gradient(
      from 60deg,
      transparent 0deg,
      transparent calc(360deg - var(--brush-conic)),
      #000 calc(360deg - var(--brush-conic)),
      #000 360deg
    );
            mask-image: conic-gradient(
      from 60deg,
      transparent 0deg,
      transparent calc(360deg - var(--brush-conic)),
      #000 calc(360deg - var(--brush-conic)),
      #000 360deg
    );
    /* Bezier = ease-in-out-cubic (cubic-bezier(0.65, 0, 0.35, 1)) —
       sustained arc motion. The brush winds up slightly, sweeps
       through the middle of the enso at speed, decelerates to close
       the loop. Feels weightier and more deliberate than the line
       sweep, matching how an enso is actually painted (one continuous
       breath, not a flick).

       Duration 1.1s — the sidebar's enso is the ceremonial anchor of
       the cascade, the brushmark that names the whole section. It
       still takes its time relative to the other strokes, but 1.4s
       felt sluggish; 1.1s is a confident slow gesture without
       wandering. Paired with the tier-1 pre-roll in the JS cascade
       (180ms of wait before the stroke begins), the enso feels
       deliberate rather than rushed — the brush lingers, then the
       sub-sidebar lines paint AFTER it completes.

       Duration 1.1s must match BRUSH_CIRCLE_DUR in JS — the cascade
       math uses it to decide when tier-2 starts. */
    animation: brush-circle-sweep 1.1s cubic-bezier(0.65, 0, 0.35, 1) both;
  }
  @keyframes brush-circle-sweep {
    from { --brush-conic: 0deg; }
    to   { --brush-conic: 360deg; }
  }

  /* Respect reduced-motion preference — drop the animation, snap to the
     final state immediately. Both line + circle keep the same visible
     mark at rest. */
  @media (prefers-reduced-motion: reduce) {
    .active-brush.is-line,
    .active-brush.is-circle {
      animation: none;
      clip-path: none;
      -webkit-mask-image: none;
              mask-image: none;
    }
  }

  .sidebar-foot {
    margin-top: auto;
    padding: 12px 10px;
    border-top: 1px dashed rgba(141,102,48,0.3);
    font-family: var(--serif); font-style: italic; font-size: 12px; color: var(--ink-3);
  }
  .streak {
    display: flex; align-items: center; gap: 8px;
    margin-bottom: 6px;
  }
  .streak-dot {
    width: 6px; height: 6px; border-radius: 50%; background: var(--gold);
    box-shadow: 0 0 0 3px rgba(180,138,74,0.20);
  }

  /* ─── shared page chrome ────────────────────────────────────────────── */
  .page-head { margin-bottom: 28px; }
  .page-eyebrow {
    font-family: var(--serif); font-style: italic; font-size: 13px;
    color: var(--ink-3); letter-spacing: 0.04em;
  }
  .page-title-jp {
    font-family: var(--font-title);
    font-weight: 600;
    font-size: clamp(28px, 4vw, 40px);
    color: var(--ink);
    letter-spacing: 0.02em;
    margin: 4px 0 6px;
    line-height: 1.1;
    word-break: keep-all;
    overflow-wrap: break-word;
    line-break: strict;
  }
  /* Furigana inside a page title (e.g. the Pitch page's 抑揚) — small and
     faded so the kanji stays the anchor. Only ruby-bearing titles use it. */
  .page-title-jp rt {
    font-size: 0.36em;
    color: var(--ink-faint);
    font-weight: 400;
  }
  /* Inline connector inside a title — e.g. "ひらがな と カタカナ".
     The と sits smaller and faded so the two anchoring scripts read as
     the main subject. */
  .page-title-jp .title-conj {
    font-size: 0.55em;
    color: var(--ink-3);
    font-weight: 400;
    margin: 0 0.3em;
    vertical-align: 0.12em;
    letter-spacing: 0.05em;
  }
  /* Legacy stacked variant — retained for any title that still uses it. */
  .page-title-jp.page-title-jp-stacked > span { display: block; }
  .page-title-jp.page-title-jp-stacked > span + span { margin-top: 2px; }
  .page-title-en {
    font-family: var(--serif); font-style: italic; font-size: 16px;
    color: var(--ink-2);
  }
  .rule {
    height: 0; margin: 16px 0 24px;
    border-top: 1px solid var(--paper-edge);
    position: relative;
  }
  .rule::after {
    content: ""; position: absolute; left: 0; top: -1px; width: 36px;
    border-top: 2px solid var(--gold);
  }

  /* ─── small ui ──────────────────────────────────────────────────────── */
  .pill {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 4px 10px; border-radius: 999px;
    background: var(--paper-2);
    border: 1px solid rgba(141,102,48,0.25);
    font-family: var(--serif); font-size: 12px; color: var(--ink-2);
    cursor: pointer;
    transition: all .15s;
  }
  .pill:hover { background: var(--gold-soft); color: var(--ink); }
  .pill.active {
    background: var(--gold); color: white; border-color: var(--gold-dark);
  }
  .pill .ja { font-family: var(--serif-jp); }

  .btn {
    display: inline-flex; align-items: center; gap: 8px;
    padding: 8px 16px; border-radius: 8px;
    background: var(--paper-2); color: var(--ink);
    border: 1px solid rgba(141,102,48,0.25);
    font-family: var(--serif); font-size: 14px;
    cursor: pointer; transition: all .15s;
  }
  .btn:hover { background: var(--gold-soft); border-color: var(--gold); }
  .btn.primary { background: var(--gold); color: white; border-color: var(--gold-dark); }
  .btn.primary:hover { background: var(--gold-dark); }
  .btn.ghost { background: transparent; border-style: dashed; color: var(--ink-2); }

  .icon-btn {
    width: 36px; height: 36px; border-radius: 50%;
    background: var(--paper-2); border: 1px solid rgba(141,102,48,0.25);
    color: var(--ink-2); cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
    transition: all .15s;
  }
  .icon-btn:hover { background: var(--gold-soft); color: var(--ink); }
  .icon-btn:disabled { opacity: 0.35; cursor: not-allowed; }

  /* ─── card / panel ──────────────────────────────────────────────────── */
  .panel {
    background: var(--paper-2);
    border: 1px solid rgba(141,102,48,0.22);
    border-radius: 12px;
    padding: 24px;
    box-shadow: 0 1px 0 rgba(255,255,255,0.4) inset, 0 4px 14px var(--paper-shadow);
  }

  /* ─── book / cheatsheet frame ───────────────────────────────────────── */
  .book-frame {
    background: var(--paper-2);
    border: 2px solid var(--gold-soft);
    box-shadow:
      0 0 0 6px var(--paper-2),
      0 0 0 7px var(--gold-soft),
      0 8px 24px rgba(100,72,30,0.18);
    border-radius: 4px;
    padding: 36px clamp(20px, 4vw, 56px) 40px;
    position: relative;
  }
  .book-frame::before, .book-frame::after,
  .book-frame > .corner-tl, .book-frame > .corner-tr {
    content: ""; position: absolute; width: 14px; height: 14px;
    border: 2px solid var(--gold);
    background: var(--paper-2);
    border-radius: 50%;
  }
  .book-frame::before { top: -8px; left: -8px; }
  .book-frame::after  { bottom: -8px; right: -8px; }
  .book-frame > .corner-tl { top: -8px; right: -8px; }
  .book-frame > .corner-tr { bottom: -8px; left: -8px; }

  .book-title {
    text-align: center;
    font-family: var(--serif-jp); font-weight: 700;
    font-size: clamp(22px, 3vw, 32px);
    color: var(--ink); letter-spacing: 0.06em;
    padding-bottom: 18px;
    border-bottom: 1px solid var(--gold-soft);
    margin-bottom: 24px;
    position: relative;
    word-break: keep-all;
    overflow-wrap: break-word;
    line-break: strict;
  }
  .book-title .particle { color: var(--ink-3); font-weight: 400; font-size: 0.8em; margin: 0 6px; }

  .sheet-image {
    width: 100%;
    margin: 0 auto 24px;
    border-radius: 4px;
    border: 1px solid var(--paper-edge);
    background: var(--paper-2);
    position: relative;
    overflow: hidden;
  }
  image-slot {
    border-radius: 4px;
  }

  .vocab-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 4px 32px;
  }
  @media (max-width: 640px) { .vocab-grid { grid-template-columns: 1fr; } }
  .vocab-row {
    display: grid;
    grid-template-columns: 28px 1fr;
    align-items: baseline;
    gap: 4px 10px;
    padding: 8px 4px;
    border-bottom: 1px dashed rgba(141,102,48,0.18);
    cursor: pointer;
    border-radius: 4px;
    transition: background .12s;
  }
  .vocab-row .body {
    display: flex; flex-wrap: wrap; align-items: baseline;
    gap: 2px 8px;
    min-width: 0;
  }
  .vocab-row:hover { background: rgba(180,138,74,0.10); }
  .vocab-row .num {
    font-family: var(--serif); font-size: 14px; color: var(--ink-3);
    font-weight: 600;
  }
  .vocab-row .kanji {
    font-family: var(--serif-jp); font-size: 18px; color: var(--ink);
    font-weight: 500;
  }
  .vocab-row .kana {
    font-family: var(--serif-jp); font-size: 13px; color: var(--ink-2);
    /* Treat the parenthesized hiragana as one atomic unit. Without this the
       browser may break mid-（kana）— "ベッド（べっ\nど）" — which looks
       broken. With nowrap the entire span either fits on the kanji line or
       flex-wraps to its own line as a whole. */
    white-space: nowrap;
  }
  .vocab-row .en {
    font-family: var(--serif); font-style: italic; font-size: 12px;
    color: var(--ink-3);
    width: 100%;
    margin-top: -2px;
  }
  body.no-english .vocab-row .en { visibility: hidden; }
  body.no-english .flash-en { visibility: hidden; }
  body.no-english .sentence .en { visibility: hidden; }
  body.no-english .reveal-en::after { content: "(tap to reveal)"; visibility: visible !important; opacity: .5; font-family: var(--serif); font-size: 11px; }

  /* ─── stepper / book navigation ──────────────────────────────────────
     Full-width horizontal step indicator. Each step is its own clickable
     column with a numbered marker + JP/EN title. Connector lines link
     consecutive markers; they color gold for the steps you've reached
     (left of and including the active step), muted gray for upcoming.
     Replaced the small-dot pager — titled steps tell you what each
     destination is before you click it. */
  .stepper {
    /* Transparent: no paper backdrop, no bottom rule. Lets the section
       background (e.g. eating-out's painterly bg) breathe through and
       the stepper just floats as markers + labels over whatever is
       behind it. Sticky positioning dropped — without a backdrop, the
       stepper would otherwise overlap scrolled content unreadably.
       It scrolls away naturally with the page, which is fine for a
       3-step nav the user only consults once. */
    margin: 0 0 22px;
    display: grid;
    grid-template-columns: repeat(var(--steps, 1), minmax(0, 1fr));
    gap: 0;
    padding: 12px 10px 14px;
    background: transparent;
    border-bottom: none;
  }
  .step {
    position: relative;
    background: transparent;
    border: none;
    cursor: pointer;
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 0 6px;
    font: inherit;
    color: var(--ink-4);
    transition: color .15s;
    min-width: 0;
  }
  .step:hover { color: var(--ink-2); }
  .step:focus-visible {
    outline: 2px solid var(--gold);
    outline-offset: 4px;
    border-radius: 4px;
  }
  .step.is-done   { color: var(--ink-3); }
  .step.is-active { color: var(--ink); }
  /* Numbered marker (the circle) */
  .step-marker {
    display: flex; align-items: center; justify-content: center;
    width: 30px; height: 30px;
    border-radius: 50%;
    background: var(--paper-2);
    border: 1.5px solid var(--paper-edge);
    font-family: var(--serif);
    font-weight: 600;
    font-size: 13px;
    color: var(--ink-3);
    margin-bottom: 8px;
    transition: background .15s, border-color .15s, color .15s, box-shadow .15s;
    position: relative;
    z-index: 2;
    flex-shrink: 0;
  }
  .step:hover .step-marker {
    border-color: var(--gold);
    color: var(--ink-2);
  }
  .step.is-done .step-marker {
    background: var(--gold-soft);
    border-color: var(--gold);
    color: var(--gold-dark);
  }
  .step.is-active .step-marker {
    background: var(--gold);
    border-color: var(--gold-dark);
    color: var(--paper);
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--gold-soft) 60%, transparent);
  }
  /* Connector line — lives behind the marker, spans from the previous
     marker's center to this marker's center. Hidden on the first step. */
  .step:not(:first-child)::before {
    content: '';
    position: absolute;
    top: 14px; /* center of the 30px marker */
    right: 50%;
    width: 100%;
    height: 2px;
    background: var(--paper-edge);
    z-index: 0;
    transform: translateY(-1px);
    transition: background .15s;
  }
  /* The connector to a done/active step is gold (progress visualization). */
  .step.is-done:not(:first-child)::before,
  .step.is-active:not(:first-child)::before {
    background: var(--gold);
  }
  /* Title block under the marker — JP on top, EN italic underneath. */
  .step-label {
    display: flex; flex-direction: column;
    align-items: center;
    gap: 1px;
    text-align: center;
    line-height: 1.2;
    min-width: 0;
    width: 100%;
  }
  .step-ja {
    font-family: var(--serif-jp);
    font-size: 13px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    width: 100%;
  }
  .step-en {
    font-family: var(--serif); font-style: italic;
    font-size: 10px;
    color: var(--ink-4);
    letter-spacing: 0.02em;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    width: 100%;
  }
  .step.is-active .step-ja { font-weight: 600; }
  .step.is-active .step-en { color: var(--ink-3); }
  /* Narrow screens — hide EN sub-label, shrink fonts. */
  @media (max-width: 560px) {
    .stepper { padding: 10px 6px 12px; }
    .step { padding: 0 4px; }
    .step-marker { width: 26px; height: 26px; font-size: 12px; }
    .step:not(:first-child)::before { top: 12px; }
    .step-ja { font-size: 11px; }
    .step-en { display: none; }
  }

  /* ─── usage list ────────────────────────────────────────────────────── */
  .usage-grid {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 14px;
  }
  .usage-card {
    background: var(--paper-2);
    border: 1px solid rgba(141,102,48,0.22);
    border-radius: 8px;
    padding: 14px 16px;
    transition: all .15s;
    cursor: pointer;
  }
  .usage-card:hover { border-color: var(--gold); background: var(--paper); transform: translateY(-1px); }
  .usage-card .ja {
    font-family: var(--serif-jp); font-size: 19px; color: var(--ink);
    font-weight: 500;
  }
  .usage-card .kana {
    font-family: var(--serif-jp); font-size: 12px; color: var(--ink-3);
    margin-top: 2px;
  }
  .usage-card .en {
    font-family: var(--serif); font-style: italic; font-size: 13px;
    color: var(--ink-2); margin-top: 8px;
    padding-top: 8px;
    border-top: 1px dashed rgba(141,102,48,0.18);
  }

  /* ─── sentences ─────────────────────────────────────────────────────── */
  .sentence {
    padding: 18px 0;
    border-bottom: 1px solid var(--paper-edge);
    display: grid;
    grid-template-columns: auto 1fr auto;
    gap: 16px;
    align-items: center;
  }
  .sentence:last-child { border-bottom: none; }
  .sentence .level-tag {
    font-family: var(--serif); font-size: 11px; color: var(--gold-dark);
    background: var(--gold-soft);
    padding: 2px 8px; border-radius: 4px;
    letter-spacing: 0.04em;
    white-space: nowrap;
  }
  .sentence .body { min-width: 0; }
  .sentence .ja {
    font-family: var(--serif-jp); font-size: 20px; color: var(--ink);
    line-height: 1.6;
    font-weight: 400;
  }
  .sentence .en {
    font-family: var(--serif); font-style: italic; font-size: 14px;
    color: var(--ink-3);
    margin-top: 6px;
    line-height: 1.4;
  }
  .sentence .audio { color: var(--ink-3); }

  /* ─── Jougo explainer page ─────────────────────────────────────────── */
  .explainer-body {
    font-family: var(--serif);
    font-size: 15px;
    color: var(--ink-2);
    line-height: 1.6;
    max-width: 780px;
    margin-top: 18px;
  }
  .explainer-body .jougo-section {
    margin-bottom: 8px;
    padding-bottom: 22px;
    border-bottom: 1px dashed var(--paper-edge);
    /* flow-root establishes a block formatting context so floated
       figures inside the section (.jougo-noma-figure) are contained
       and can't bleed into the next section. */
    display: flow-root;
  }
  .explainer-body .jougo-section:last-child { border-bottom: none; }
  .explainer-body h3 {
    font-family: var(--serif-jp);
    font-size: 22px;
    color: var(--ink);
    font-weight: 600;
    margin: 30px 0 12px;
    letter-spacing: 0.02em;
  }
  .explainer-body h4 {
    font-family: var(--serif-jp);
    font-size: 16px;
    color: var(--ink-2);
    font-weight: 600;
    margin: 20px 0 8px;
  }
  .explainer-body p { margin: 0 0 12px; }
  .explainer-body ul {
    margin: 0 0 14px;
    padding-left: 22px;
  }
  .explainer-body li {
    margin-bottom: 6px;
  }
  .explainer-body strong { color: var(--ink); }
  .explainer-body em { font-style: italic; color: var(--ink-2); }
  .explainer-body .ja {
    font-family: var(--serif-jp);
    color: var(--ink);
  }
  .explainer-body code,
  .explainer-body .glyph-inline {
    font-family: var(--serif-jp);
    color: var(--gold-dark);
    font-weight: 600;
    font-size: 1.08em;
    background: transparent;
    padding: 0;
  }
  .explainer-body a {
    color: var(--gold-dark);
    text-decoration: underline;
    text-decoration-color: var(--gold-soft);
    text-underline-offset: 2px;
    transition: text-decoration-color .15s;
  }
  .explainer-body a:hover { text-decoration-color: var(--gold); }

  /* Editorial cover figure — sits inline in the explainer body to break
     up long prose with a visual rest. Soft paper-2 fill + gold-soft
     halo + drop shadow matches the flashcard chrome so it reads as a
     framed plate from the same family of editorial cards. The width
     respects the body's reading column so the figure doesn't blow out
     wider than the surrounding paragraphs. */
  .explainer-body .jougo-cover {
    margin: 18px auto 22px;
    padding: 0;
    max-width: 560px;
    background: var(--paper-2);
    border-radius: 8px;
    box-shadow:
      0 0 0 1px var(--paper-edge),
      0 0 0 5px var(--paper-2),
      0 0 0 6px var(--gold-soft),
      0 16px 36px rgba(60, 40, 10, 0.14);
    overflow: hidden;
  }
  .explainer-body .jougo-cover img {
    display: block;
    width: 100%;
    height: auto;
    border-radius: 8px;
  }

  /* Smaller inline figure — used to illustrate a single concept inside
     a section's prose (e.g. the 々 hand-to-ear "say it again" image at
     the start of section B). Floats right on wider viewports so the
     paragraph wraps around it; stacks centered on narrow viewports. */
  .explainer-body .jougo-noma-figure {
    float: right;
    width: clamp(160px, 26vw, 240px);
    margin: 4px 0 14px 22px;
    padding: 0;
    background: var(--paper-2);
    border-radius: 8px;
    box-shadow:
      0 0 0 1px var(--paper-edge),
      0 0 0 4px var(--paper-2),
      0 0 0 5px var(--gold-soft),
      0 10px 24px rgba(60, 40, 10, 0.12);
    overflow: hidden;
  }
  .explainer-body .jougo-noma-figure img {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 1 / 1;
    object-fit: cover;
  }
  .explainer-body .jougo-noma-figure figcaption {
    font-family: var(--serif); font-size: 11px;
    font-style: italic;
    color: var(--ink-3);
    line-height: 1.45;
    padding: 8px 12px 10px;
    text-align: left;
  }
  .explainer-body .jougo-noma-figure figcaption em {
    color: var(--ink-2);
  }
  @media (max-width: 600px) {
    .explainer-body .jougo-noma-figure {
      float: none;
      width: 100%;
      max-width: 280px;
      margin: 6px auto 16px;
    }
  }

  /* Tile grids inside the explainer (group examples). */
  .jougo-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    gap: 10px;
    margin: 14px 0 18px;
  }
  .jougo-tile {
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 6px;
    padding: 14px 10px 12px;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    text-align: center;
    transition: border-color .15s, transform .15s;
  }
  .jougo-tile:hover {
    border-color: var(--gold);
    transform: translateY(-2px);
  }
  /* Illustrated variant — first child is an <img> that sits flush
     against the top edge of the tile. The tile's TOP corners are
     squared off so the image touches the card with a clean horizontal
     seam (no rounded-vs-square mismatch). The image carries no border
     and no rounding of its own. Bottom corners of the tile stay
     rounded for visual continuity with the rest of the grid. The
     tile's top padding drops to zero since the image takes that
     space; the kanji glyph gets a margin-top so it still breathes. */
  .jougo-tile.jougo-tile-illus {
    padding-top: 0;
    border-top-left-radius: 0;
    border-top-right-radius: 0;
    overflow: hidden;          /* clip image to the tile's bottom corners */
  }
  .jougo-tile-img {
    display: block;
    width: calc(100% + 22px);  /* escape the tile's L/R padding (10px each + 1px border) */
    margin: 0 -11px;
    height: auto;
    aspect-ratio: 4 / 3;
    object-fit: cover;
    object-position: center;
    border: none;              /* no stroke per spec */
    border-radius: 0;          /* square so it meets the squared-off tile top */
  }
  .jougo-tile.jougo-tile-illus .jougo-tile-glyph {
    margin-top: 12px;          /* breathing room between image and glyph */
  }
  /* Illustrated tiles act as buttons — open a modal with the larger
     illustration + an example sentence on click. Cursor + subtle lift on
     hover signal the affordance; keyboard users get a focus ring. */
  .jougo-tile[data-jougo-modal] { cursor: pointer; }
  .jougo-tile[data-jougo-modal]:focus-visible {
    outline: 2px solid var(--gold);
    outline-offset: 3px;
  }

  /* ─── Jougo example modal ─────────────────────────────────────────
     A native <dialog> opened from the illustrated jōgo tiles. Shows
     the illustration enlarged on top, then the glyph + reading + gloss
     stack, then one strong example sentence (JP big serif, EN italic
     underneath). Paper texture + gold-soft halo match the editorial
     flashcard family so the modal reads as part of the same world. */
  dialog.jougo-modal {
    border: none;
    padding: 0;
    background: transparent;
    max-width: 640px;
    width: calc(100% - 32px);
    max-height: 90vh;
    overflow: visible;
    color: var(--ink);
  }
  dialog.jougo-modal::backdrop {
    background: rgba(60, 40, 10, 0.42);
    backdrop-filter: blur(2px);
  }
  .jougo-modal-card {
    background: var(--paper-2);
    border-radius: 10px;
    box-shadow:
      0 0 0 1px var(--paper-edge),
      0 0 0 5px var(--paper-2),
      0 0 0 6px var(--gold-soft),
      0 18px 48px rgba(60, 40, 10, 0.28);
    overflow: hidden;
    position: relative;
    max-height: 90vh;
    display: flex; flex-direction: column;
  }
  .jougo-modal-image-wrap {
    width: 100%;
    aspect-ratio: 16 / 9;
    background: var(--paper);
    overflow: hidden;
    flex: 0 0 auto;
  }
  .jougo-modal-image {
    display: block;
    width: 100%; height: 100%;
    object-fit: cover; object-position: center;
  }
  .jougo-modal-body {
    padding: 22px 26px 26px;
    display: flex; flex-direction: column;
    gap: 10px;
    overflow: auto;
  }
  .jougo-modal-glyph {
    font-family: var(--font-title);
    font-weight: 700;
    font-size: clamp(36px, 5vw, 52px);
    line-height: 1;
    color: var(--ink);
    letter-spacing: 0.02em;
  }
  .jougo-modal-reading {
    font-family: var(--serif-jp);
    font-size: 18px;
    color: var(--ink-2);
    margin-top: -2px;
  }
  .jougo-modal-en {
    font-family: var(--serif); font-style: italic;
    font-size: 14px;
    color: var(--ink-3);
    margin-bottom: 6px;
  }
  .jougo-modal-example {
    margin-top: 6px;
    padding-top: 14px;
    border-top: 1px dashed var(--paper-edge);
    display: flex; flex-direction: column;
    gap: 6px;
  }
  .jougo-modal-example-eyebrow {
    font-family: var(--serif); font-style: italic;
    font-size: 11px;
    color: var(--ink-4);
    letter-spacing: 0.06em;
    text-transform: uppercase;
  }
  /* JP sentence + TTS speaker side-by-side. Speaker is right-aligned
     and locked to the top so multi-line sentences keep their start
     aligned with the icon, instead of the icon drifting to mid-text. */
  .jougo-modal-example-jp-row {
    display: flex;
    align-items: flex-start;
    gap: 10px;
  }
  .jougo-modal-example .jp {
    flex: 1 1 auto;
    font-family: var(--serif-jp);
    font-size: 18px;
    color: var(--ink);
    line-height: 1.55;
    margin: 0;
  }
  .jougo-modal-tts {
    flex: 0 0 auto;
    margin-top: 2px;          /* nudge down so the icon aligns with the JP cap-height */
  }
  .jougo-modal-example .en {
    font-family: var(--serif); font-style: italic;
    font-size: 14px;
    color: var(--ink-3);
    line-height: 1.5;
    margin: 0;
  }
  .jougo-modal-close {
    position: absolute;
    top: 10px; right: 10px;
    width: 32px; height: 32px;
    display: flex; align-items: center; justify-content: center;
    border-radius: 50%;
    background: rgba(252, 248, 240, 0.92);
    border: 1px solid var(--paper-edge);
    color: var(--ink-2);
    font-size: 18px; line-height: 1;
    cursor: pointer;
    box-shadow: 0 2px 6px rgba(60, 40, 10, 0.16);
    transition: background .15s, color .15s, transform .12s;
    z-index: 1;
  }
  .jougo-modal-close:hover {
    background: var(--paper);
    color: var(--ink);
    transform: scale(1.06);
  }
  @media (max-width: 480px) {
    .jougo-modal-image-wrap { aspect-ratio: 4 / 3; }
    .jougo-modal-body { padding: 18px 18px 22px; }
  }
  .jougo-tile-glyph {
    font-family: var(--serif-jp);
    font-size: 24px;
    font-weight: 600;
    color: var(--ink);
    line-height: 1.1;
  }
  .jougo-tile-reading {
    font-family: var(--serif-jp);
    font-size: 12px;
    color: var(--ink-3);
  }
  .jougo-tile-en {
    font-family: var(--serif);
    font-style: italic;
    font-size: 11px;
    color: var(--ink-3);
    line-height: 1.3;
  }

  /* ─── Jougo overview page — three editorial zones ────────────────── */
  /* Used by vocab/jougo/onomatopoeia + vocab/jougo/common-jougo. Same
     .explainer-body chrome inherited from the intro page (typography,
     dashed section dividers, max-width body lane). Each of the three
     zones (gallery / daily / sentences) overrides one or two rules to
     get its distinct rhythm.

     Zone headings carry an italic EN sub-label so the JP chapter
     numbering ("A · 形") still scans for English readers without
     drifting into the body. */
  .zone-label-en {
    font-family: var(--serif); font-style: italic;
    font-weight: 400;
    font-size: 14px;
    color: var(--ink-3);
    letter-spacing: 0;
    margin-left: 4px;
  }

  /* Zone A · gallery — uses the existing .jougo-tile-illus from the
     intro page, no extra rules. The tile chrome ALREADY handles the
     squared top corners + image-flush-to-top + click-to-modal. The
     no-image fallback variant is .jougo-tile-placeholder: paper-2 fill,
     centered giant glyph instead of a 4:3 image, same overall card
     height so the grid stays uniform. */
  .jougo-tile.jougo-tile-placeholder {
    /* Match the illustrated variant's structural moves (square top,
       overflow:hidden, no top padding) so placeholder + illustrated
       tiles share the same outer box and the grid alignment holds. */
    padding-top: 0;
    border-top-left-radius: 0;
    border-top-right-radius: 0;
    overflow: hidden;
    cursor: pointer;
  }
  .jougo-tile-glyph-large {
    /* Replaces the image-region on placeholder tiles. Same 4:3 footprint
       so the gallery grid stays uniform. Paper background is a touch
       deeper than the card body so the glyph still reads as "the
       illustration" rather than as a duplicated label. */
    display: flex; align-items: center; justify-content: center;
    width: calc(100% + 22px);
    margin: 0 -11px;
    aspect-ratio: 4 / 3;
    background: var(--paper);
    color: var(--ink);
    font-family: var(--font-title);
    font-weight: 600;
    font-size: clamp(28px, 6vw, 44px);
    letter-spacing: 0.02em;
    line-height: 1.05;
    text-align: center;
    border-top: none;
  }
  .jougo-tile.jougo-tile-placeholder .jougo-tile-glyph {
    margin-top: 12px;
  }

  /* Zone B · daily-use collocations — text-only cards in a tight grid.
     No images on purpose; the rhythm change is what tells the reader
     "you're in a new section." Each card is a self-contained letterpress
     stack: small kanji eyebrow → JP phrase → kana → EN gloss. Cards
     read top-to-bottom; the grid is roughly square so the eye flows
     left-to-right, top-to-bottom across the rows. */
  .jougo-daily-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 14px;
    margin: 16px 0 6px;
  }
  .jougo-daily-card {
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 6px;
    padding: 14px 16px 14px 16px;
    display: flex; flex-direction: column;
    /* Gold-soft left-rule echoes the "this card is anchored to its
       word" idea without competing with the eyebrow glyph above it.
       Subtle enough to read as a typographic ornament, not a chrome
       element. */
    border-left: 2px solid var(--gold-soft);
    transition: border-left-color .15s, background .15s;
  }
  .jougo-daily-card:hover {
    border-left-color: var(--gold);
    background: var(--paper);
  }
  .jougo-daily-eyebrow {
    font-family: var(--serif-jp); font-weight: 600;
    font-size: 13px;
    color: var(--gold-dark);
    letter-spacing: 0.04em;
    line-height: 1.1;
    margin-bottom: 6px;
  }
  .jougo-daily-phrase {
    font-family: var(--serif-jp);
    font-weight: 600;
    font-size: 18px;
    color: var(--ink);
    line-height: 1.4;
    margin: 0;
  }
  .jougo-daily-kana {
    font-family: var(--serif-jp);
    font-size: 12px;
    color: var(--ink-4);
    letter-spacing: 0.02em;
    line-height: 1.3;
    margin: 4px 0 0;
  }
  .jougo-daily-en {
    font-family: var(--serif); font-style: italic;
    font-size: 13px;
    color: var(--ink-3);
    line-height: 1.45;
    margin: 8px 0 0;
  }

  /* Zone C · sentences — wide letterpressed stripes. One sentence per
     row, full body width, generous line-height. Different rhythm from
     Zone B's tight cards: stripes flow long-format like newspaper
     paragraphs, cards flow as compact tiles. The eye reads each zone
     differently because each zone's geometry IS different. */
  .jougo-sentences-list {
    display: flex; flex-direction: column;
    gap: 14px;
    margin: 16px 0 6px;
  }
  .jougo-sentence-stripe {
    background: var(--paper);
    border-radius: 6px;
    padding: 12px 16px 14px;
    border-left: 2px solid var(--gold-soft);
    /* Subtle pressed-into-paper shadow — keeps the stripe distinct
       from the page background without competing with the gallery
       tiles' gold halo. */
    box-shadow: 0 1px 0 var(--paper-edge);
  }
  .jougo-sentence-jp-row {
    display: flex;
    align-items: flex-start;
    gap: 12px;
  }
  .jougo-sentence-stripe .jp {
    flex: 1 1 auto;
    font-family: var(--serif-jp);
    font-size: 16px;
    color: var(--ink);
    line-height: 1.65;
    margin: 0;
  }
  .jougo-sentence-meta {
    flex: 0 0 auto;
    display: flex; align-items: center;
    gap: 8px;
    margin-top: 2px;
  }
  .jougo-sentence-level {
    font-family: var(--serif); font-style: italic;
    font-size: 11px;
    color: var(--ink-4);
    letter-spacing: 0.06em;
    padding: 2px 8px;
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 999px;
  }
  .jougo-sentence-tts { flex-shrink: 0; }
  .jougo-sentence-stripe .en {
    font-family: var(--serif); font-style: italic;
    font-size: 13px;
    color: var(--ink-3);
    line-height: 1.5;
    margin: 6px 0 0;
  }

  /* Mobile: tighten everything, drop the level chip's flex-grab so it
     wraps under the sentence on narrow viewports rather than stealing
     width from the JP. */
  @media (max-width: 520px) {
    .jougo-daily-grid { grid-template-columns: 1fr; }
    .jougo-sentence-meta { flex-direction: row-reverse; }
    .jougo-sentence-level { display: none; }
  }

  /* ─── Jougo flashcards aggregator grid ─────────────────────────────── */
  .jougo-flashcards-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: 12px;
    margin-top: 18px;
  }
  .jougo-fc {
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 8px;
    padding: 18px 14px;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    cursor: pointer;
    transition: all .15s;
    text-align: center;
    font-family: inherit;
    color: inherit;
  }
  .jougo-fc:hover {
    border-color: var(--gold);
    transform: translateY(-2px);
    box-shadow: 0 4px 12px rgba(141,102,48,0.10);
  }
  /* Each tile's image well — 1:1 square so the grid stays uniform
     regardless of which words have art on disk. Same paper-2 placeholder
     pattern as the editorial flashcard's image-slot: filled shows the
     illustration, empty shows a flat-color block at the same height. */
  .jougo-fc-image {
    width: 100%;
    aspect-ratio: 1 / 1;
    border-radius: 6px;
    overflow: hidden;
    background: transparent;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .jougo-fc-image image-slot {
    display: block;
    width: 100%;
    height: 100%;
    border-radius: 6px;
    overflow: hidden;
  }
  .jougo-fc-image:has(image-slot[readonly]:not([data-filled])) {
    background: var(--paper);
  }
  .jougo-fc-glyph {
    font-family: var(--serif-jp);
    font-size: 30px;
    font-weight: 600;
    color: var(--ink);
    line-height: 1.05;
  }
  .jougo-fc-reading {
    font-family: var(--serif-jp);
    font-size: 13px;
    color: var(--ink-3);
  }
  .jougo-fc-en {
    font-family: var(--serif);
    font-style: italic;
    font-size: 12px;
    color: var(--ink-3);
    line-height: 1.3;
  }

  /* ─── Test cards (new editorial flashcard design) ─────────────────────
     Two-column layout with a vertical hairline divider. The left column
     holds a square illustration + the brush-style glyph + small readings.
     The right column is a typographic info pane: meaning, examples
     (kanji compound · ROMAJI ········ gloss), Heisig + strokes meta,
     and related-kanji chips. */
  .testcard-tabs {
    display: flex; flex-wrap: wrap;
    gap: 6px;
    margin: 0 0 24px;
    padding-bottom: 12px;
    border-bottom: 1px solid var(--paper-edge);
  }
  .testcard-tab {
    display: inline-flex; align-items: baseline;
    gap: 8px;
    padding: 8px 14px;
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 6px;
    cursor: pointer;
    transition: background .12s, border-color .12s, transform .15s;
  }
  .testcard-tab:hover {
    border-color: var(--gold-soft);
    transform: translateY(-1px);
  }
  .testcard-tab.is-active {
    background: var(--paper);
    border-color: var(--gold-dark);
    box-shadow: 0 0 0 1px var(--gold-dark);
  }
  .testcard-tab-kanji {
    font-family: var(--font-title); font-weight: 600;
    font-size: 22px; line-height: 1;
    color: var(--ink);
  }
  .testcard-tab-kun {
    font-family: var(--serif-jp); font-size: 12px;
    color: var(--ink-3);
    line-height: 1;
  }

  /* Editorial layout — viewport-perfect proportional structure:
       main-inner = 100vh - page padding
         ├── class-strip   (natural height)
         ├── view-toggle   (natural height — view + font dropdowns)
         └── flash-layout  (flex: 1 — fills the remainder)
              ├── flash-rail-left   ( 絵 · 漢字 · よみかた  — vertical)
              ├── flash-center      (card, centered)
              ├── flash-rail-inner  (単語札 caption + vertical progress)
              └── flash-rail-right  (英 / — translations + colorize)
     The 4-column grid keeps every chrome element in its OWN column so
     nothing stacks above or below the card vertically. The card hogs
     all the vertical space; rails are narrow and centered alongside. */
  /* Tighten the .main page padding when the single-card editorial layout
     is showing. The standard --pad-page (56px) is the right margin for a
     scrolling content surface, but the editorial flashcard is a single
     focus surface — it benefits from a tighter frame so the vertical
     title rail hugs the flash-sidebar (aesthetically close, not pressed
     against), and the card claims an extra ~64px of vertical room that
     would otherwise be wasted on top/bottom padding. We shadow
     --pad-page locally so the .main-inner height calc below stays
     correct without a duplicate constant. */
  .main:has(.flash-layout-editorial) {
    /* --pad-page-v is used by the .main-inner height calc; we set it to
       the vertical padding only (top + bottom = 16+16 = 32) so the inner
       fills the viewport exactly. Horizontal padding is asymmetric: tight
       on the left so the vertical title rail sits a finger's-width from
       the sidebar, slightly looser on the right. */
    --pad-page: 16px;
    padding: 16px 20px 16px 12px;
  }
  .main-inner:has(.flash-layout-editorial) {
    height: calc(100vh - var(--pad-page) * 2);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    /* Override the global .main-inner max-width + margin: 0 auto. The
       editorial flash layout has its own internal rail-grid that handles
       spacing — letting the inner fill .main's full content width pulls
       the vertical title rail close to the flash-sidebar (just past the
       tightened 20px page-padding) and hands the card more horizontal
       breathing room. */
    max-width: none;
    margin: 0;
  }
  .flash-layout.flash-layout-editorial {
    /* Inherit the 4-column rail grid from .flash-layout. Only the
       sizing behavior changes — editorial flexes to fill what's left
       of the viewport below the class-strip and view-toggle row. */
    align-items: stretch;
    flex: 1;                   /* fills remaining vertical space */
    min-height: 0;             /* allow shrinking when card grows */
  }
  .flash-layout.flash-layout-editorial .flash-nav { display: none; }
  .flash-layout.flash-layout-editorial .flash-center {
    gap: 0;
    justify-content: center;
    align-items: center;
    height: 100%;
    min-height: 0;
  }
  .flash-layout.flash-layout-editorial .flash-deck-editorial {
    max-width: 1080px;
    width: 100%;
  }
  /* Inner rail in editorial mode — 単語札 caption stacked above the
     vertical progress bar. Both are centered in the column so the
     caption sits roughly mid-card-height with the progress just below. */
  .flash-layout.flash-layout-editorial .flash-rail-inner {
    gap: 14px;
  }
  .flash-layout.flash-layout-editorial .flash-progress-vert {
    width: 2px;
    height: clamp(140px, 30vh, 260px);
  }
  /* In editorial mode, compress the above-card chrome aggressively so the
     card claims every spare pixel of vertical real estate. The view
     toggle, font pickers, and bg picker still need to exist (the user
     reaches for them often) but their row shouldn't weigh as much as the
     card it sits above. Smaller buttons, tighter padding, tighter gap. */
  .main:has(.flash-layout-editorial) .flash-top-row {
    margin: 0 auto 6px;
    gap: 10px;
  }
  .main:has(.flash-layout-editorial) .flash-view-btn {
    padding: 4px 10px;
    font-size: 12px;
  }
  .main:has(.flash-layout-editorial) .flash-view-btn svg {
    width: 12px; height: 12px;
  }
  .main:has(.flash-layout-editorial) .flash-top-fontpicker-label {
    font-size: 10px;
  }
  .main:has(.flash-layout-editorial) .flash-bg-select {
    font-size: 11px;
    padding: 3px 20px 3px 8px;
  }

  /* Wrapping frame — sits inside .flash-card so the editorial layout
     inherits the same framed-card chrome (paper-2 bg, gold-soft halo,
     drop shadow) as the classic flashcard. Tightened padding so the
     content gets more room. The --testcard-text-font variable scopes
     a per-card JP font override (set inline from APP.flashTextFont). */
  .flash-card.testcard-frame {
    --testcard-text-font: var(--serif-jp);
    padding: 22px 32px 18px;
    display: flex;
    flex-direction: column;
    gap: 12px;
  }
  /* Body grid — both columns stretch to the same height so the divider
     (drawn as the right column\'s left border) extends from top of the
     card body to just above the footer hairline. */
  .testcard {
    display: grid;
    grid-template-columns: minmax(200px, 320px) 1fr;
    gap: 44px;
    align-items: stretch;
    position: relative;
  }
  .testcard::before {
    content: "";
    position: absolute;
    left: calc(minmax(280px, 420px) + 28px);  /* fallback; the right-col rule below paints it */
    top: 0; bottom: 0;
  }
  .testcard-left {
    display: flex; flex-direction: column;
    align-items: center;
    justify-content: center;     /* vertically center image + glyph + readings */
    gap: 16px;
  }
  /* The image well reserves a fixed square slot. Wide images (e.g. the
     "roof / crown / cap" triptych) get vertically centered inside that
     square so they sit at the optical center of the column — the kanji
     glyph below stays anchored, and short/tall images don\'t shove the
     glyph up or down. A soft paper-2 background makes the 4px rounded
     corners visible even when the image doesn\'t fill the slot (e.g.
     wide images with letterbox space above/below). */
  .testcard-image {
    width: 100%;
    aspect-ratio: 1 / 1;
    background: transparent;
    border: none;
    border-radius: 8px;
    overflow: hidden;
    display: flex; align-items: center; justify-content: center;
  }
  .testcard-image image-slot {
    display: block;
    width: 100%; height: 100%;
    border-radius: 8px;
    overflow: hidden;
  }
  /* No-image flashcards — when the slot is read-only AND no image has
     been uploaded, the inner image-slot hides itself (display:none
     in its shadow DOM), but the outer .testcard-image keeps its
     1:1 aspect-ratio square. We paint it the same paper-2 color as
     the surrounding card frame so it reads as "card surface
     continues here" rather than a missing-illustration placeholder.
     Result: every editorial flashcard reserves the same vertical
     space whether or not it carries an image — the deck reads as
     a uniform grid, and the kanji glyph + readings sit at the same
     y-position card to card. */
  .testcard-image:has(image-slot[readonly]:not([data-filled])) {
    background: var(--paper-2);
  }
  /* The image-slot\'s inner <img> doesn\'t inherit the host\'s
     border-radius through the shadow root — we use ::part if exposed,
     otherwise the host\'s overflow:hidden + radius clips it. */
  .testcard-image image-slot::part(img),
  .testcard-image image-slot::part(frame) {
    border-radius: 8px;
  }
  /* Roman-numeral "image" for numbers 1–10. The 1:1 square frame is
     reused — same dimensions as any kanji's photo — but instead of
     probing an asset, the card carries a `digit` field (I, II, … X)
     that we paint as a large serif numeral on the paper-2 background.
     Reinforces the numeric meaning visually and pairs more cleanly
     than a missing-illustration placeholder would. */
  .testcard-image-digit {
    background: var(--paper-2);
  }
  .testcard-digit {
    font-family: var(--serif);
    font-weight: 400;
    font-size: clamp(72px, 9vw, 124px);
    line-height: 1;
    color: var(--ink);
    letter-spacing: 0.02em;
    user-select: none;
    /* Slight optical lift — Roman numerals look better when not
       perfectly centered on the baseline. */
    transform: translateY(-0.02em);
  }
  /* Long digits (4+ chars — currently only 1000 for 千) need to shrink
     so they don't overflow the 1:1 image frame. Tabular figures and
     a tighter tracking keep the four numerals from feeling cramped. */
  .testcard-digit[data-digit-long="1"] {
    font-size: clamp(44px, 5.5vw, 78px);
    letter-spacing: -0.01em;
    font-feature-settings: 'tnum' 1;
  }
  .testcard-glyph {
    /* font-family is set inline per-card from APP.flashGlyphFont so the
       user can flip between mincho / gothic / kyokasho / brush without
       affecting the rest of the page. Sized down 15% with the image
       reduction so the column stays proportional. */
    font-family: var(--font-title);
    font-weight: 600;
    font-size: clamp(64px, 7vw, 100px);
    line-height: 1;
    color: var(--ink);
    margin-top: 2px;
    letter-spacing: 0.02em;
  }
  .testcard-readings {
    display: flex; align-items: baseline;
    gap: 6px;
    margin-top: -8px;
    color: var(--ink-3);
  }
  .testcard-reading-label {
    font-family: var(--serif); font-style: italic;
    font-size: 11px;
    color: var(--ink-4);
    letter-spacing: 0.02em;
  }
  .testcard-reading-val {
    font-family: var(--testcard-text-font);
    font-size: 14px;
    color: var(--ink-2);
  }
  /* Hover-to-play readings — adds a quiet speaker icon next to any
     reading or example word. The icon space is RESERVED at zero opacity
     so revealing it on hover does not shift the layout (multiple
     readings sit inline, an unreserved expansion would jitter the row).
     Reused for kun, on, and each example kanji compound.
     Triggers TTS via the existing global [data-speak] click delegate. */
  .read-tts {
    position: relative;
    cursor: pointer;
    border-bottom: 1px dotted transparent;
    transition: border-color .15s, color .15s;
  }
  .read-tts:hover { border-bottom-color: rgba(141,102,48,0.35); }
  .read-tts:hover { color: var(--ink); }
  .read-tts-icon {
    display: inline-flex;
    align-items: center;
    margin-left: 5px;
    opacity: 0;
    color: var(--ink-3);
    vertical-align: middle;
    transition: opacity .2s, color .15s;
    pointer-events: none;   /* parent owns the click */
  }
  .read-tts:hover .read-tts-icon { opacity: 1; }
  .read-tts:hover .read-tts-icon { color: var(--gold-dark); }
  .read-tts-icon svg { width: 11px; height: 11px; }
  /* When TTS plays, briefly pulse the icon — gives the learner a
     visible confirmation that the sound actually fired. Pure CSS, JS
     toggles the class on click via the existing TTS handler. */
  .read-tts.is-speaking .read-tts-icon { opacity: 1; color: var(--gold-dark); }
  /* Example kanji compound — same affordance, sized to its row's
     baseline. The icon sits at the END of the compound so the kanji
     itself stays the primary read. */
  .testcard-ex-kanji.read-tts .read-tts-icon svg { width: 12px; height: 12px; }
  .testcard-reading-dot {
    color: var(--ink-4);
    font-size: 11px;
    margin: 0 4px;
  }

  /* Right column — the info pane. The left border draws the visual
     divider between the two columns. */
  .testcard-right {
    padding-left: 40px;
    border-left: 1px solid var(--paper-edge);
    display: flex; flex-direction: column;
    justify-content: center;     /* vertically center the info pane */
    gap: 0;
    min-height: 0;
  }

  /* (Font pickers moved out of the card into the top control row. See
     .flash-top-fontpickers above.) */
  .testcard-section { padding: 10px 0; }
  .testcard-eyebrow {
    font-family: var(--serif); font-style: italic;
    font-size: 11px;
    color: var(--ink-3);
    letter-spacing: 0.14em;
    text-transform: uppercase;
    margin-bottom: 10px;
  }
  .testcard-meaning {
    font-family: var(--serif); font-style: italic;
    font-size: 17px;
    color: var(--ink);
    letter-spacing: 0.01em;
  }
  .testcard-rule {
    height: 0;
    border-top: 1px dashed var(--paper-edge);
    margin: 6px 0;
  }

  /* Examples list — each row uses a dotted leader between the romaji
     and the English gloss, like a table-of-contents in a printed book. */
  .testcard-examples {
    list-style: none; margin: 0; padding: 0;
    display: flex; flex-direction: column;
    gap: 10px;
  }
  .testcard-ex {
    display: grid;
    grid-template-columns: minmax(72px, auto) auto 1fr auto;
    align-items: baseline;
    gap: 14px;
    line-height: 1.2;
  }
  .testcard-ex-kanji {
    font-family: var(--testcard-text-font); font-weight: 500;
    font-size: 18px;
    color: var(--ink);
    letter-spacing: 0.02em;
  }
  .testcard-ex-romaji {
    font-family: var(--serif); font-style: italic;
    font-size: 13px;
    color: var(--ink-2);
    letter-spacing: 0.08em;
  }
  /* The dotted leader is drawn with a repeating background — a row of
     small dots vertically centered on the row baseline. */
  .testcard-ex-leader {
    display: block;
    min-height: 1em;
    border-bottom: 1px dotted var(--ink-4);
    transform: translateY(-3px);
    opacity: 0.7;
  }
  .testcard-ex-en {
    font-family: var(--serif); font-style: italic;
    font-size: 13px;
    color: var(--ink-2);
    text-align: right;
    white-space: nowrap;
  }

  /* Meta row — Heisig chip + strokes count, sitting in a quiet row. */
  .testcard-meta {
    display: flex; align-items: center;
    gap: 10px;
    padding: 6px 0 14px;
  }
  .testcard-strokes {
    margin-left: auto;
    padding: 3px 12px;
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 999px;
    font-family: var(--serif-jp);
    font-size: 12px;
    color: var(--ink-3);
  }

  /* Related-kanji block — sits below a hairline (visual separator from
     the meta row above). Two columns: ALSO READS + SEE ALSO. */
  .testcard-related {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 24px;
    margin-top: 6px;
    padding-top: 18px;
  }
  .testcard-related-block {
    display: flex; flex-direction: column;
    gap: 8px;
  }
  .testcard-related .testcard-eyebrow { margin-bottom: 4px; }
  .testcard-chips {
    display: flex; flex-wrap: wrap; gap: 6px;
  }
  .testcard-chip {
    display: inline-flex; align-items: baseline;
    gap: 8px;
    padding: 4px 12px;
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 4px;
    cursor: pointer;
    transition: background .12s, border-color .12s, transform .15s;
    /* Long glosses like "character / letter" stay on one line — the chip
       grows horizontally, doesn\'t wrap. */
    white-space: nowrap;
  }
  .testcard-chip:hover {
    border-color: var(--gold-soft);
    transform: translateY(-1px);
  }
  .testcard-chip-kanji {
    font-family: var(--testcard-text-font); font-weight: 500;
    font-size: 18px;
    color: var(--ink);
    line-height: 1;
  }
  .testcard-chip-en {
    font-family: var(--serif); font-style: italic;
    font-size: 11px;
    color: var(--ink-3);
    white-space: nowrap;
  }

  /* Footer row — sits at the bottom of the card, full-width, separated
     from the 2-col body by a hairline. Heisig + strokes on the left,
     prev/next navigation on the right. Two ends of a single horizontal
     beam — same vertical alignment, same breathing room. */
  .testcard-footer {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 16px;
    padding-top: 16px;
    border-top: 1px solid var(--paper-edge);
    margin-top: 6px;
  }
  /* In the editorial card the heisig chip sits at the bottom-left of
     the footer. Its default tooltip centers above the chip via
     translateX(-50%), which means the tooltip\'s left half extends past
     the card\'s left edge — and gets clipped by .main-inner\'s overflow:
     hidden (set to keep the editorial layout viewport-bounded). Anchor
     the tooltip to the chip\'s LEFT edge here so it extends rightward
     into the card body where there\'s plenty of room. */
  .testcard-footer-meta .heisig-chip .heisig-tip {
    left: 0;
    transform: none;
  }
  .testcard-footer-meta .heisig-chip .heisig-tip::after {
    left: 16px;
    transform: none;
  }
  /* Radical-card variant — the radical card has its own padding scheme
     so we add a small extra gap above the footer for visual balance. */
  .testcard-footer.radical-footer {
    margin-top: 18px;
  }
  .testcard-footer-meta {
    display: flex; align-items: center;
    gap: 10px;
    flex-wrap: wrap;
  }
  /* Both chips sit on the same horizontal axis in the footer. The
     heisig chip has a legacy `margin-top: 6px` for older contexts —
     zero it out here so the chip aligns with the strokes pill beside it. */
  .testcard-footer-meta .heisig-chip {
    margin-top: 0;
    padding: 4px 10px 4px 6px;     /* match strokes chip vertical height */
    border-radius: 999px;          /* same pill silhouette as 3画 */
  }
  .testcard-footer-meta .heisig-chip .h-icon {
    /* Force baseline-neutral alignment inside the chip — the h-icon
       sits centered, never sticking above or below the kw text. */
    align-self: center;
    line-height: 1;
  }
  .testcard-footer-meta .testcard-strokes {
    margin-left: 0;
    padding: 4px 12px;
    line-height: 1;
  }

  /* Prev / next nav buttons — outlined "prev" + filled gold "next."
     Above each button sits a small furigana label (まえ / つぎ) so the
     learner sees the reading of 前 / 次 while pressing them. */
  .testcard-footer-nav {
    display: flex; align-items: flex-end;
    gap: 10px;
  }
  .testcard-nav-btn {
    position: relative;
    display: inline-flex; flex-direction: column; align-items: stretch;
    padding: 0;
    background: transparent;
    border: none;
    cursor: pointer;
    transition: transform .15s;
  }
  .testcard-nav-btn:hover { transform: translateY(-1px); }
  .testcard-nav-btn:active { transform: translateY(0); }
  .testcard-nav-furi {
    font-family: var(--serif-jp);
    font-size: 11px;
    color: var(--ink-3);
    text-align: center;
    margin-bottom: 4px;
    letter-spacing: 0.06em;
    line-height: 1;
    user-select: none;
  }
  .testcard-nav-label {
    display: inline-flex; align-items: center; justify-content: center;
    gap: 6px;
    min-width: 80px;
    padding: 9px 16px;
    background: var(--paper);
    border: 1.5px solid var(--gold-dark);
    border-radius: 6px;
    color: var(--gold-dark);
    font-family: var(--font-title);
    font-weight: 600;
    font-size: 18px;
    line-height: 1;
    transition: background .12s, color .12s, border-color .12s;
  }
  .testcard-nav-btn.is-primary .testcard-nav-label {
    background: var(--gold-dark);
    color: var(--paper);
    border-color: var(--gold-dark);
  }
  .testcard-nav-btn:hover .testcard-nav-label {
    background: color-mix(in srgb, var(--gold-soft) 30%, var(--paper) 70%);
  }
  .testcard-nav-btn.is-primary:hover .testcard-nav-label {
    background: var(--ink-2);
    border-color: var(--ink-2);
  }
  .testcard-nav-arrow {
    font-size: 16px;
    line-height: 1;
  }

  /* ─── Review mode (FSRS) chrome ──────────────────────────────────────
     The card template is untouched; review wraps it with a meta line, the
     reveal / rating bar (reusing the testcard-nav-btn grammar — furigana
     mini-label over the label), and a quiet session close. Deliberately
     NOT drill-app styled: rating chips are ink-uniform, the interval
     preview is the information, and また carries a single vermilion
     underline (the card returns to you, marked in the logo's red). */
  .flash-review {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 16px;
    padding-top: 8px;
  }
  .flash-review .flash-deck { width: 100%; }
  .flash-review-meta {
    width: 100%;
    max-width: 920px;
    display: flex;
    align-items: baseline;
    gap: 16px;
    padding: 0 4px;
  }
  .review-pos, .review-deck {
    font-family: var(--serif);
    font-style: italic;
    font-size: 13px;
    letter-spacing: .06em;
    color: var(--ink-3);
  }
  .review-deck { flex: 1; }
  .review-exit {
    appearance: none;
    background: none;
    border: 0;
    padding: 4px 6px;
    font-family: var(--serif);
    font-style: italic;
    font-size: 13px;
    color: var(--ink-3);
    cursor: pointer;
    border-bottom: 1px dotted var(--ink-4);
  }
  .review-exit:hover { color: var(--ink); }
  .review-exit:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
  .review-bar {
    display: flex;
    gap: 12px;
    justify-content: center;
    flex-wrap: wrap;
  }
  .review-bar .testcard-nav-btn { min-width: 104px; }
  .review-bar .testcard-nav-furi { min-height: 14px; }
  .review-rate-again .testcard-nav-label {
    box-shadow: inset 0 -2px 0 var(--accent-vermilion);
  }
  .review-kbd-hint {
    font-family: var(--serif);
    font-style: italic;
    font-size: 12px;
    color: var(--ink-4);
    letter-spacing: .04em;
  }
  /* The review card is its own lean template (reviewCardHTML) — a bare
     prompt and a spare confirmation, NOT the full browse spread. Front is
     the kanji alone; back adds the image, meaning, and kun/on readings. */
  .review-card {
    width: 100%;
    max-width: 920px;
    min-height: 380px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 18px;
    padding: 48px 32px;
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 6px;
    box-shadow: 0 2px 14px var(--paper-shadow);
  }
  .review-card-glyph {
    font-size: clamp(96px, 18vw, 168px);
    line-height: 1.1;
    color: var(--ink);
    letter-spacing: .02em;
    text-align: center;
    overflow-wrap: anywhere;
  }
  .review-card.is-back .review-card-glyph {
    font-size: clamp(72px, 12vw, 120px);
  }
  .review-card-pair {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 40px;
    flex-wrap: wrap;
  }
  .review-card-image {
    width: 190px;
    height: 190px;
  }
  .review-card-image image-slot { width: 100%; height: 100%; display: block; }
  /* No image on file → a readonly image-slot hides itself (its own
     :host([readonly]:not([data-filled])) rule); collapse the 190px box too
     so imageless cards don't carry a hollow square. data-filled lives on
     the host (light DOM), so :has() can see it. */
  .review-card-image:has(image-slot:not([data-filled])) { display: none; }
  .review-card-meaning {
    font-family: var(--serif);
    font-style: italic;
    font-size: 20px;
    color: var(--ink-2);
    text-align: center;
  }
  .review-card-readings {
    display: flex;
    align-items: baseline;
    gap: 14px;
    flex-wrap: wrap;
    justify-content: center;
  }
  .review-reading { display: inline-flex; align-items: baseline; gap: 8px; }
  .review-reading-label {
    font-family: var(--serif);
    font-style: italic;
    font-size: 12px;
    letter-spacing: .08em;
    color: var(--ink-4);
  }
  .review-reading-value {
    font-family: var(--serif-jp);
    font-size: 18px;
    color: var(--ink-2);
  }
  .review-reading-dot { color: var(--ink-4); }
  .review-done {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 12px;
    padding: 96px 24px 48px;
    text-align: center;
  }
  .review-done-ja {
    font-family: var(--serif-jp);
    font-size: 30px;
    font-weight: 500;
    color: var(--ink);
    letter-spacing: .08em;
  }
  .review-done-en {
    font-family: var(--serif);
    font-style: italic;
    font-size: 14px;
    color: var(--ink-3);
  }
  .review-done-rule { width: 72px; margin: 10px 0 6px; }
  .review-learn { margin-top: 4px; }
  @media (max-width: 760px) {
    .review-bar { gap: 8px; }
    .review-bar .testcard-nav-btn { min-width: 0; flex: 1 1 40%; }
    .flash-review-meta { flex-wrap: wrap; }
  }

  /* ─── Flip card (front ↔ back) ────────────────────────────────────────
     The editorial card flips on its Y-axis to reveal a back face that
     carries the stroke-order GIF, the constituent radicals, and writing
     notes. The footer (heisig + flip btn + prev/next) lives OUTSIDE the
     flip so it stays accessible from either side. Front is in normal
     flow (drives the height); back is absolute-positioned over the same
     box. `transform-style: preserve-3d` on the inner wrapper lets both
     faces share a 3D context so backface-visibility correctly hides the
     face that's currently rotated away from the viewer. */
  .testcard-flip-wrap {
    position: relative;
    perspective: 1600px;
    flex: 1;
    min-height: 0;
  }
  .testcard-flip-inner {
    position: relative;
    width: 100%;
    transform-style: preserve-3d;
    transition: transform .65s cubic-bezier(.34,1.16,.64,1);
  }
  .testcard-flip-wrap[data-flipped="true"] .testcard-flip-inner {
    transform: rotateY(180deg);
  }
  .testcard-flip-front,
  .testcard-flip-back {
    backface-visibility: hidden;
    -webkit-backface-visibility: hidden;
  }
  /* Front drives the height — it's in normal flow. */
  .testcard-flip-front { position: relative; }
  /* Back overlays the same box; pre-rotated so it faces the viewer once
     the inner wrapper has been rotated 180°. Allows internal scrolling
     if the back's content (gif + composition + notes + credit) is
     taller than the front-driven height — better than clipping. */
  .testcard-flip-back {
    position: absolute;
    inset: 0;
    transform: rotateY(180deg);
    display: flex; flex-direction: column;
    gap: 14px;
    padding: 4px 0 0;
    overflow-y: auto;
  }
  /* Reduced-motion: skip the 3D rotation and cross-fade instead. Same
     state model, just a different transition. The user opted into less
     motion site-wide so the flip respects that. */
  @media (prefers-reduced-motion: reduce) {
    .testcard-flip-inner { transition: opacity .25s ease; transform: none !important; }
    .testcard-flip-wrap[data-flipped="true"] .testcard-flip-front { opacity: 0; pointer-events: none; }
    .testcard-flip-wrap[data-flipped="true"] .testcard-flip-back { opacity: 1; }
    .testcard-flip-back { opacity: 0; transition: opacity .25s ease; }
    .testcard-flip-front, .testcard-flip-back { backface-visibility: visible; -webkit-backface-visibility: visible; }
    .testcard-flip-back { transform: none; }
  }

  /* ─── Back face content ──────────────────────────────────────────────
     A vertically-stacked layout: eyebrow strip (kanji + meaning so the
     learner doesn't lose context), stroke-order GIF (mix-blend: multiply
     so the white field of the gif dissolves into the paper texture and
     only the ink reads), composition chips, optional writing note,
     credit footer. Generous padding mirrors the front face's frame. */
  .testcard-back-head {
    display: flex; align-items: baseline; gap: 14px;
    padding-bottom: 10px;
    border-bottom: 1px solid var(--paper-edge);
  }
  .testcard-back-glyph {
    font-family: var(--font-title);
    font-weight: 600;
    font-size: 34px;
    color: var(--ink);
    line-height: 1;
  }
  .testcard-back-readings {
    font-family: var(--serif-jp);
    font-size: 13px;
    color: var(--ink-3);
    letter-spacing: 0.04em;
  }
  .testcard-back-meaning {
    margin-left: auto;
    font-family: var(--serif); font-style: italic;
    font-size: 16px;
    color: var(--ink-2);
  }
  .testcard-back-body {
    display: grid;
    grid-template-columns: minmax(220px, 320px) 1fr;
    gap: 36px;
    align-items: start;
    flex: 1;
    min-height: 0;
  }
  .testcard-back-stroke {
    position: relative;
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    border-radius: 4px;
    aspect-ratio: 1 / 1;
    overflow: hidden;
    display: flex; align-items: center; justify-content: center;
  }
  .testcard-stroke-gif {
    width: 100%; height: 100%;
    object-fit: contain;
    /* Multiply blends the gif's white field into the paper texture so
       only the brush strokes register — feels like ink on the same paper
       stock as the card. */
    mix-blend-mode: multiply;
    background: transparent;
  }
  .testcard-stroke-gif.is-missing { display: none; }
  .testcard-stroke-missing {
    display: none;
    padding: 20px 24px;
    text-align: center;
    color: var(--ink-3);
    font-family: var(--serif); font-style: italic;
    font-size: 13px;
    line-height: 1.5;
  }
  .testcard-stroke-gif.is-missing + .testcard-stroke-missing { display: block; }
  .testcard-stroke-missing a {
    color: var(--gold-dark);
    text-decoration: underline dotted;
  }
  /* Multi-kanji back face — when a compound card (元気, 御飯, 烏賊) is
     showing, the stroke-order area splits into N equal panels side by
     side, one per kanji. A small label under each panel re-states the
     kanji so the learner knows which stroke order belongs to which
     half of the compound. Single-kanji cards (the majority) use the
     default flow above — no flex container needed. Spec §4.4. */
  .testcard-back-stroke.is-multi {
    display: flex;
    gap: 14px;
    align-items: stretch;
  }
  .testcard-back-stroke.is-multi .testcard-back-stroke-panel {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 4px;
  }
  .testcard-back-stroke.is-multi .testcard-stroke-gif {
    width: 100%;
    height: auto;
    display: block;
  }
  .testcard-back-stroke-label {
    font-family: var(--serif-jp);
    font-size: 16px;
    font-weight: 600;
    color: var(--ink-2);
    text-align: center;
    line-height: 1;
  }
  .testcard-back-info {
    display: flex; flex-direction: column;
    gap: 18px;
  }
  /* Composition radicals — chips arranged in a flowing row, sorted by
     Kangxi order (stroke count, then within-stroke index). Each chip
     shows the radical glyph + a short English gloss; chips that
     correspond to cards in our own deck become clickable to jump to
     that radical's card. */
  .testcard-compose-chips {
    display: flex; flex-wrap: wrap;
    gap: 8px;
  }
  .testcard-compose-chip {
    display: inline-flex; align-items: center;
    gap: 8px;
    padding: 6px 12px;
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    border-radius: 999px;
    font-family: var(--serif-jp);
    color: var(--ink-2);
    transition: background .12s, border-color .12s, color .12s;
  }
  .testcard-compose-chip.is-linkable {
    cursor: pointer;
    border-color: rgba(141,102,48,0.35);
  }
  .testcard-compose-chip.is-linkable:hover {
    background: var(--paper-2);
    border-color: var(--gold);
    color: var(--ink);
  }
  .testcard-compose-glyph {
    font-size: 18px;
    line-height: 1;
    font-weight: 600;
    color: var(--ink);
  }
  .testcard-compose-gloss {
    font-family: var(--serif); font-style: italic;
    font-size: 12px;
    color: var(--ink-3);
  }
  .testcard-compose-empty {
    font-family: var(--serif); font-style: italic;
    font-size: 13px;
    color: var(--ink-4);
  }
  .testcard-back-note p {
    margin: 0;
    font-family: var(--serif);
    font-size: 14px;
    line-height: 1.55;
    color: var(--ink-2);
  }
  .testcard-back-credit {
    margin-top: auto;
    padding-top: 10px;
    border-top: 1px dashed rgba(141,102,48,0.18);
    font-family: var(--serif); font-style: italic;
    font-size: 11px;
    color: var(--ink-4);
    letter-spacing: 0.04em;
    text-align: right;
  }
  .testcard-back-credit a {
    color: inherit;
    text-decoration: underline dotted;
  }

  /* ─── 3-block footer (meta · flip · nav) ────────────────────────────
     Replaces the old 2-block space-between layout. Grid keeps the flip
     button centered regardless of how wide the meta or nav blocks grow.
     The flip button has its own button style — outlined paper-2 chip
     with the kanji + furigana stack, distinct from the nav buttons
     (which carry the primary navigational accent). */
  .testcard-footer {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: end;
    gap: 16px;
  }
  .testcard-footer-meta  { justify-self: start; }
  .testcard-footer-flip  { justify-self: center; }
  .testcard-footer-nav   { justify-self: end; }
  .testcard-flip-btn {
    position: relative;
    display: inline-flex; flex-direction: column; align-items: stretch;
    padding: 0;
    background: transparent;
    border: none;
    cursor: pointer;
    transition: transform .15s;
  }
  .testcard-flip-btn:hover { transform: translateY(-1px); }
  .testcard-flip-btn:active { transform: translateY(0); }
  .testcard-flip-btn .testcard-nav-furi {
    /* Reuse furi label style from the nav buttons so the visual rhythm
       across the 3 blocks reads as a single horizontal beam. */
    font-family: var(--serif-jp);
    font-size: 11px;
    color: var(--ink-3);
    text-align: center;
    margin-bottom: 4px;
    letter-spacing: 0.06em;
    line-height: 1;
    user-select: none;
  }
  .testcard-flip-label {
    display: inline-flex; align-items: center; justify-content: center;
    gap: 6px;
    min-width: 64px;
    padding: 9px 14px;
    background: var(--paper-2);
    border: 1.5px solid rgba(141,102,48,0.35);
    border-radius: 6px;
    color: var(--ink-2);
    font-family: var(--font-title);
    font-weight: 600;
    font-size: 18px;
    line-height: 1;
    transition: background .12s, color .12s, border-color .12s;
  }
  .testcard-flip-btn:hover .testcard-flip-label {
    background: var(--paper);
    border-color: var(--gold);
    color: var(--ink);
  }
  .testcard-flip-btn[data-flipped="true"] .testcard-flip-label {
    background: var(--paper);
    border-color: var(--gold-dark);
    color: var(--gold-dark);
  }
  /* Heisig "no-English" mini-chip — pill-shaped same as the full chip
     but without the keyword and without the hover tooltip. The H badge
     + frame number remain so the learner can still anchor to the Heisig
     entry without seeing the meaning. */
  .heisig-chip-noen .heisig-tip { display: none !important; }
  .heisig-chip-noen { cursor: default; }
  /* Example row with English hidden — the leader line + gloss collapse
     out, so the row reads as a tidy [kanji · romaji] pair without an
     awkward trailing void where the meaning used to sit. */
  .testcard-ex-noen .testcard-ex-leader,
  .testcard-ex-noen .testcard-ex-en { display: none; }

  @media (max-width: 900px) {
    .testcard { grid-template-columns: 1fr; gap: 24px; }
    .testcard-right {
      padding-left: 0; padding-top: 24px;
      border-left: none; border-top: 1px solid var(--paper-edge);
    }
    .testcard-related { grid-template-columns: 1fr; }
    .flash-card.testcard-frame { padding: 20px 22px; }
    .testcard-footer { flex-direction: column; align-items: stretch; gap: 12px; }
    .testcard-footer-nav { justify-content: space-between; }
  }

  /* ─── View toggle (card / list) ──────────────────────────────────────
     A small segmented control above the deck. Two buttons, no fuss —
     identifies the current view at a glance and swaps with one click. */
  /* Top control row — sits above the card. Three groups laid out in a
     centered flex row:
       1. view toggle (old / new / list)
       2. font dropdowns (glyph + text) — appears only in NEW view
       3. English toggle (英 / —)
     The whole row centers horizontally above the card. */
  .flash-top-row {
    display: flex;
    align-items: center; justify-content: center;
    flex-wrap: wrap;
    gap: 12px;
    margin: 0 auto 10px;
  }

  .flash-view-toggle {
    display: flex;
    width: max-content;
    margin: 0;
    padding: 2px;
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 8px;
    gap: 2px;
  }

  /* Glyph + Text font dropdowns inside the top row. Each carries a tiny
     italic label above (or beside) its dropdown trigger. */
  .flash-top-fontpickers {
    display: flex; align-items: center;
    gap: 10px;
  }
  .flash-top-fontpicker {
    display: inline-flex; align-items: center;
    gap: 6px;
  }
  .flash-top-fontpicker-label {
    font-family: var(--serif); font-style: italic;
    font-size: 11px;
    color: var(--ink-3);
    letter-spacing: 0.04em;
  }
  /* Native <select> styled to fit the top-row chrome — paper fill,
     subtle border, tiny italic font. Used for the bg-preference picker
     on flashcards only. We don't need a custom dropdown component
     because the option labels are short and don't need preview swatches
     like the font dropdowns do. */
  .flash-bg-select {
    appearance: none; -webkit-appearance: none;
    font: inherit;
    font-family: var(--serif); font-style: italic;
    font-size: 12px;
    color: var(--ink-2);
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 6px;
    padding: 4px 22px 4px 10px;
    cursor: pointer;
    /* Chevron drawn inline so we avoid a separate <svg> element. */
    background-image:
      linear-gradient(45deg, transparent 50%, var(--ink-3) 50%),
      linear-gradient(135deg, var(--ink-3) 50%, transparent 50%);
    background-position:
      calc(100% - 12px) calc(50% - 1px),
      calc(100% - 8px)  calc(50% - 1px);
    background-size: 4px 4px, 4px 4px;
    background-repeat: no-repeat;
    transition: border-color .15s, color .15s;
  }
  .flash-bg-select:hover { border-color: var(--gold); color: var(--ink); }
  .flash-bg-select:focus-visible {
    outline: 2px solid var(--gold);
    outline-offset: 2px;
  }

  /* English toggle — paired 英 / — pills. Reuses .pill-sq from the
     classic right-rail toggle so visual language stays consistent. */
  .flash-top-en {
    display: flex; align-items: center;
    gap: 4px;
    padding: 2px;
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 8px;
  }
  .flash-top-en .pill-sq {
    width: 28px; height: 28px;
    border-radius: 6px;
    border: none;
    background: transparent;
  }
  .flash-top-en .pill-sq.active {
    background: var(--gold);
    color: white;
  }
  .flash-view-btn {
    display: inline-flex; align-items: center; gap: 6px;
    background: transparent;
    border: none;
    padding: 6px 12px;
    border-radius: 6px;
    font-family: var(--serif); font-size: 13px;
    color: var(--ink-3);
    cursor: pointer;
    transition: background .12s, color .12s;
  }
  .flash-view-btn svg { opacity: 0.7; }
  .flash-view-btn:hover { color: var(--ink-2); }
  .flash-view-btn.is-active {
    background: var(--paper);
    color: var(--ink);
    box-shadow: 0 1px 3px rgba(100,72,30,0.10);
  }
  .flash-view-btn.is-active svg { opacity: 1; color: var(--gold-dark); }

  /* ─── List view — all kanji as a 2-column grid ──────────────────────
     Two columns of rows so the user can scan more of the class at once
     without scrolling. Collapses to one column on narrow viewports. */
  .flash-list {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 6px 12px;
    max-width: 1200px;
  }
  @media (max-width: 760px) {
    .flash-list { grid-template-columns: 1fr; }
  }
  .flash-list-row {
    display: grid;
    /* Col 1 (kanji) is auto-sized with a 52px floor so single-glyph rows
       look consistent and multi-glyph rows (up to 6 chars) lay out on a
       single line. The 7+ char rows opt into wrap via the .is-long
       modifier below — they're rare (kana onomatopoeia, long compounds)
       and look better stacked. */
    grid-template-columns: minmax(52px, max-content) 1fr auto auto;
    align-items: center;
    gap: 16px;
    padding: 12px 18px;
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 6px;
    text-align: left;
    cursor: pointer;
    transition: transform .12s, border-color .12s, background .12s;
    font-family: var(--serif);
  }
  .flash-list-row:hover {
    transform: translateX(2px);
    border-color: var(--gold-soft);
    background: var(--paper);
  }
  .flash-list-kanji {
    /* The list\'s glyph font, settable via the top-row dropdown. */
    font-family: var(--list-glyph-font, var(--font-title));
    font-weight: 600;
    font-size: 32px;
    color: var(--ink);
    line-height: 1;
    /* Default: 1–6 chars stay on a single line. Column 1 expands as
       needed (capped by max-content). */
    white-space: nowrap;
  }
  /* 7+ char strings (long kana onomatopoeia like にゃーにゃー, multi-kanji
     compounds) wrap so col 1 doesn\'t blow out and shove the readings off
     the right edge. Smaller font keeps the row visually balanced. */
  .flash-list-kanji.is-long {
    white-space: normal;
    font-size: 22px;
    max-width: 7em;
    line-height: 1.15;
  }
  .flash-list-readings {
    display: flex; align-items: baseline;
    gap: 6px;
    color: var(--ink-3);
  }
  .flash-list-kun, .flash-list-on {
    /* The list\'s text font, settable via the top-row dropdown. */
    font-family: var(--list-text-font, var(--serif-jp));
    font-size: 14px;
    color: var(--ink-2);
  }
  /* Hidden-English placeholder — renders a dash so the row layout
     doesn\'t collapse when the user toggles English off. */
  .flash-list-en-hidden {
    color: var(--ink-4);
    opacity: 0.4;
  }
  .flash-list-sep { color: var(--ink-4); font-size: 11px; }
  .flash-list-en {
    font-family: var(--serif); font-style: italic;
    font-size: 13px;
    color: var(--ink-3);
    text-align: right;
    max-width: 240px;
  }
  .flash-list-strokes {
    padding: 2px 10px;
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    border-radius: 999px;
    font-family: var(--serif-jp);
    font-size: 11px;
    color: var(--ink-3);
  }
  @media (max-width: 600px) {
    .flash-list-row { grid-template-columns: 44px 1fr auto; padding: 10px 14px; gap: 12px; }
    .flash-list-en { display: none; }
    .flash-list-kanji { font-size: 26px; }
  }

  /* Editorial deck — when a class opts into the new layout, the deck
     gets more horizontal room than the standard flash-card. */
  .flash-deck.flash-deck-editorial {
    max-width: 1080px;
  }

  /* ─── flashcards ────────────────────────────────────────────────────── */
  /* ─── flash 3-column layout (rails hug the gutters, card lives in the
     middle column with a narrow max-width). Spans the full main-inner so
     the title rail sits close to the sidebar and the controls rail sits
     close to the right edge — every pixel between them belongs to the
     card. ──────────────────────────────────────────────────────────── */
  /* Four-column rail layout: title (far left, hugs sidebar), card center,
     inner rail (単語札 caption + vertical progress), outer rail (toggles).
     Splitting the caption + toggles into separate columns prevents them
     from stacking vertically — every pixel of vertical space belongs to
     the card. Rails are deliberately narrow so the card breathes wide. */
  .flash-layout {
    display: grid;
    /* Narrow rails so the card hogs the middle column. Left rail is
       intentionally the tightest (20px) — just enough to fit the vertical
       caption text + a 1px hairline. The 14px column-gap reads as page
       breathing room, not as wasted column. */
    grid-template-columns: 22px 1fr 26px 40px;
    column-gap: 12px;
    align-items: stretch;
    width: 100%;
    margin: 0;
    min-height: calc(100vh - var(--pad-page) * 2);
  }
  .flash-rail-left {
    display: flex; align-items: center; justify-content: flex-start;
    padding: 0;
  }
  /* Vertical page title — runs top-to-bottom along the left gutter so it
     reads as a deliberate caption pinned to the sidebar edge, not chrome
     that steals vertical room from the card. Gold dot caps at top + bottom
     terminate the hairline as if it were a punctuated stroke rather than
     a leftover rule. */
  .flash-title-vert {
    writing-mode: vertical-rl;
    font-family: var(--serif-jp); font-weight: 500;
    font-size: clamp(12px, 1.05vw, 14px);
    color: var(--ink-3); letter-spacing: 0.42em;
    white-space: nowrap;
    /* Hairline as a soft vertical gradient — strong in the middle where the
       text sits, fading at top/bottom so it reads as a deliberate caption
       gutter rather than a hard rule running floor-to-ceiling. */
    border-left: none;
    background:
      linear-gradient(
        to bottom,
        transparent 0%,
        rgba(141,102,48,0.0) 4%,
        rgba(141,102,48,0.22) 22%,
        rgba(141,102,48,0.28) 50%,
        rgba(141,102,48,0.22) 78%,
        rgba(141,102,48,0.0) 96%,
        transparent 100%
      ) left center / 1px 80% no-repeat;
    /* Using physical properties because writing-mode: vertical-rl remaps
       padding-block / padding-inline in confusing ways. Padding-left
       creates the gap between the hairline and the vertical text column;
       padding-top/bottom create the breathing room at the ends. */
    padding: 4px 0 4px 7px;
    position: relative;
  }
  /* Gold dot caps — terminate the hairline at top and bottom so the title
     rail reads as an intentional vertical mark, not a stray border. */
  .flash-title-vert::before,
  .flash-title-vert::after {
    content: "";
    position: absolute;
    left: -1.5px;
    width: 4px; height: 4px;
    border-radius: 50%;
    background: rgba(141,102,48,0.45);
  }
  .flash-title-vert::before { top: 8%; }
  .flash-title-vert::after  { bottom: 8%; }
  .flash-center {
    display: flex; align-items: center; justify-content: center;
    gap: 14px;
    min-width: 0;
  }
  /* Inner rail — caption + progress. Sits next to the card. Vertical only:
     the 単語札 label runs top-to-bottom, the progress bar runs as a thin
     gold column underneath. Both share the same narrow column so the card
     keeps every pixel of width it can. */
  .flash-rail-inner {
    display: flex; flex-direction: column;
    align-items: center; justify-content: center;
    gap: 14px;
    padding: 0;
  }
  /* Outer rail — controls only. Toggles live here, pushed to the edge so
     they never overlap the caption column vertically. */
  .flash-rail-right {
    display: flex; flex-direction: column;
    align-items: center; justify-content: center;
    gap: 10px;
    padding: 0;
  }
  /* Vertical 単語札 caption — mirrors the left rail's vertical title in
     weight and rhythm so the card is bookended by two matched gutter
     marks. Hairline on the RIGHT side (closer to the card) reads as a
     pinned caption, not a free-floating label. */
  .flash-label-vert {
    writing-mode: vertical-rl;
    font-family: var(--serif-jp); font-weight: 500;
    font-size: 11px; color: var(--ink-3);
    letter-spacing: 0.42em;
    white-space: nowrap;
    /* Physical padding — keeps the layout predictable under vertical-rl
       (logical block/inline get remapped in confusing ways). Right-side
       padding creates the gap between the hairline and the vertical text
       column; top/bottom create breathing room at the ends. */
    padding: 4px 6px 4px 0;
    /* Matched soft hairline — same vertical fade as the title rail so
       both rails read as paired gutter marks. */
    border-right: none;
    background:
      linear-gradient(
        to bottom,
        transparent 0%,
        rgba(141,102,48,0.0) 6%,
        rgba(141,102,48,0.18) 24%,
        rgba(141,102,48,0.24) 50%,
        rgba(141,102,48,0.18) 76%,
        rgba(141,102,48,0.0) 94%,
        transparent 100%
      ) right center / 1px 80% no-repeat;
  }
  .flash-toggle-vert {
    display: flex; flex-direction: column; gap: 4px;
  }
  .pill-sq {
    width: 32px; height: 32px;
    display: flex; align-items: center; justify-content: center;
    border-radius: 6px;
    background: var(--paper-2);
    border: 1px solid rgba(141,102,48,0.25);
    font-family: var(--serif-jp); font-size: 13px; font-weight: 500;
    color: var(--ink-3); cursor: pointer;
    transition: background .15s, color .15s, border-color .15s;
  }
  .pill-sq:hover { border-color: var(--gold); color: var(--ink-2); }
  .pill-sq.active {
    background: var(--gold); color: white; border-color: var(--gold-dark);
  }
  /* Vertical progress bar — no text, no tooltip, no axis label. Just a
     thin gold column that fills bottom-to-top as the deck advances. The
     track is barely visible (paper-edge) so the FILL reads as the only
     mark on the rail. Gold dot caps at both ends terminate the bar
     visually so it doesn't read as a clipped stripe. */
  .flash-progress-vert {
    width: 2px; height: clamp(160px, 36vh, 320px);
    background: rgba(141,102,48,0.10);
    border-radius: 999px;
    position: relative;
    overflow: visible;          /* dot caps poke past the track ends */
  }
  .flash-progress-vert::before,
  .flash-progress-vert::after {
    content: "";
    position: absolute;
    left: 50%; transform: translateX(-50%);
    width: 4px; height: 4px;
    border-radius: 50%;
    background: rgba(141,102,48,0.35);
  }
  .flash-progress-vert::before { top: -3px; }
  .flash-progress-vert::after  { bottom: -3px; }
  .flash-progress-vert > div {
    position: absolute; left: 0; right: 0; bottom: 0;
    background: var(--gold);
    border-radius: 999px;
    transition: height .35s cubic-bezier(.4,.0,.2,1);
  }
  .flash-mobile-head {
    display: none;
  }
  @media (max-width: 760px) {
    .flash-layout { grid-template-columns: 1fr; min-height: auto; }
    .flash-rail-left, .flash-rail-inner, .flash-rail-right { display: none; }
    .flash-mobile-head {
      display: flex; justify-content: flex-end; margin-bottom: 10px;
    }
  }

  .flash-deck {
    max-width: 580px; margin: 0 auto; width: 100%;
  }
  .flash-stage {
    perspective: 1200px;
    width: 100%;
  }
  .flash-nav {
    flex-shrink: 0;
    width: 52px; height: 52px;
    background: var(--paper);
    border-color: transparent;
    color: var(--ink-3);
    transition: background .15s, color .15s, border-color .15s;
  }
  .flash-nav svg { width: 22px; height: 22px; }
  .flash-nav:hover {
    background: var(--paper-2);
    color: var(--ink);
    border-color: rgba(141,102,48,0.25);
  }
  @media (max-width: 520px) {
    .flash-center { gap: 6px; }
    .flash-nav { width: 40px; height: 40px; }
    .flash-nav svg { width: 18px; height: 18px; }
  }
  .flash-card {
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    box-shadow:
      0 0 0 4px var(--paper-2),
      0 0 0 5px var(--gold-soft),
      0 12px 32px rgba(100,72,30,0.18);
    border-radius: 6px;
    padding: 32px;
    display: flex; flex-direction: column;
    transition: transform .35s cubic-bezier(.4,.0,.2,1);
  }

  /* ─── Wrap-up card ─────────────────────────────────────────────────
     The "you've reached the end" card that sits at position deck.length
     in CARD view. Looks like the editorial flashcard chrome family but
     shows every card in the class as a small image-tile grid — a quick
     scan-back over what was just learned. Click any tile to flip image
     ↔ kun/on. From here, "次" loops back to card 0; from card 0, "前"
     loops here. The deck reads as a closed cycle. */
  .flash-wrapup {
    padding: 22px 22px 18px;
    gap: 18px;
  }
  .wrapup-head {
    text-align: center;
    padding: 0 4px;
  }
  .wrapup-eyebrow {
    font-family: var(--serif-jp);
    font-size: 13px;
    color: var(--gold-dark);
    letter-spacing: 0.1em;
  }
  .wrapup-eyebrow em {
    font-family: var(--serif); font-style: italic;
    font-size: 12px;
    color: var(--ink-4);
    letter-spacing: 0.04em;
    margin-left: 6px;
  }
  .wrapup-title {
    font-family: var(--serif-jp);
    font-weight: 600;
    font-size: clamp(22px, 2.6vw, 28px);
    color: var(--ink);
    margin: 4px 0 2px;
    letter-spacing: 0.02em;
  }
  .wrapup-title em {
    font-family: var(--serif); font-style: italic;
    font-weight: 400;
    font-size: 14px;
    color: var(--ink-3);
    margin-left: 8px;
  }
  .wrapup-sub {
    font-family: var(--serif); font-style: italic;
    font-size: 12px;
    color: var(--ink-4);
    margin: 4px 0 0;
  }

  /* Tile grid — auto-fill so the grid adapts from ~10 to ~30 cards
     without manual breakpoints. Min tile width keeps small images
     legible; max is implicit from the grid container width. */
  .wrapup-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(78px, 1fr));
    gap: 8px;
    margin: 4px 0;
  }
  .wrapup-tile {
    aspect-ratio: 1 / 1;
    position: relative;
    padding: 0;
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    border-radius: 6px;
    cursor: pointer;
    overflow: hidden;
    transition: border-color .15s, transform .12s, box-shadow .15s;
  }
  .wrapup-tile:hover {
    border-color: var(--gold);
    transform: translateY(-1px);
    box-shadow: 0 4px 10px rgba(141, 102, 48, 0.12);
  }
  .wrapup-tile:focus-visible {
    outline: 2px solid var(--gold);
    outline-offset: 2px;
  }
  .wrapup-tile-face {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: opacity .18s;
  }
  /* Default: glyph + readings face visible, image hidden. Click toggles
     data-flip to reveal the image. Inverted from the original layout
     because the learner has just finished the deck — the readings are
     what they're testing themselves on, and the image is the reveal. */
  .wrapup-tile[data-flip="0"] .wrapup-face-image { opacity: 0; pointer-events: none; }
  .wrapup-tile[data-flip="1"] .wrapup-face-back  { opacity: 0; pointer-events: none; }

  .wrapup-face-image image-slot {
    width: 100%; height: 100%;
    display: block;
  }
  .wrapup-face-image image-slot::part(img),
  .wrapup-face-image image-slot::part(frame) {
    border-radius: 6px;
  }
  .wrapup-tile:has(image-slot[readonly]:not([data-filled])) .wrapup-face-image {
    /* When the slot has no illustration, the placeholder is paper-2;
       overlay the glyph so the empty tile still identifies its card. */
    background: var(--paper-2);
  }
  .wrapup-radical-glyph {
    font-family: var(--serif-jp);
    font-weight: 700;
    font-size: 32px;
    color: var(--ink);
    line-height: 1;
    letter-spacing: 0.02em;
  }
  /* Roman-numeral image for number cards in the wrap-up grid —
     mirrors .testcard-image-digit's role on the main flashcard.
     Sized down to fit the small tile; serif weight stays light. */
  .wrapup-digit {
    font-family: var(--serif);
    font-weight: 400;
    font-size: 26px;
    color: var(--ink);
    line-height: 1;
    letter-spacing: 0.02em;
  }
  .wrapup-digit[data-digit-long="1"] {
    font-size: 16px;
    letter-spacing: -0.01em;
    font-feature-settings: 'tnum' 1;
  }
  .wrapup-tile:has(.wrapup-digit) .wrapup-face-image {
    background: var(--paper-2);
  }

  /* Back face — kanji glyph on top, kun + on stacked below */
  .wrapup-face-back {
    flex-direction: column;
    gap: 4px;
    padding: 6px;
    background: var(--paper-2);
    text-align: center;
  }
  .wrapup-tile-glyph {
    font-family: var(--serif-jp);
    font-weight: 700;
    font-size: 22px;
    color: var(--ink);
    line-height: 1.05;
    letter-spacing: 0.01em;
  }
  /* Tile inner width is ~66px (78px tile minus 6px padding × 2). At
     the default 22px font, full-width kana chars are ~22px wide each,
     so 3 chars × 22 = 66px — exactly the available width, which
     means tiny letter-spacing + sub-pixel rounding push 3-char lines
     into a second-row overflow. Step the font down per max-line-len
     so 3-char lines breathe at 19px and 4-char lines fit at 16px.
     The 1-2 char case keeps the default 22px (handles all the
     にこ / にこ style two-up splits). */
  .wrapup-tile-glyph[data-line-len="3"] {
    font-size: 19px;
    letter-spacing: 0;
  }
  .wrapup-tile-glyph[data-line-len="4"] {
    font-size: 16px;
    letter-spacing: -0.01em;
  }
  .wrapup-tile-glyph[data-line-len="5"],
  .wrapup-tile-glyph[data-line-len="6"],
  .wrapup-tile-glyph[data-line-len="7"],
  .wrapup-tile-glyph[data-line-len="8"] {
    font-size: 13px;
    letter-spacing: -0.02em;
  }
  .wrapup-tile-readings {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: 3px 6px;
    font-family: var(--serif-jp);
    font-size: 11px;
    color: var(--ink-3);
    line-height: 1.2;
  }
  .wrapup-tile-readings .kun { color: var(--ink-2); }
  .wrapup-tile-readings .on  { color: var(--ink-4); letter-spacing: 0.04em; }
  .wrapup-tile-readings .dot { color: var(--ink-5); }
  .wrapup-tile-readings .wrapup-title-ja {
    font-size: 10px;
    color: var(--ink-3);
    letter-spacing: 0.04em;
  }

  /* Footer — three-column layout that mirrors the editorial card's
     testcard-footer (left | center | right). Left holds the
     flip-all-tiles bulk controls; center confirms "you're at end of
     deck"; right holds prev+next together so muscle memory carries
     from card view. */
  .wrapup-footer {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    gap: 14px;
    padding-top: 10px;
    border-top: 1px dashed var(--paper-edge);
    margin-top: 4px;
  }
  .wrapup-flip-all {
    display: inline-flex;
    align-items: stretch;
    gap: 6px;
  }
  .wrapup-flip-all-btn {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    padding: 0;
    background: transparent;
    border: none;
    cursor: pointer;
    transition: transform .15s;
  }
  .wrapup-flip-all-btn:hover  { transform: translateY(-1px); }
  .wrapup-flip-all-btn:active { transform: translateY(0); }
  .wrapup-flip-all-furi {
    font-family: var(--serif-jp);
    font-size: 10px;
    color: var(--ink-4);
    letter-spacing: 0.06em;
    line-height: 1;
    margin-bottom: 3px;
    user-select: none;
  }
  .wrapup-flip-all-glyph {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px; height: 30px;
    background: var(--paper-2);
    border: 1.5px solid rgba(141,102,48,0.35);
    border-radius: 6px;
    font-family: var(--serif-jp);
    font-weight: 700;
    font-size: 16px;
    color: var(--ink-2);
    line-height: 1;
    transition: background .12s, color .12s, border-color .12s;
  }
  .wrapup-flip-all-btn:hover .wrapup-flip-all-glyph {
    background: var(--paper);
    border-color: var(--gold);
    color: var(--ink);
  }
  .wrapup-footer-nav {
    display: inline-flex;
    align-items: center;
    gap: 8px;
  }
  .wrapup-meta {
    text-align: center;
    font-family: var(--serif); font-style: italic;
    font-size: 12px;
    color: var(--ink-4);
    letter-spacing: 0.04em;
  }
  @media (max-width: 520px) {
    .wrapup-grid { grid-template-columns: repeat(auto-fill, minmax(64px, 1fr)); gap: 6px; }
    .wrapup-tile-glyph { font-size: 18px; }
    .wrapup-tile-readings { font-size: 10px; }
    /* Collapse the three-column footer to a centered stack — flip-all
       above, nav below. Meta hides since the deck position is implied
       by reaching this screen at all. */
    .wrapup-footer {
      grid-template-columns: 1fr;
      justify-items: center;
      gap: 10px;
    }
    .wrapup-meta { display: none; }
  }
  .flash-kanji-inner {
    position: relative;
    display: inline-block;
  }
  .flash-swatch {
    position: absolute;
    left: -38px; top: 50%;
    transform: translateY(-50%) rotate(-12deg);
    width: 28px; height: 40px;
    opacity: 0.55;
    pointer-events: none;
  }
  .flash-swatch[data-light] {
    opacity: 0.7;
  }
  .flash-image {
    width: 100%;
    margin-bottom: 24px;
    border-radius: 4px;
    overflow: hidden;
  }
  /* When the inner image-slot is readonly and not yet filled (no shipped
     file), drop the container so the card doesn't carry an empty 24px
     placeholder gap above the kanji. */
  .flash-image:has(image-slot[readonly]:not([data-filled])) {
    display: none;
  }
  .flash-kanji {
    font-family: var(--serif-jp); font-weight: 700;
    font-size: clamp(56px, 10vw, 88px);
    color: var(--ink);
    text-align: center;
    line-height: 1;
    letter-spacing: 0.02em;
    white-space: nowrap;
  }
  /* Colors-class colorize: paint the kanji in the swatch hue. Inline
     style sets `color` on the span. The trailing 色 (rendered as a separate
     span without color) inherits the parent .flash-kanji ink so the compound
     reads as "<COLORED prefix> + ink 色". */
  .color-glyph {
    /* Color set inline via style="color:#xxx" — keep declarations minimal so
       nothing here overrides the swatch hue. */
    transition: color .25s ease;
  }
  /* White on cream is invisible — switch to an outlined glyph (transparent
     fill, paper-edge stroke) so the white card still reads. */
  .color-glyph.color-glyph-light {
    color: transparent;
    -webkit-text-stroke: 1.5px var(--gold-dark);
    text-stroke: 1.5px var(--gold-dark);
  }
  /* ── Heisig tooltip chip ── */
  .heisig-chip {
    display: inline-flex; align-items: center; gap: 5px;
    margin-top: 6px;
    padding: 3px 10px 3px 8px;
    font-family: 'SF Mono', 'Consolas', monospace; font-size: 11px;
    color: var(--ink-3); background: var(--paper-2);
    border: 1px solid var(--paper-edge); border-radius: 12px;
    cursor: help; position: relative;
    transition: color .15s, border-color .15s;
  }
  .heisig-chip:hover { color: var(--ink); border-color: var(--gold); }
  .heisig-chip .h-icon {
    font-weight: 700; font-size: 10px; color: var(--gold-dark);
    background: var(--paper); border: 1px solid var(--paper-edge);
    border-radius: 3px; padding: 1px 4px; line-height: 1;
  }
  .heisig-chip .h-kw {
    font-family: var(--serif); font-style: italic; font-size: 12px;
  }
  /* tooltip bubble */
  .heisig-tip {
    display: none; position: absolute; bottom: calc(100% + 8px);
    left: 50%; transform: translateX(-50%);
    width: max(280px, min(380px, 60vw));
    padding: 14px 16px;
    background: var(--paper); border: 1px solid var(--paper-edge);
    border-radius: 8px;
    box-shadow: 0 6px 24px rgba(60,40,10,0.12);
    z-index: 40;
    text-align: left;
  }
  .heisig-chip:hover .heisig-tip { display: block; }
  .heisig-tip::after {
    content: ''; position: absolute; top: 100%; left: 50%;
    transform: translateX(-50%);
    border: 6px solid transparent; border-top-color: var(--paper-edge);
  }
  .heisig-tip .ht-head {
    display: flex; align-items: baseline; gap: 8px;
    margin-bottom: 8px;
  }
  .heisig-tip .ht-frame {
    font-family: 'SF Mono', 'Consolas', monospace; font-size: 11px; color: var(--gold-dark);
    background: var(--paper-2); border: 1px solid var(--paper-edge);
    border-radius: 4px; padding: 2px 6px;
  }
  .heisig-tip .ht-kw {
    font-family: var(--serif); font-style: italic; font-size: 15px;
    font-weight: 500; color: var(--ink);
  }
  .heisig-tip .ht-story {
    font-family: var(--serif); font-size: 13px; line-height: 1.55;
    color: var(--ink-2);
  }
  .heisig-tip .ht-src {
    margin-top: 8px;
    font-family: var(--serif); font-style: italic; font-size: 10px;
    color: var(--ink-4);
  }
  /* ── Stroke count badge ── */
  .flash-strokes {
    display: inline-block;
    font-family: 'SF Mono', 'Consolas', monospace; font-size: 10px;
    color: var(--ink-3); background: var(--paper-2);
    border: 1px solid var(--paper-edge); border-radius: 10px;
    padding: 1px 7px; margin-left: 6px;
    vertical-align: 2px; letter-spacing: 0; font-style: normal;
  }
  /* ── Word examples ── */
  .flash-notes {
    margin-top: 16px; padding-top: 14px;
    border-top: 1px dashed rgba(141,102,48,0.25);
    font-family: var(--serif); font-size: 13px;
    color: var(--ink-2); line-height: 1.55;
  }
  .flash-notes > .eyebrow {
    display: block; font-family: var(--serif);
    font-size: 10px; text-transform: uppercase; letter-spacing: 0.1em;
    color: var(--ink-4); margin-bottom: 8px; font-style: normal;
  }
  .flash-examples {
    margin-top: 16px; padding-top: 14px;
    border-top: 1px dashed rgba(141,102,48,0.25);
  }
  .flash-examples > .eyebrow {
    display: block; font-family: var(--serif);
    font-size: 10px; text-transform: uppercase; letter-spacing: 0.1em;
    color: var(--ink-4); margin-bottom: 8px; font-style: normal;
  }
  .flash-ex-row {
    display: grid; grid-template-columns: auto 1fr auto;
    gap: 4px 14px; align-items: baseline; padding: 5px 0;
  }
  .flash-ex-row + .flash-ex-row {
    border-top: 1px solid rgba(141,102,48,0.08);
  }
  .ex-word {
    font-family: var(--serif-jp); font-size: 17px;
    color: var(--ink); letter-spacing: 0.03em;
  }
  .ex-reading {
    font-family: var(--serif); font-size: 13px;
    color: var(--ink-2); letter-spacing: 0.02em;
  }
  .ex-meaning {
    font-family: var(--serif); font-style: italic; font-size: 12px;
    color: var(--ink-3); text-align: right; white-space: nowrap;
  }
  /* ── Flash card footer (Heisig chip) ── */
  .flash-foot {
    margin-top: 14px; padding-top: 12px;
    border-top: 1px dashed rgba(141,102,48,0.25);
    display: flex; align-items: center; justify-content: space-between; gap: 8px;
  }
  .flash-foot:empty { display: none; }

  /* ── Radical breakdown card ─────────────────────────────────────────
     A different kind of flashcard. Same chrome (border, shadow, padding,
     580px deck width) but the body is a 3-row composition:
       1. the radical glyph paired with the source kanji it derives from
       2. title (japanese name · english label) + bilingual explanation
       3. 2×2 grid of example kanji that use the radical
     No image-slot — the layout is purely typographic. */
  .flash-card.radical-card {
    padding: 28px 32px;
  }
  .radical-head {
    display: flex; align-items: center; justify-content: center;
    gap: 22px;
    padding: 18px 0 14px;
  }
  .radical-glyph {
    font-family: var(--serif-jp); font-weight: 700;
    font-size: clamp(86px, 14vw, 124px);
    color: var(--ink);
    line-height: 1;
    letter-spacing: 0.02em;
  }
  /* Paired form — two derivative radicals from one source kanji (e.g. ナ · ヨ
     both from 手). Scaled down a touch so the pair doesn't crowd the source. */
  .radical-glyph.radical-glyph-pair {
    display: flex; align-items: center; gap: 10px;
    font-size: clamp(70px, 11vw, 100px);
  }
  .radical-glyph-pair .radical-sep {
    color: var(--gold-dark); opacity: 0.45;
    font-size: 0.35em;
    transform: translateY(-0.4em);
    font-weight: 400;
  }
  .radical-from {
    display: flex; flex-direction: column; align-items: center; gap: 6px;
    padding-top: 14px;
  }
  .radical-from .eyebrow {
    font-family: var(--serif); font-size: 10px; letter-spacing: 0.18em;
    text-transform: uppercase; color: var(--ink-4);
    font-style: italic;
  }
  .radical-from-glyph {
    font-family: var(--serif-jp); font-weight: 600;
    font-size: clamp(42px, 6vw, 58px);
    color: var(--ink-3);
    line-height: 1;
  }
  /* Multi-component variant — used when `from` is an array (e.g. 曜
     decomposes into 日 + 隹). Glyphs sit on a baseline with a small
     gold-tinted "+" between them. The font-size shrinks a touch so
     two-character pairs don't overflow the column. */
  .radical-from-glyph-multi {
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
    font-size: clamp(36px, 5vw, 50px);
  }
  .radical-from-sep {
    font-family: var(--serif);
    font-weight: 400;
    font-size: 0.55em;
    color: var(--gold-dark);
    margin: 0 2px;
  }
  .radical-arrow {
    font-family: var(--serif); font-size: 18px;
    color: var(--gold-dark);
    opacity: 0.55;
    align-self: center;
    margin-top: 10px;
    letter-spacing: 0.1em;
  }
  .radical-title {
    text-align: center;
    display: flex; flex-direction: column; align-items: center; gap: 4px;
    padding: 4px 0 14px;
    border-bottom: 1px solid rgba(141,102,48,0.18);
    margin-bottom: 16px;
  }
  .radical-title .ja {
    font-family: var(--serif-jp); font-weight: 600;
    font-size: 19px; color: var(--ink);
    letter-spacing: 0.02em;
  }
  .radical-title .en {
    font-family: var(--serif); font-style: italic;
    font-size: 12px; color: var(--ink-4);
    letter-spacing: 0.04em;
  }
  .radical-desc { display: flex; flex-direction: column; gap: 8px; margin-bottom: 18px; }
  .radical-desc p {
    margin: 0;
    text-align: center;
  }
  .radical-desc p.en {
    font-family: var(--serif); font-size: 13px;
    line-height: 1.55; color: var(--ink-2);
  }
  .radical-desc p.ja {
    font-family: var(--serif-jp); font-size: 13px;
    line-height: 1.7; color: var(--ink-3);
  }
  .radical-grid {
    display: grid; grid-template-columns: 1fr 1fr; gap: 10px;
  }
  /* Six examples render as a 3-column grid — keeps the card height bounded
     while still giving each example enough room for kanji + reading + gloss. */
  .radical-grid.radical-grid-6 {
    grid-template-columns: repeat(3, 1fr); gap: 8px;
  }
  .radical-grid.radical-grid-6 .radical-ex { padding: 11px 8px 10px; }
  .radical-grid.radical-grid-6 .rex-kanji { font-size: 36px; }
  .radical-grid.radical-grid-6 .rex-en { font-size: 11px; }
  .radical-ex {
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    border-radius: 4px;
    padding: 14px 12px 12px;
    display: flex; flex-direction: column; align-items: center; gap: 4px;
  }
  .radical-ex .rex-kanji {
    font-family: var(--serif-jp); font-weight: 700;
    font-size: 42px; line-height: 1; color: var(--ink);
    letter-spacing: 0.01em;
  }
  .radical-ex .rex-reading {
    display: flex; gap: 6px; align-items: baseline;
    font-family: var(--serif-jp); font-size: 12px;
    margin-top: 4px;
  }
  .radical-ex .rex-reading .kun { color: var(--ink-2); }
  .radical-ex .rex-reading .on  { color: var(--ink-4); font-size: 11px; letter-spacing: 0.05em; }
  .radical-ex .rex-reading .dot { color: var(--ink-5); }
  .radical-ex .rex-en {
    font-family: var(--serif); font-style: italic;
    font-size: 12px; color: var(--ink-3);
    text-align: center; line-height: 1.3;
    margin-top: 2px;
  }
  @media (max-width: 520px) {
    .radical-grid.radical-grid-6 { grid-template-columns: 1fr 1fr; }
  }
  @media (max-width: 420px) {
    .radical-grid { grid-template-columns: 1fr; }
    .radical-grid.radical-grid-6 { grid-template-columns: 1fr 1fr; }
  }

  /* Image-card variant — opted into via card.examplesAsImageCards.
     Mirrors the .dt-day-card visual from the Days & Time basics page:
     image-first 1:1 frame on top, then kanji compound + reading + EN
     stacked below. The auto-fill grid puts ~4 across on the editorial
     card width (cleanly accommodates the 7 day-of-week examples for
     the ◆曜 radical card). Tighter padding than the text-only variant
     because the image already carries most of the visual identity. */
  .radical-grid.radical-grid-img {
    grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
    gap: 8px;
  }
  .radical-grid.radical-grid-img .radical-ex {
    padding: 8px;
    gap: 3px;
  }
  .radical-ex-image {
    width: 100%;
    aspect-ratio: 1 / 1;
    background: var(--paper-2);
    border-radius: 6px;
    overflow: hidden;
    margin-bottom: 4px;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .radical-ex-image image-slot {
    display: block;
    width: 100%;
    height: 100%;
    border-radius: 6px;
  }
  .radical-ex-image image-slot::part(img),
  .radical-ex-image image-slot::part(frame) {
    border-radius: 6px;
  }
  /* Override the text-only kanji size — the kanji compound sits below
     the image now and shouldn't dominate. */
  .radical-grid.radical-grid-img .rex-kanji {
    font-size: 18px;
    font-weight: 600;
    line-height: 1.05;
    margin-top: 2px;
  }
  .radical-grid.radical-grid-img .rex-reading {
    font-size: 11px;
    margin-top: 2px;
  }
  .radical-grid.radical-grid-img .rex-en {
    font-size: 11px;
  }
  /* Mobile fallback — the auto-fill already adapts, but tighten the
     min for very narrow screens so we don't get 1-col single tiles. */
  @media (max-width: 420px) {
    .radical-grid.radical-grid-img {
      grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
    }
  }

  /* Radical card CTA — hands the learner off to another page in the
     app (typically a vocab/jougo explainer). Sits below the examples
     grid, above the card footer. Soft gold pill with a paper-2 fill
     so it reads as "more here" without competing visually with the
     primary teaching content. The two-line stack (JP big, EN italic
     small underneath) matches the testcard-nav-btn pattern used
     elsewhere on radical cards. */
  .radical-cta {
    display: flex; flex-direction: column; align-items: center;
    gap: 2px;
    margin: 18px auto 14px;
    padding: 11px 22px 10px;
    background: var(--paper-2);
    border: 1px solid var(--gold-soft);
    border-radius: 999px;
    cursor: pointer;
    color: var(--ink);
    transition:
      background .15s,
      border-color .15s,
      transform .08s,
      box-shadow .15s;
    box-shadow: 0 1px 0 var(--paper-edge);
    text-align: center;
  }
  .radical-cta:hover {
    background: var(--paper);
    border-color: var(--gold);
    box-shadow: 0 4px 12px rgba(141, 102, 48, 0.16);
    transform: translateY(-1px);
  }
  .radical-cta:active {
    transform: translateY(0);
    box-shadow: 0 1px 0 var(--paper-edge);
  }
  .radical-cta-ja {
    font-family: var(--serif-jp); font-weight: 600;
    font-size: 14px;
    color: var(--ink);
    letter-spacing: 0.04em;
    line-height: 1.2;
  }
  .radical-cta-en {
    font-family: var(--serif); font-style: italic;
    font-size: 11px;
    color: var(--gold-dark);
    line-height: 1.2;
  }

  .flash-reading {
    display: flex; justify-content: center; gap: 14px;
    margin-top: 12px;
    font-family: var(--serif-jp); font-size: 18px;
  }
  .flash-reading > span { white-space: nowrap; }
  .flash-reading .label {
    font-family: var(--serif); font-style: italic; font-size: 11px;
    color: var(--ink-3); margin-right: 4px; vertical-align: 4px;
  }
  .flash-reading .kun { color: var(--ink); }
  .flash-reading .on  { color: var(--ink-2); }
  .flash-en {
    text-align: center;
    margin-top: 22px;
    padding-top: 16px;
    border-top: 1px dashed rgba(141,102,48,0.25);
    font-family: var(--serif); font-style: italic; font-size: 13px;
    color: var(--ink-3);
    letter-spacing: 0.04em;
  }
  .flash-en .eyebrow {
    display: block; font-size: 10px; text-transform: uppercase; letter-spacing: 0.1em;
    color: var(--ink-4); margin-bottom: 4px; font-style: normal;
  }
  /* The bottom area of a flashcard. When the current card has no
     homophone peers, it's a single-column block holding just .flash-en
     (centered). When peers exist, it becomes a two-column grid with the
     meaning on the left and a small "also reads X" chip cluster on the
     right. The dashed divider moves from .flash-en up to .flash-bottom
     so both columns sit under one rule. */
  .flash-bottom {
    margin-top: 22px;
    padding-top: 16px;
    border-top: 1px dashed rgba(141,102,48,0.25);
    display: grid;
    grid-template-columns: 1fr;
  }
  .flash-bottom.has-right {
    grid-template-columns: 1fr auto;
    gap: 18px;
    align-items: start;
  }
  .flash-bottom .flash-en {
    margin-top: 0;
    padding-top: 0;
    border-top: none;
  }
  .flash-bottom.has-right .flash-en {
    text-align: left;
  }
  .flash-right {
    display: flex; flex-direction: column;
    gap: 14px;
    min-width: 130px;
  }
  .flash-usage {
    display: flex; flex-direction: column; gap: 2px;
  }
  .flash-usage .eyebrow {
    font-family: var(--serif);
    font-size: 10px; text-transform: uppercase; letter-spacing: 0.1em;
    color: var(--ink-4); font-style: normal;
    margin-bottom: 2px;
  }
  .flash-usage .usage-ja {
    font-family: var(--serif-jp);
    font-size: 18px; color: var(--ink);
    letter-spacing: 0.02em; line-height: 1.15;
  }
  .flash-usage .usage-kana {
    font-family: var(--serif-jp);
    font-size: 12px; color: var(--ink-3);
    letter-spacing: 0.04em;
  }
  .flash-related {
    display: flex; flex-direction: column;
    gap: 6px;
  }
  .flash-related .eyebrow {
    font-family: var(--serif);
    font-size: 10px; text-transform: uppercase; letter-spacing: 0.1em;
    color: var(--ink-4); font-style: normal;
  }
  .flash-related .chips {
    display: flex; flex-wrap: wrap; gap: 6px;
  }
  .related-chip {
    display: inline-flex; align-items: baseline; gap: 6px;
    padding: 5px 10px;
    border-radius: 6px;
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    cursor: pointer;
    font-family: var(--serif);
    transition: background .12s, border-color .12s, transform .12s;
  }
  .related-chip:hover {
    background: var(--gold-soft);
    border-color: var(--gold);
    transform: translateY(-1px);
  }
  .related-chip .rk {
    font-family: var(--serif-jp);
    font-size: 20px; line-height: 1;
    color: var(--ink);
  }
  .related-chip .re {
    font-style: italic; font-size: 11px;
    color: var(--ink-3);
  }
  @media (max-width: 480px) {
    .flash-bottom.has-right { grid-template-columns: 1fr; }
    .flash-bottom.has-right .flash-en { text-align: center; }
    .flash-right { align-items: center; }
    .flash-usage { align-items: center; text-align: center; }
    .flash-related { align-items: center; }
    .flash-related .chips { justify-content: center; }
  }
  .flash-controls {
    display: flex; align-items: center; justify-content: space-between;
    gap: 12px;
  }
  .flash-progress {
    flex: 1; height: 4px; border-radius: 2px;
    background: var(--paper-edge); position: relative;
    margin: 0 12px;
  }
  .flash-progress > div {
    position: absolute; left: 0; top: 0; bottom: 0;
    background: var(--gold); border-radius: 2px;
    transition: width .25s;
  }

  /* ─── dictionary ────────────────────────────────────────────────────── */
  .search-bar {
    display: flex; align-items: center; gap: 10px;
    background: var(--paper-2);
    border: 1px solid rgba(141,102,48,0.25);
    border-radius: 12px;
    padding: 12px 16px;
    margin-bottom: 16px;
    box-shadow: 0 1px 0 rgba(255,255,255,0.4) inset;
  }
  .search-bar input {
    flex: 1; border: none; outline: none; background: transparent;
    font-family: var(--serif-jp); font-size: 18px; color: var(--ink);
  }
  .search-bar input::placeholder { color: var(--ink-3); font-family: var(--serif); font-style: italic; }
  .filters {
    display: flex; flex-wrap: wrap; gap: 6px;
    margin-bottom: 28px;
  }
  /* Whole list lives inside a paper card so the rows read cleanly
     against the section background. Each row keeps its own
     border-bottom for the horizontal separator; the container fill is
     what isolates the text from the bg image behind. */
  .dict-list {
    display: flex; flex-direction: column; gap: 0;
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 8px;
    padding: 0 16px;
  }
  .dict-row {
    display: grid;
    grid-template-columns: 130px 1fr auto auto;
    column-gap: 32px;
    padding: 16px 8px;
    border-bottom: 1px solid var(--paper-edge);
    align-items: center;
    cursor: pointer;
    transition: background .12s;
  }
  .dict-row:last-child { border-bottom: none; }
  .dict-row:hover { background: rgba(180,138,74,0.08); }
  .dict-row .kanji-cell {
    display: flex; flex-direction: column; align-items: center;
    text-align: center; line-height: 1.1;
    font-family: var(--serif-jp);
  }
  .dict-row .kanji-cell .furigana {
    font-size: 22px;
    color: var(--ink-2);
    margin-bottom: 6px;
    letter-spacing: 0.02em;
  }
  .dict-row .kanji-cell .kanji {
    font-weight: 600; font-size: 32px;
    color: var(--ink); line-height: 1;
  }
  .dict-row .en {
    font-family: var(--serif); font-style: italic;
    font-size: 16px; color: var(--ink);
    padding-left: 96px;
  }
  .dict-row .meta {
    font-family: var(--serif); font-size: 11px; color: var(--ink-3); text-align: right;
  }
  .dict-row .meta .level {
    background: var(--gold-soft); color: var(--gold-dark);
    padding: 2px 6px; border-radius: 4px;
    font-weight: 600; letter-spacing: 0.04em;
  }
  /* JLPT level palette — applied site-wide via the .level-NX modifier on any
     chip-style level badge (.level on dictionary rows, .level-tag on
     sentence rows, plus any future surfaces). N5 sits quiet (easiest), then
     each subsequent level picks up a distinct ink hue. Tones are paper-
     period: soft pastel bg with a darker matching foreground so the badges
     read at a glance without screaming at the page. Color order maps
     difficulty → emotional weight: gold (familiar), blue (calm/water), red
     (warning), purple (rare/regal). */
  .level-N5 {
    background: var(--paper-2) !important;
    color: var(--ink-3) !important;
  }
  .level-N4 {
    background: var(--gold-soft) !important;
    color: var(--gold-dark) !important;
  }
  .level-N3 {
    background: #dbe7f3 !important;
    color: #1f4e87 !important;
  }
  .level-N2 {
    background: #f3dcd6 !important;
    color: #8a2818 !important;
  }
  .level-N1 {
    background: #e6d8f1 !important;
    color: #531889 !important;
  }
  @media (max-width: 640px) {
    .dict-row { grid-template-columns: 90px 1fr auto; column-gap: 16px; }
    .dict-row .kanji-cell .furigana { font-size: 16px; }
    .dict-row .kanji-cell .kanji { font-size: 26px; }
    .dict-row .en { padding-left: 0; font-size: 14px; }
    .dict-row .meta:last-child { display: none; }
  }
  .empty-state {
    padding: 60px 20px; text-align: center;
    color: var(--ink-3); font-style: italic; font-family: var(--serif);
  }

  /* ─── flash category sidebar ─────────────────────────────────────────── */
  .flash-sidebar {
    display: none;
    background: color-mix(in srgb, var(--paper-edge) 35%, var(--paper) 65%);
    border-right: 1px solid rgba(141,102,48,0.15);
    box-shadow: inset -6px 0 16px -12px rgba(100,72,30,0.10);
    padding: 16px 10px;
    position: sticky; top: 0; height: 100vh;
    overflow-y: auto; overflow-x: hidden;
  }
  .app.show-flash-sidebar .flash-sidebar { display: block; }
  .app.show-flash-sidebar { grid-template-columns: 188px 160px 1fr; }

  .flash-sidebar-head {
    font-family: var(--font-menu-en); font-style: italic;
    font-size: 11px; text-transform: uppercase;
    letter-spacing: 0.08em; color: var(--ink-4);
    padding: 4px 8px 10px;
    border-bottom: 1px dashed rgba(141,102,48,0.20);
    margin-bottom: 6px;
  }
  .cat-list {
    display: flex; flex-direction: column; gap: 2px;
    list-style: none; padding: 0; margin: 0;
  }
  .cat-item {
    display: flex; align-items: center; gap: 10px;
    padding: 8px 10px; border: none; background: transparent;
    cursor: pointer; color: var(--ink-3); width: 100%; text-align: left;
    border-radius: 0 6px 6px 0;
    transition: background .15s, color .15s;
  }
  .cat-item { position: relative; }    /* anchor for the active brush */
  .cat-item:hover { background: rgba(180,138,74,0.10); color: var(--ink-2); }
  /* Active state — subtle paper tint only, no gold inset stroke. The
     brush brush handles the "you're here" signal. */
  .cat-item.active {
    background: rgba(180,138,74,0.10);
    color: var(--ink);
  }
  .cat-item .cat-glyph {
    font-family: var(--font-menu-ja); font-weight: 600; font-size: 18px;
    width: 26px; height: 26px; display: flex; align-items: center;
    justify-content: center; color: var(--gold-dark); flex: 0 0 auto;
  }
  .cat-item.active .cat-glyph { color: var(--ink); }
  .cat-item .cat-label {
    display: flex; flex-direction: column; gap: 1px; line-height: 1.1;
    flex: 1; min-width: 0;
  }
  .cat-item .cat-ja { font-family: var(--font-menu-ja); font-size: 13px; }
  /* When the JA label contains multiple script names joined by ・, each piece
     renders as its own block so the sidebar text never breaks mid-word. */
  .cat-item .cat-ja > span { display: block; }
  .cat-item .cat-en {
    font-family: var(--font-menu-en); font-style: italic; font-size: 11px; color: var(--ink-3);
  }
  .cat-item.active .cat-en { color: var(--ink-2); }

  /* ── Interactive (Experience) sidebar variant ─────────────────────────
     Visually identical to the plain .cat-item — no card chrome, no
     bordered tile, no gold-tint active fill. The active state is
     signaled SOLELY by the brushstroke (has-bg-brush), the same way
     every other selection in the sidebar tree works. The .cat-item-exp
     class is kept on the markup because the section divider needs to
     know which items belong to the interactive cluster, but it carries
     no visual treatment of its own beyond what cat-item already does.
     The previous bordered-card style read as outdated against the
     calligraphy-driven aesthetic everywhere else.

     If you want the experiences to stand out visually from regular
     vocab books later, the header "INTERACTIVE" above already does
     that — adding chrome here would compete with the brushstroke.

     One subtle override stays: the glyph uses gold-dark by default
     so the kanji eyebrow reads as a label rather than as the same-
     weight ink the regular vocab uses. Active state restores ink
     because the brushstroke does the highlight work. */
  .cat-item-exp .cat-glyph { color: var(--gold-dark); }
  .cat-item-exp.active .cat-glyph { color: var(--ink); }
  /* Section header for the interactive cluster */
  .cat-list-exp { margin-bottom: 6px; }
  /* Dashed dividing rule above the regular books section so the eye reads
     them as a separate group. */
  .flash-sidebar-head.with-divider {
    margin-top: 14px;
    padding-top: 10px;
    border-top: 1px dashed rgba(141,102,48,0.20);
  }

  /* Hide the horizontal class-strip on desktop when sidebar is visible */
  @media (min-width: 761px) {
    .app.show-flash-sidebar .class-strip { display: none; }
  }
  @media (max-width: 760px) {
    .flash-sidebar { display: none !important; }
    .app.show-flash-sidebar { grid-template-columns: 1fr; }
  }

  /* ─── writing sidebar (reuses cat-* styles from flash sidebar) ─────── */
  .writing-sidebar {
    display: none;
    background: color-mix(in srgb, var(--paper-edge) 35%, var(--paper) 65%);
    border-right: 1px solid rgba(141,102,48,0.15);
    box-shadow: inset -6px 0 16px -12px rgba(100,72,30,0.10);
    padding: 16px 10px;
    position: sticky; top: 0; height: 100vh;
    overflow-y: auto; overflow-x: hidden;
  }
  .app.show-writing-sidebar .writing-sidebar { display: block; }
  .app.show-writing-sidebar { grid-template-columns: 188px 160px 1fr; }
  @media (max-width: 760px) {
    .writing-sidebar { display: none !important; }
    .app.show-writing-sidebar { grid-template-columns: 1fr; }
  }

  /* Library hub — nested sidebar (Search · Dictionary · Books). Mirrors
     the writing-sidebar treatment. */
  .library-sidebar {
    display: none;
    background: color-mix(in srgb, var(--paper-edge) 35%, var(--paper) 65%);
    border-right: 1px solid rgba(141,102,48,0.15);
    box-shadow: inset -6px 0 16px -12px rgba(100,72,30,0.10);
    padding: 16px 10px;
    position: sticky; top: 0; height: 100vh;
    overflow-y: auto; overflow-x: hidden;
  }
  .app.show-library-sidebar .library-sidebar { display: block; }
  .app.show-library-sidebar { grid-template-columns: 188px 160px 1fr; }
  @media (max-width: 760px) {
    .library-sidebar { display: none !important; }
    .app.show-library-sidebar { grid-template-columns: 1fr; }
  }

  /* ─── Books page — ported from the portfolio Library (books.html /
     styles.css 4673–5158). Scoped under .books-page; the portfolio's
     own design tokens are remapped to the app's parchment+ink palette
     locally so the 3D open-cover treatment (KKEdXOa flip) lands
     identically. Masthead/title clamps are trimmed because the in-app
     content column is narrower than the full portfolio page. */
  .books-page {
    --rule: color-mix(in srgb, var(--ink) 12%, transparent);
    --accent: var(--accent-vermilion, #c34b30);
    --ink-faintest: color-mix(in srgb, var(--ink) 10%, transparent);
    --ink-2: var(--ink-soft);
    --paper-deep: color-mix(in srgb, var(--ink) 6%, var(--paper));
    --serif: var(--font-body);
    --mono: var(--font-menu-en);
    --m-fast: .15s;
    min-height: 60vh;
    padding: 0 0 72px;
  }

  /* masthead */
  .books-page .library-head { padding: 8px 0 32px; position: relative; }
  .books-page .library-crumbs {
    font-family: var(--mono);
    font-size: 11px;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--ink-faint);
    display: flex;
    gap: 10px;
    align-items: center;
    margin-bottom: 22px;
  }
  .books-page .library-crumbs .sep { color: var(--ink-faintest); }
  .books-page .library-title-grid {
    display: grid;
    grid-template-columns: 1.2fr 0.8fr;
    gap: 48px;
    align-items: end;
  }
  .books-page .library-h1 {
    font-family: var(--serif);
    font-size: clamp(40px, 6vw, 72px);
    line-height: 0.95;
    letter-spacing: -0.025em;
    font-weight: 400;
    margin: 0;
    text-wrap: balance;
  }
  .books-page .library-h1 em { font-style: italic; color: var(--accent); font-weight: 400; }
  .books-page .library-blurb {
    font-family: var(--serif);
    font-size: 17px;
    line-height: 1.55;
    color: var(--ink-soft);
    text-wrap: pretty;
    border-left: 1px solid var(--rule);
    padding-left: 24px;
    margin: 0;
  }
  .books-page .library-blurb em { font-style: italic; color: var(--accent); }

  /* shelf section */
  .books-page .shelf { padding: 48px 0 0; }
  .books-page .shelf-head {
    display: grid;
    grid-template-columns: 80px 1fr auto;
    gap: 28px;
    align-items: center;
    margin-bottom: 16px;
  }
  .books-page .shelf-rule { height: 1px; background: var(--rule); border: 0; margin: 0; }
  .books-page .shelf-title {
    font-family: var(--serif);
    font-size: clamp(26px, 3.5vw, 40px);
    line-height: 1;
    font-weight: 400;
    letter-spacing: -0.015em;
    margin: 0;
  }
  .books-page .shelf-title em { font-style: italic; color: var(--accent); font-weight: 400; }
  .books-page .shelf-meta {
    font-family: var(--mono);
    font-size: 11px;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--ink-faint);
    text-align: right;
    line-height: 1.7;
    display: flex;
    flex-direction: column;
    gap: 2px;
  }
  .books-page .shelf-meta .v { color: var(--ink); }
  .books-page .shelf-meta-sub {
    text-transform: none;
    letter-spacing: 0.04em;
    font-style: italic;
    font-family: var(--serif);
    font-size: 13px;
    color: var(--ink-soft);
  }

  /* static expanded shelf — books wrap into a multi-row grid (no Embla) */
  .books-page .shelf-grid-wrap { position: relative; }
  .books-page .shelf-grid { overflow: visible; padding: 24px 0 32px; }
  .books-page .embla__container {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: flex-start;
    gap: 8px;
    row-gap: 24px;
  }

  /* single book — KKEdXOa "3D Book Flip Cover" (cover + page-stack inset) */
  .books-page .book-items {
    position: relative;
    cursor: pointer;
    padding: 16px;
    margin: 0;
    display: grid;
    flex: 0 0 auto;
    border: 0;
    background: transparent;
    min-height: 320px;
    align-items: end;
    font: inherit;
    color: inherit;
  }
  .books-page .book-items:focus { outline: none; }
  .books-page .book-items:focus-visible .book-image {
    outline: 2px solid var(--accent);
    outline-offset: 4px;
  }
  .books-page .main-book-wrap {
    position: relative;
    width: var(--cw, 187px);
    height: var(--bh, 280px);
  }
  .books-page .book-cover { position: relative; width: 100%; height: 100%; }
  .books-page .book-cover .book-inside {
    position: absolute;
    width: 90%;
    height: 96%;
    top: 1%;
    left: 16px;
    border: 1px solid var(--ink-faintest);
    border-radius: 2px 6px 6px 2px;
    background: var(--paper);
    box-shadow:
      10px 40px 40px -10px rgba(26, 24, 21, 0.20),
      inset -2px 0 0 var(--ink-faintest),
      inset -3px 0 0 var(--paper-deep),
      inset -4px 0 0 var(--paper),
      inset -5px 0 0 var(--paper-deep),
      inset -6px 0 0 var(--paper),
      inset -7px 0 0 var(--paper-deep),
      inset -8px 0 0 var(--paper),
      inset -9px 0 0 var(--paper-deep);
  }
  .books-page .book-cover .book-image {
    line-height: 0;
    position: relative;
    border-radius: 2px 6px 6px 2px;
    box-shadow:
      6px 6px 18px -2px rgba(0, 0, 0, 0.2),
      24px 28px 40px -6px rgba(0, 0, 0, 0.1);
    transition: all 300ms ease-in-out;
    transform: perspective(2000px) rotateY(-15deg) translateX(-10px) scaleX(0.94);
    cursor: pointer;
  }
  .books-page .book-image img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: 2px 6px 6px 2px;
  }
  .books-page .book-items:hover .book-image,
  .books-page .book-items:focus-visible .book-image {
    transform: perspective(2000px) rotateY(0deg) translateX(0px) scaleX(1);
    transform-style: preserve-3d;
    box-shadow:
      6px 6px 12px -1px rgba(0, 0, 0, 0.1),
      20px 14px 16px -6px rgba(0, 0, 0, 0.1);
  }
  .books-page .effect {
    position: absolute;
    width: 20px;
    height: 100%;
    margin-left: 16px;
    top: 0;
    border-left: 2px solid rgba(0, 0, 0, 0.06);
    background-image: linear-gradient(90deg,
      rgba(255, 255, 255, 0.2) 0%,
      rgba(255, 255, 255, 0) 100%);
    transition: all 500ms ease;
    z-index: 5;
  }
  .books-page .book-items:hover .effect { margin-left: 14px; }
  .books-page .light {
    width: 90%;
    height: 100%;
    position: absolute;
    border-radius: 3px;
    background-image: linear-gradient(90deg,
      rgba(255, 255, 255, 0) 0%,
      rgba(255, 255, 255, 0.2) 100%);
    top: 0;
    right: 0;
    opacity: 0.1;
    transition: all 500ms ease;
    z-index: 4;
  }

  /* small English gloss under the JP masthead title */
  .books-page .library-recommend-en {
    font-family: var(--font-menu-en);
    font-size: 12px;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--ink-faint);
    margin: 14px 0 0;
  }

  /* ─── editorial detail modal (click a cover) ─────────────────────────
     A large, scrollable popover that presents each book as a short
     editorial spread: a header, then stacked sections that switch layout
     to suit the page scans — two-column (image 60 / text 40), full-bleed
     image, or full-width text. Portrait scans are height-bounded and
     centred; landscape spreads fill the width. */
  .book-modal {
    border: 0;
    padding: 0;
    background: transparent;
    color: var(--ink);
    width: min(1080px, 94vw);
    max-height: 90vh;
    overflow: hidden;
    position: relative;
  }
  .book-modal::backdrop { background: rgba(26, 24, 21, 0.60); }
  .book-modal[open] { animation: book-dialog-in 320ms cubic-bezier(0.22, 1, 0.36, 1) both; }
  @keyframes book-dialog-in {
    from { transform: translateY(14px) scale(0.985); opacity: 0; }
    to   { transform: translateY(0)    scale(1);     opacity: 1; }
  }
  .book-modal-scroll {
    background: var(--paper);
    border: 1px solid color-mix(in srgb, var(--ink) 12%, transparent);
    box-shadow: 0 24px 60px color-mix(in srgb, var(--ink) 30%, transparent);
    max-height: 90vh;
    overflow-y: auto;
    overflow-x: hidden;
  }
  .book-modal-close {
    position: absolute;
    top: 14px; right: 14px;
    width: 44px; height: 44px;
    border: 1px solid color-mix(in srgb, var(--ink) 14%, transparent);
    background: color-mix(in srgb, var(--paper) 88%, transparent);
    backdrop-filter: blur(4px);
    border-radius: 2px;
    display: flex; align-items: center; justify-content: center;
    font-family: var(--font-menu-en);
    font-size: 22px; line-height: 1;
    cursor: pointer; z-index: 3; color: var(--ink);
    transition: background .15s, color .15s;
  }
  .book-modal-close:hover { background: var(--ink); color: var(--paper); }
  .book-modal-close:focus-visible { outline: 2px solid var(--accent-vermilion, #c34b30); outline-offset: 2px; }
  .book-modal-head {
    padding: 52px 56px 28px;
    border-bottom: 1px solid color-mix(in srgb, var(--ink) 10%, transparent);
  }
  .book-modal-eyebrow {
    font-family: var(--font-menu-en);
    font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase;
    color: var(--ink-faint); margin: 0 0 14px;
  }
  .book-modal-title {
    font-family: var(--font-body);
    font-size: clamp(28px, 3.4vw, 44px);
    line-height: 1.04; letter-spacing: -0.02em; font-weight: 400;
    margin: 0; text-wrap: balance; color: var(--ink);
  }
  .book-modal-author {
    font-family: var(--font-body); font-size: 18px; font-style: italic;
    color: var(--ink-soft); margin: 10px 0 0;
  }
  .book-modal-tagline {
    font-family: var(--font-body); font-size: 18px; line-height: 1.5;
    color: var(--ink-soft); margin: 18px 0 0; max-width: 60ch; text-wrap: pretty;
    border-left: 2px solid var(--accent-vermilion, #c34b30); padding-left: 18px;
  }
  .book-modal-sections { display: grid; gap: 0; }
  .book-modal-section { padding: 40px 56px; }
  .book-modal-section + .book-modal-section {
    border-top: 1px solid color-mix(in srgb, var(--ink) 8%, transparent);
  }
  .book-modal-section[data-layout="two-col"] {
    display: grid; grid-template-columns: 60% 40%; gap: 40px; align-items: center;
  }
  .book-modal-section[data-layout="two-col"][data-image-side="right"] .bms-figure { order: 2; }
  .bms-figure { margin: 0; }
  .bms-figure img {
    display: block; width: 100%; height: auto;
    border: 1px solid color-mix(in srgb, var(--ink) 12%, transparent);
    border-radius: 2px;
    box-shadow: 0 10px 30px color-mix(in srgb, var(--ink) 12%, transparent);
  }
  .bms-cap {
    margin: 12px 0 0; font-family: var(--font-body); font-size: 13px;
    font-style: italic; line-height: 1.5; color: var(--ink-faint); text-wrap: pretty;
  }
  .book-modal-section[data-layout="full-image"] { display: block; }
  .book-modal-section[data-layout="full-image"] .bms-figure { text-align: center; }
  .book-modal-section[data-layout="full-image"] .bms-figure img {
    width: auto; max-width: 100%; max-height: 78vh; margin: 0 auto;
  }
  .book-modal-section[data-layout="full-text"] .bms-text { max-width: 68ch; margin: 0 auto; }
  .bms-heading {
    font-family: var(--font-body); font-size: clamp(20px, 2vw, 26px);
    line-height: 1.15; letter-spacing: -0.01em; font-weight: 400;
    margin: 0 0 12px; color: var(--ink);
  }
  .bms-body {
    font-family: var(--font-body); font-size: 16px; line-height: 1.62;
    color: var(--ink-soft); text-wrap: pretty;
  }
  .bms-body em { font-style: italic; color: var(--accent-vermilion, #c34b30); }

  @media (prefers-reduced-motion: reduce) {
    .books-page .book-cover .book-image,
    .books-page .effect,
    .books-page .light { transition: none; }
    .book-modal[open] { animation: none; }
  }
  @media (max-width: 760px) {
    .books-page .library-head { padding: 8px 0 24px; }
    .book-modal { width: 96vw; max-height: 92vh; }
    .book-modal-scroll { max-height: 92vh; }
    .book-modal-head { padding: 44px 24px 22px; }
    .book-modal-section { padding: 28px 24px; }
    .book-modal-section[data-layout="two-col"] { grid-template-columns: 1fr; gap: 20px; }
    .book-modal-section[data-layout="two-col"][data-image-side="right"] .bms-figure { order: 0; }
    .book-modal-section[data-layout="full-image"] .bms-figure img { max-height: 66vh; }
  }

  /* ─── speaking sidebar (2nd column when on Speaking) ──────────────────
     Mirrors writing-sidebar styling. Shows the category list (Food first,
     later Greetings/Travel/Work). Phrase navigation happens inside the
     main column via the Up Next filmstrip — no third sidebar here. */
  .speaking-sidebar {
    display: none;
    background: color-mix(in srgb, var(--paper-edge) 22%, var(--paper) 78%);
    border-right: 1px solid rgba(141,102,48,0.15);
    box-shadow: inset -6px 0 16px -12px rgba(100,72,30,0.10);
    padding: 16px 10px;
    position: sticky; top: 0; height: 100vh;
    overflow-y: auto; overflow-x: hidden;
  }
  .app.show-speaking-sidebar .speaking-sidebar { display: block; }
  .app.show-speaking-sidebar { grid-template-columns: 188px 160px 1fr; }
  @media (max-width: 760px) {
    .speaking-sidebar { display: none !important; }
    .app.show-speaking-sidebar { grid-template-columns: 1fr; }
  }

  /* ─── particles sidebar (3rd column when on Writing → Particles) ──────
     Mirrors vocab-books-sidebar visually: slightly lighter wash than the
     parent writing-sidebar so the eye reads the cascade left-to-right:
     nav → writing-sections → particles-list. Only visible when both the
     writing section AND the particles page are active. */
  .particles-sidebar {
    display: none;
    background: color-mix(in srgb, var(--paper-edge) 20%, var(--paper) 80%);
    border-right: 1px solid rgba(141,102,48,0.15);
    padding: 16px 10px;
    position: sticky; top: 0; height: 100vh;
    overflow-y: auto; overflow-x: hidden;
  }
  .app.show-particles-sidebar .particles-sidebar { display: block; }
  .app.show-particles-sidebar { grid-template-columns: 188px 160px 160px 1fr; }
  @media (max-width: 1100px) {
    .app.show-particles-sidebar { grid-template-columns: 188px 160px 1fr; }
    .particles-sidebar { display: none !important; }
  }
  @media (max-width: 760px) {
    .app.show-particles-sidebar { grid-template-columns: 1fr; }
  }

  /* Each cat-item in the particles list takes a colored accent stripe from
     the particle's own color (--pc). Active state replaces the gold-accent
     with the particle color so the visual link between sidebar and lesson
     is immediate. */
  .particles-sidebar .cat-item {
    position: relative;
    overflow: hidden;
  }
  .particles-sidebar .cat-item[data-particle-key]::before {
    content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
    background: var(--pc, var(--gold-soft));
    opacity: 0.6;
    transition: opacity .15s;
  }
  .particles-sidebar .cat-item[data-particle-key]:hover::before { opacity: 1; }
  .particles-sidebar .cat-item.active[data-particle-key]::before { opacity: 1; }
  .particles-sidebar .cat-item.active[data-particle-key] .cat-glyph {
    color: var(--pc, var(--gold-dark));
    border-color: var(--pc, var(--gold-dark));
  }
  /* When the particle-brush is firing, hide the accent stripe — the
     brush IS the selection signal now, and the colored stripe would
     visually cut across the sumi ink. !important here overrides the
     .active::before opacity:1 (same specificity, but !important wins
     unconditionally). The existing .15s opacity transition (defined
     on ::before above) smoothly fades the stripe out as the brush
     wipes in, then back in if the user navigates away. */
  .particles-sidebar .cat-item.has-particle-brush[data-particle-key]::before {
    opacity: 0 !important;
    background: transparent !important;
  }
  /* 2-char particles (から, まで) need a tighter font to keep both kana on
     a single line inside the 26×26 glyph tile. Single chars (は, が, …)
     keep the default 18px. */
  .particles-sidebar .cat-glyph-long {
    font-size: 11px;
    letter-spacing: -0.04em;
    white-space: nowrap;
  }

  /* ─── vocab category sidebar (reuses cat-* styles) ──────────────────── */
  .vocab-sidebar, .vocab-books-sidebar {
    display: none;
    background: color-mix(in srgb, var(--paper-edge) 35%, var(--paper) 65%);
    border-right: 1px solid rgba(141,102,48,0.15);
    box-shadow: inset -6px 0 16px -12px rgba(100,72,30,0.10);
    padding: 16px 10px;
    position: sticky; top: 0; height: 100vh;
    overflow-y: auto; overflow-x: hidden;
  }
  /* The third sidebar (books inside the current category) gets a slightly
     lighter wash so the eye reads the cascade left-to-right: nav → category
     → book. Plus a subtle dashed inner border on the left to mark the
     "drilling deeper" relationship to the category column. */
  .vocab-books-sidebar {
    background: color-mix(in srgb, var(--paper-edge) 20%, var(--paper) 80%);
    box-shadow: none;
  }
  .app.show-vocab-sidebar .vocab-sidebar,
  .app.show-vocab-sidebar .vocab-books-sidebar { display: block; }
  /* Four-column layout: main-nav → categories → books → content. The middle
     two are tight so the content column gets the most room. */
  .app.show-vocab-sidebar { grid-template-columns: 188px 160px 160px 1fr; }
  /* Hide the horizontal class-strip + inline book-strip on desktop when
     the sidebars are visible — they would be redundant. */
  @media (min-width: 901px) {
    .app.show-vocab-sidebar .class-strip,
    .app.show-vocab-sidebar .book-strip { display: none; }
  }
  /* Mid-width laptops — drop the books sidebar to keep content readable. */
  @media (max-width: 1100px) {
    .app.show-vocab-sidebar { grid-template-columns: 188px 160px 1fr; }
    .vocab-books-sidebar { display: none !important; }
  }
  @media (max-width: 760px) {
    .vocab-sidebar { display: none !important; }
    .app.show-vocab-sidebar { grid-template-columns: 1fr; }
  }

  /* ─── food vocabulary gallery ─────────────────────────────────────────
     Lives as page 2 of the Experience book. A sectioned grid of food
     SVGs (ramen / sushi / omakase / yakitori / dumplings / drinks /
     other). Card = square paper panel with the SVG + JP name + EN
     gloss. The SVGs use currentColor + var(--paper); the panel sets
     them so they inherit the ink tone automatically — no per-SVG
     coloring needed. */
  /* ── Fast Food hub ─────────────────────────────────────────────
     Card selector across the top (one card per chain), then the
     experience-launch button, then the standard cheatsheet underneath.
     The selector replaces the usual bottom pager dots — chains are
     visual enough that a card list reads faster than a dot row. */
  .ff-hub {
    display: flex; flex-direction: column;
    gap: 14px;
  }
  /* Selector defaults to auto-fit when the column count isn't fixed.
     Single-chain hubs never render the selector, so this is only used
     by multi-chain hubs (Fast Food = 2, Street Food = 4). The
     ff-selector-cols-N variants force the row count so 4 cards never
     wrap on a wide desktop. */
  .ff-selector {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 10px;
  }
  .ff-selector-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ff-selector-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .ff-selector-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  @media (max-width: 760px) {
    /* Allow wrap on phones — 4 cards become 2x2. */
    .ff-selector-cols-3,
    .ff-selector-cols-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  }
  @media (max-width: 480px) {
    .ff-selector-cols-2,
    .ff-selector-cols-3,
    .ff-selector-cols-4 { grid-template-columns: 1fr; }
  }
  .ff-card {
    display: grid;
    grid-template-columns: 80px 1fr;
    align-items: center;
    gap: 12px;
    padding: 8px 14px 8px 8px;
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    border-radius: 6px;
    cursor: pointer;
    font: inherit; color: inherit; text-align: left;
    transition: background .12s, border-color .12s, box-shadow .12s, transform .08s;
  }
  .ff-card:hover {
    background: color-mix(in srgb, var(--gold-soft) 16%, var(--paper));
    border-color: var(--gold-soft);
    transform: translateY(-1px);
    box-shadow: 0 4px 14px rgba(100,72,30,0.10);
  }
  .ff-card.is-active {
    background: var(--gold-soft);
    border-color: var(--gold-dark);
    box-shadow: inset 0 0 0 1px var(--gold-dark);
  }
  .ff-card-img {
    display: block;
    width: 80px; height: 56px;  /* ~16:9 thumb */
    border-radius: 4px;
    overflow: hidden;
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
  }
  .ff-card-img img {
    width: 100%; height: 100%;
    object-fit: cover;
    display: block;
  }
  .ff-card-name {
    display: flex; flex-direction: column;
    line-height: 1.2;
    min-width: 0;
  }
  .ff-card-name .ja {
    font-family: var(--serif-jp); font-weight: 600;
    font-size: 14px;
    color: var(--ink);
  }
  .ff-card-name .en {
    font-family: var(--serif); font-style: italic;
    font-size: 11px;
    color: var(--ink-3);
  }
  .ff-card.is-active .ff-card-name .ja { color: var(--ink); }
  .ff-card.is-active .ff-card-name .en { color: var(--gold-dark); }
  /* Experience launch button — lives inside the cheatsheet's image
     column (above the title), aligned to the left. `align-self:
     flex-start` keeps it from stretching across the column. */
  .ff-experience-btn {
    align-self: flex-start;
    display: inline-flex; flex-direction: column; align-items: flex-start;
    gap: 2px;
    padding: 10px 18px;
    background: var(--ink);
    color: var(--paper);
    border: none;
    border-radius: 5px;
    cursor: pointer;
    font: inherit;
    letter-spacing: 0.02em;
    transition: background .15s, transform .08s;
  }
  .ff-experience-btn:hover {
    background: color-mix(in srgb, var(--ink) 80%, var(--gold-dark) 20%);
  }
  .ff-experience-btn:active { transform: translateY(1px); }
  .ff-experience-ja {
    font-family: var(--serif-jp); font-weight: 600;
    font-size: 15px;
  }
  .ff-experience-en {
    font-family: var(--serif); font-style: italic;
    font-size: 11px;
    color: color-mix(in srgb, var(--paper) 70%, transparent);
  }
  /* Header row inside the cheatsheet's image column — used when a
     `prefix` is passed to cheatsheetHTML (Fast Food hub). Title sits
     on the left edge, button sits on the right edge, the slack lives
     between them (justify-content: space-between). Each side gets a
     comfortable margin so the title and button don't crowd against
     the card's interior edges. Wraps on narrow viewports. */
  .sheet-head-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 16px;
    padding: 4px 0;
  }
  .sheet-head-row .book-title {
    margin: 0 0 0 8px;
    padding: 0;
    border-bottom: none;
    text-align: left;
    min-width: 0;
  }
  .sheet-head-row .ff-experience-btn {
    margin-right: 8px;
  }
  @media (max-width: 640px) {
    .ff-selector { grid-template-columns: 1fr; }
    .ff-experience-btn { width: 100%; align-self: stretch; }
  }

  /* ── Explanation page (intro to a category) ─────────────────────
     Used by Sushi / Omakase / Izakaya / Ramen. JP-led paragraphs with
     a faded EN gloss below each, framed around a 16:9 hero image.
     Header row mirrors the cheatsheet pattern so navigating from page
     1 to page 2 to page 3 within the same book keeps the eye anchored. */
  .book-frame.intro-frame {
    display: flex; flex-direction: column;
    gap: 18px;
  }
  .intro-head .book-title { display: flex; flex-direction: column; gap: 2px; align-items: flex-start; }
  .intro-title-ja {
    font-family: var(--serif-jp); font-weight: 700;
    font-size: clamp(22px, 2.6vw, 30px);
    color: var(--ink);
    line-height: 1.15;
  }
  .intro-title-en {
    font-family: var(--serif); font-style: italic;
    font-size: 13px;
    color: var(--ink-3);
  }
  /* Cover image renders at its NATURAL aspect ratio at full width — no
     forced 16:9 crop. The covers carry baked-in titles / text that must
     stay legible, so cropping the top or sides isn\'t acceptable. We cap
     max-height so an unusually tall image can\'t dominate the page; the
     image-slot itself scrolls within the cheatsheet column if needed. */
  .intro-hero {
    width: 100%;
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 8px;
    overflow: hidden;
    box-shadow: 0 4px 16px rgba(60,40,10,0.08);
    display: flex;
    justify-content: center;
  }
  .intro-hero img {
    display: block;
    width: 100%;
    height: auto;
    max-height: 56vh;
    object-fit: contain;
  }
  .intro-hero img[data-placeholder] {
    opacity: 0.7;
    max-height: 40vh;
  }
  .intro-body {
    display: flex; flex-direction: column;
    gap: 18px;
    max-width: 720px;
  }
  .intro-section { padding: 0; }
  .intro-ja {
    margin: 0;
    font-family: var(--serif-jp);
    font-size: 16px;
    line-height: 1.8;
    color: var(--ink);
    letter-spacing: 0.01em;
  }
  .intro-en {
    margin: 6px 0 0;
    padding-top: 6px;
    border-top: 1px dotted rgba(141,102,48,0.16);
    font-family: var(--serif); font-style: italic;
    font-size: 12px;
    line-height: 1.55;
    color: var(--ink-4);
  }

  /* ── Menu-reference page ─────────────────────────────────────────
     Scrollable list of typical dishes for the category. Each row has
     a small SVG thumb, JP name, kana, dotted leader, EN gloss, and
     price chip on the right. Pure reference — no clickability. */
  .book-frame.menu-ref-frame {
    display: flex; flex-direction: column;
    gap: 14px;
  }
  .menu-ref-title { display: flex; flex-direction: column; gap: 2px; align-items: flex-start; }
  .menu-ref-list {
    list-style: none;
    padding: 0;
    margin: 0;
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    border-radius: 6px;
    /* Page itself scrolls (see the :has(.menu-ref-frame) lift above),
       so the list grows naturally — no internal max-height to avoid
       a confusing inside-the-card scroll. */
  }
  .menu-ref-row {
    display: grid;
    grid-template-columns: 48px minmax(0, auto) minmax(0, auto) minmax(0, 1fr) minmax(0, auto) minmax(0, auto);
    align-items: baseline;
    gap: 10px;
    padding: 10px 16px;
    border-bottom: 1px dotted rgba(141,102,48,0.16);
  }
  .menu-ref-row:last-child { border-bottom: none; }
  .menu-ref-svg {
    grid-column: 1;
    display: flex; align-items: center; justify-content: center;
    width: 48px; height: 36px;
    color: var(--ink-2);
    flex-shrink: 0;
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 3px;
    padding: 4px;
  }
  .menu-ref-svg img {
    width: 100%; height: 100%;
    object-fit: contain;
    display: block;
  }
  /* PNG food photos carry a white (or near-white) background. Multiply
     blend dissolves that white into the paper-2 frame fill so only the
     food's pigment registers — no need to pre-mask transparent backgrounds.
     Scoped to .webp via attribute selector so existing line-art SVGs (which
     are already transparent) keep their normal blending. (Was scoped to
     .png before the optimize-images pass converted all raster sources;
     SVG fallbacks still match by ending in .svg, which this rule excludes.) */
  .menu-ref-svg img[src$=".webp"] {
    mix-blend-mode: multiply;
  }
  .menu-ref-svg-empty {
    font-family: var(--serif); color: var(--ink-4);
    font-size: 16px;
    opacity: 0.5;
  }
  .menu-ref-kanji {
    grid-column: 2;
    font-family: var(--serif-jp); font-size: 16px;
    color: var(--ink); font-weight: 500;
    white-space: nowrap;
  }
  .menu-ref-kana {
    grid-column: 3;
    font-family: var(--serif-jp); font-size: 12px;
    color: var(--ink-4);
    white-space: nowrap;
  }
  .menu-ref-dots {
    grid-column: 4;
    border-bottom: 1px dotted var(--paper-edge);
    height: 1px;
    align-self: end;
    margin-bottom: 6px;
  }
  .menu-ref-en {
    grid-column: 5;
    font-family: var(--serif); font-style: italic;
    font-size: 12px;
    color: var(--ink-3);
    text-align: right;
    white-space: nowrap;
  }
  .menu-ref-price {
    grid-column: 6;
    font-family: var(--serif); font-size: 14px;
    color: var(--ink-2);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    min-width: 52px;
    text-align: right;
  }
  @media (max-width: 640px) {
    .menu-ref-row {
      grid-template-columns: 40px minmax(0, 1fr) minmax(0, auto);
      gap: 8px;
    }
    .menu-ref-svg { width: 40px; height: 30px; }
    .menu-ref-kanji { grid-column: 2; grid-row: 1; }
    .menu-ref-kana  { grid-column: 2; grid-row: 2; font-size: 11px; }
    .menu-ref-dots  { display: none; }
    .menu-ref-en    { grid-column: 2; grid-row: 3; text-align: left; }
    .menu-ref-price { grid-column: 3; grid-row: 1 / span 3; align-self: center; }
  }

  .book-frame.food-gallery-frame {
    /* Standard book-frame chrome, but with restraint on its own top
       padding — the page eyebrow + h1 inside the header already give
       enough breathing room. */
    padding-top: 28px;
  }
  .food-gallery-head {
    text-align: left;
    margin-bottom: 6px;
  }
  .food-gallery-head .page-eyebrow { margin-bottom: 6px; }
  .food-gallery-head .page-title-jp { margin: 0; }
  .food-gallery-head .page-title-en { margin-top: 6px; max-width: 640px; }
  .food-nav {
    margin-top: 16px;
    display: flex; flex-wrap: wrap;
    gap: 6px;
  }
  .food-nav-chip {
    display: inline-flex; align-items: baseline; gap: 6px;
    padding: 5px 12px;
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    border-radius: 999px;
    color: var(--ink-2);
    text-decoration: none;
    font-family: var(--serif);
    font-size: 13px;
    transition: background .15s, border-color .15s, color .15s;
  }
  .food-nav-chip:hover {
    background: var(--gold-soft);
    border-color: var(--gold);
    color: var(--ink);
  }
  .food-nav-chip .ja {
    font-family: var(--serif-jp); font-size: 14px;
    color: var(--gold-dark);
  }
  .food-nav-chip .en {
    font-style: italic;
    color: var(--ink-3);
    font-size: 11px;
    letter-spacing: 0.02em;
  }
  /* Hairline rule under the header — uses the existing .rule pattern. */
  .food-gallery-frame .rule {
    margin: 18px 0 24px;
  }
  .food-section { margin: 0 0 32px; }
  .food-section:last-child { margin-bottom: 4px; }

  /* Visual food-gallery header that sits at the top of every menu
     page (menu-reference). Mirrors the food vocabulary page style —
     image-heavy cards grouped by section — so the learner sees the
     dish before reading the row label below. Tighter spacing than
     the standalone food vocab page because it sits inside a menu
     frame, sharing real estate with the row list beneath. */
  .menu-food-gallery {
    margin: 18px 0 24px;
  }
  .menu-food-section {
    margin: 0 0 22px;
  }
  .menu-food-section:last-child {
    margin-bottom: 4px;
  }
  .menu-food-section-head {
    display: flex; align-items: baseline; gap: 10px;
    margin: 0 0 12px;
    padding: 0 0 6px;
    border-bottom: 1px dashed rgba(141,102,48,0.22);
    font-weight: 500;
  }
  .menu-food-section-head .ja {
    font-family: var(--font-title);
    font-size: 18px;
    color: var(--ink);
  }
  .menu-food-section-head .en {
    font-family: var(--font-menu-en); font-style: italic;
    font-size: 12px;
    color: var(--ink-3);
    letter-spacing: 0.04em;
  }
  .menu-food-section-head .count {
    margin-left: auto;
    font-family: var(--font-menu-en); font-style: italic;
    font-size: 11px;
    color: var(--ink-3);
  }
  /* Slightly tighter cards than the standalone food gallery grid so
     the header doesn't dominate the menu page. The food-card base
     styles still apply — this just overrides the grid density. */
  .menu-food-grid {
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    gap: 12px;
  }
  @media (max-width: 760px) {
    .menu-food-grid {
      grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
      gap: 10px;
    }
  }
  .food-section-head {
    display: flex; align-items: baseline; gap: 12px;
    margin: 0 0 16px;
    padding: 0 0 8px;
    border-bottom: 1px dashed rgba(141,102,48,0.28);
    font-weight: 500;
  }
  .food-section-head .ja {
    font-family: var(--serif-jp); font-size: 20px;
    color: var(--ink);
    letter-spacing: 0.04em;
  }
  .food-section-head .en {
    font-family: var(--serif); font-style: italic;
    font-size: 15px; color: var(--ink-3);
  }
  .food-section-head .count {
    margin-left: auto;
    font-family: var(--serif); font-style: italic;
    font-size: 12px; color: var(--ink-4);
    letter-spacing: 0.04em;
  }
  .food-grid {
    display: grid;
    /* Aim for ~5 across on a wide page, gracefully reflows narrower. */
    grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
    gap: 16px;
  }
  .food-card {
    display: flex; flex-direction: column;
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    border-radius: 6px;
    padding: 10px 10px 12px;
    /* It's a <button>, so reset the browser defaults. */
    font: inherit;
    color: inherit;
    text-align: left;
    cursor: pointer;
    transition: transform .15s, border-color .15s, box-shadow .15s;
  }
  .food-card:hover {
    border-color: var(--gold-soft);
    box-shadow: 0 4px 14px rgba(100,72,30,0.10);
    transform: translateY(-1px);
  }
  .food-card:focus-visible {
    outline: 2px solid var(--gold);
    outline-offset: 2px;
  }
  .food-svg-frame {
    aspect-ratio: 1 / 1;
    border-radius: 4px;
    /* Faint hash texture so the line-art has a backdrop instead of
       floating on the card. Matches the paper aesthetic. */
    background:
      repeating-linear-gradient(135deg,
        rgba(141,102,48,0.045) 0 6px,
        transparent 6px 12px),
      var(--paper-2);
    border: 1px solid rgba(141,102,48,0.12);
    display: flex; align-items: center; justify-content: center;
    padding: 12px;
    /* Set the ink tone on the frame so any inlined SVG that uses
       `currentColor` inherits the same warm sepia line as the rest
       of the app. */
    color: var(--ink-2);
  }
  /* SVG image inside the frame — contained, doesn't crop. */
  .food-svg-frame > img,
  .food-svg-frame > svg {
    width: 100%; height: 100%;
    object-fit: contain;
    display: block;
  }
  /* PNG photographs blend into the hash-textured paper frame via
     multiply — the white background of the photo dissolves so only the
     food shows. Lets us drop in white-bg or transparent-bg PNGs without
     pre-masking. SVG line-art skips this rule (it's already transparent
     and would multiply its strokes against the paper texture in a way
     that washes them out). Selector targets .webp since the optimize-
     images pass converted every raster source from .png to .webp. */
  .food-svg-frame > img[src$=".webp"] {
    mix-blend-mode: multiply;
  }
  /* Global rule for ANY food raster image anywhere in the app — the
     ordering experience renders food images in several scopes
     (scene-svg, size-card-svg, receive-food, receive-drink). They all
     get the same multiply treatment so the white background dissolves
     consistently. SVG fallbacks skip the rule since their src ends in
     .svg. (Selector switched .png → .webp after the bulk-optimize. */
  img[src*="images/food/"][src$=".webp"] {
    mix-blend-mode: multiply;
  }
  .food-card-name {
    margin-top: 10px;
    display: flex; flex-direction: column;
    gap: 2px;
    min-width: 0;
  }
  .food-card-name .ja {
    font-family: var(--serif-jp); font-size: 15px;
    color: var(--ink);
    overflow-wrap: anywhere;
    line-height: 1.25;
  }
  .food-card-name .en {
    font-family: var(--serif); font-style: italic;
    font-size: 12px; color: var(--ink-3);
    line-height: 1.35;
  }
  @media (max-width: 760px) {
    .food-grid { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 12px; }
    .food-card { padding: 8px 8px 10px; }
    .food-svg-frame { padding: 8px; }
    .food-card-name .ja { font-size: 14px; }
    .food-card-name .en { font-size: 11px; }
    .food-nav { gap: 4px; }
    .food-nav-chip { padding: 4px 10px; font-size: 12px; }
  }

  /* ─────────────────────────────────────────────────────────────────
     Flavors page (Phase 1 of the Flavors & Textures sub-system).

     Two states, one page: bento (default, 10 cards) and immersion
     (per-flavor full-bleed color flood). The page is rendered by
     flavorsPageHTML() in app.html; both states live inside the same
     .flavors-frame container and the active state is gated by
     data-mode="bento" | "immersion" on that container.

     The signature decision: color floods the entire viewport when
     immersion is active. The flood IS the encoding of the flavor —
     yellow IS suppai, not "yellow because lemons are yellow."

     See:
       docs/superpowers/specs/2026-05-26-flavors-textures.PRODUCT.md
       docs/superpowers/specs/2026-05-26-flavors-textures.DESIGN.md
     ───────────────────────────────────────────────────────────────── */

  /* Container shell. Sits inside #vocab-page-content; expands to fill
     the available width like the other interactive surfaces. The
     padding here is intentionally generous — editorial breathing room
     per the brief. Mobile reclaims most of it. */
  /* ─────────────────────────────────────────────────────────────────
     TEXTURES PAGE (Phase 2 — single scrollable page, no bento)
     ─────────────────────────────────────────────────────────────────
     One scrollable surface with five stacked sections:
       1. Page head    (eyebrow · ruby title · italic sub)
       2. Spectrum     (10 staple-food tiles laid out soft→hard,
                        with edge labels and a gradient indicator line)
       3. Spotlight    (selected texture: image + kana + audio + pills)
       4. Collage      (more foods that share this texture)
       5. Drawer       (English gloss + cultural notes)

     Color rule (DESIGN.md): per-texture tint is a whisper, scoped to
     surfaces that need to identify (active tile, line dot, spotlight
     card background at low opacity). It never floods the viewport —
     that's what the flavors page does. Tint is wired via --tint on
     .textures-page (current texture) and overridden on each tile/dot.

     Spec: docs/superpowers/specs/2026-05-26-flavors-textures.PRODUCT.md §7.2
  */
  .textures-page {
    --tint: var(--paper-2);
    position: relative;
    padding: clamp(20px, 2.2vw, 40px) clamp(16px, 2.4vw, 40px) clamp(40px, 4vw, 64px);
    max-width: 1280px;
    margin: 0 auto;
    outline: none;
  }
  .textures-page:focus { outline: none; }

  /* ── 1. Page head ─────────────────────────────────────────────── */
  .textures-page-head {
    display: flex; flex-direction: column; align-items: center;
    gap: clamp(4px, 0.45vw, 8px);
    margin: 0 0 clamp(20px, 2.6vw, 40px);
    text-align: center;
  }
  .textures-page-eyebrow {
    font-family: var(--font-menu-en);
    font-size: clamp(10px, 0.75vw, 12px);
    letter-spacing: 0.14em; text-transform: uppercase;
    color: var(--ink-3);
  }
  .textures-page-title {
    font-family: var(--font-title);
    font-size: clamp(1.8rem, 3.2vw + 0.5rem, 3.2rem);
    color: var(--ink);
    line-height: 1.05; margin: 0;
  }
  .textures-page-title rt {
    font-size: 0.32em;
    font-family: var(--font-body);
    color: var(--ink-3);
    letter-spacing: 0.02em;
  }
  .textures-page-sub {
    font-family: var(--font-menu-en); font-style: italic;
    font-size: clamp(13px, 0.85vw + 4px, 15px);
    color: var(--ink-2);
    max-width: 64ch;
    margin: 0;
    line-height: 1.55;
  }

  /* ── Shared section header (numbered) ─────────────────────────── */
  .textures-section-head {
    display: flex; flex-direction: column;
    gap: 4px;
    margin: 0 0 clamp(14px, 1.6vw, 22px);
    padding-bottom: clamp(8px, 0.9vw, 14px);
    border-bottom: 1px solid color-mix(in srgb, var(--ink) 12%, transparent);
  }
  .textures-section-head .section-num {
    font-family: var(--font-menu-en);
    font-size: 11px; letter-spacing: 0.18em;
    color: var(--ink-3);
  }
  .textures-section-head .section-title {
    font-family: var(--font-title);
    font-size: clamp(1.2rem, 1.6vw + 0.4rem, 1.7rem);
    color: var(--ink);
    margin: 0; line-height: 1.2;
  }
  .textures-section-head .section-sub {
    font-family: var(--font-body);
    font-size: clamp(13px, 0.7vw + 6px, 14px);
    color: var(--ink-2);
    line-height: 1.6;
    margin: 4px 0 0;
    max-width: 70ch;
  }

  /* ── 2. Spectrum (soft → hard) ───────────────────────────────── */
  .textures-spectrum {
    margin: 0 0 clamp(28px, 3.2vw, 48px);
  }
  .textures-spectrum-row {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: stretch;
    gap: clamp(12px, 1.4vw, 24px);
  }
  @media (max-width: 760px) {
    .textures-spectrum-row {
      grid-template-columns: 1fr;
      gap: 12px;
    }
  }
  .spectrum-edge {
    display: flex; flex-direction: column;
    align-items: center; justify-content: center;
    gap: 4px;
    min-width: 64px;
    padding: 6px 4px;
    color: var(--ink-2);
  }
  /* Hiragana stays as the primary label — readable first-glance form,
     matches the readiness the learner is building. The kanji subtitle
     sits beneath in a smaller weight so the eye lands on hiragana
     first and picks up the kanji as the deeper layer. */
  .spectrum-edge-ja {
    font-family: var(--font-title);
    font-size: clamp(15px, 1vw + 4px, 18px);
    color: var(--ink);
    line-height: 1.1;
    writing-mode: horizontal-tb;
  }
  .spectrum-edge-kanji {
    font-family: var(--font-title);
    font-size: clamp(10px, 0.55vw + 4px, 12px);
    color: var(--ink-3);
    line-height: 1.2;
  }
  @media (max-width: 760px) {
    .spectrum-edge { flex-direction: row; gap: 8px; }
    .spectrum-edge-soft { order: 1; }
    .spectrum-edge-hard { order: 3; }
  }

  /* The middle column of .textures-spectrum-row. Holds the tile row
     and the dot line stacked vertically — keeping them in the same
     parent column means they share the same width and inner padding,
     which is the only way to make the dot grid math line up with the
     tile grid math at every breakpoint. */
  .spectrum-mid {
    display: flex; flex-direction: column;
    gap: clamp(8px, 0.9vw, 14px);
    min-width: 0;
  }
  .textures-spectrum-tiles {
    display: grid;
    grid-template-columns: repeat(10, 1fr);
    gap: clamp(4px, 0.55vw, 10px);
    list-style: none; padding: 0; margin: 0;
    min-width: 0;
  }
  @media (max-width: 1100px) {
    .textures-spectrum-tiles { grid-template-columns: repeat(5, 1fr); }
  }
  @media (max-width: 540px) {
    .textures-spectrum-tiles { grid-template-columns: repeat(2, 1fr); }
  }
  .textures-spectrum-tile-wrap {
    display: flex;
    min-width: 0;
  }
  .textures-spectrum-tile {
    appearance: none;
    background: var(--tint, var(--paper-2));
    border: 1px solid color-mix(in srgb, var(--ink) 10%, transparent);
    border-radius: 10px;
    padding: 8px 6px 10px;
    cursor: pointer;
    color: var(--ink);
    width: 100%;
    display: flex; flex-direction: column; align-items: center;
    gap: 4px;
    transition: transform .15s ease-out, box-shadow .15s ease-out, border-color .15s;
    font: inherit;
  }
  .textures-spectrum-tile:hover {
    transform: translateY(-2px);
    box-shadow: 0 6px 16px -8px color-mix(in srgb, var(--ink) 30%, transparent);
  }
  .textures-spectrum-tile:focus-visible {
    outline: 2px solid var(--ink);
    outline-offset: 2px;
  }
  .textures-spectrum-tile.is-active {
    border-color: var(--ink);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--ink) 18%, transparent),
                0 8px 20px -8px color-mix(in srgb, var(--ink) 35%, transparent);
    transform: translateY(-2px);
  }
  @media (prefers-reduced-motion: reduce) {
    .textures-spectrum-tile,
    .textures-spectrum-tile:hover,
    .textures-spectrum-tile.is-active { transition: none; transform: none; }
  }
  .spectrum-tile-image {
    width: 100%; aspect-ratio: 1 / 1;
    display: flex; align-items: center; justify-content: center;
    overflow: hidden;
  }
  .spectrum-tile-image img {
    width: 100%; height: 100%;
    object-fit: contain;
  }
  /* Slightly smaller so 4-char kana (もちもち, さらさら, ふわふわ, etc.)
     sit on a single line at the desktop tile width. `white-space:
     nowrap` is the explicit guarantee — the browser's default
     character-level wrapping was occasionally breaking 4-char kana
     mid-word at narrow widths. The 6-char exception (シャキシャキ)
     gets its symmetric 3+3 stack from an explicit <br> injected by
     formatSpectrumKana; <br> bypasses nowrap so the stack still
     renders. */
  .spectrum-tile-kana {
    font-family: var(--font-title);
    font-size: clamp(10px, 0.55vw + 4px, 12px);
    color: var(--ink);
    line-height: 1.15;
    text-align: center;
    white-space: nowrap;
  }
  /* Brushstroke glyph below the kana — the visual signature of the
     texture. Replaces the old English stapleLabel; the kana names it,
     the photo anchors it, the brushstroke shows what it feels like. */
  .spectrum-tile-glyph {
    width: 100%;
    display: flex; align-items: center; justify-content: center;
    color: color-mix(in srgb, var(--ink) 78%, transparent);
    margin-top: 2px;
  }
  .spectrum-tile-glyph .texture-motion-line {
    width: 100%; height: auto; max-height: 22px;
  }

  /* Soft→hard indicator line. Sits inside .spectrum-mid so it shares
     the tile row's exact column width. The dot grid mirrors the tile
     grid exactly — same repeat(10, 1fr), same gap — which means dot N
     and tile N share the same vertical centerline, regardless of how
     the soft/hard edge labels wrap or how the viewport stretches. */
  .textures-spectrum-line {
    display: grid;
    grid-template-columns: repeat(10, 1fr);
    gap: clamp(4px, 0.55vw, 10px);
    position: relative;
    align-items: center;
    padding: 6px 0;
  }
  .textures-spectrum-line::before {
    content: '';
    position: absolute;
    left: 0; right: 0;
    top: 50%; transform: translateY(-50%);
    height: 2px;
    background: linear-gradient(90deg,
      color-mix(in srgb, var(--ink) 14%, transparent) 0%,
      color-mix(in srgb, var(--ink) 30%, transparent) 50%,
      color-mix(in srgb, var(--ink) 14%, transparent) 100%);
    z-index: 0;
  }
  @media (max-width: 1100px) {
    .textures-spectrum-line { display: none; }
  }
  .spectrum-line-dot {
    width: 12px; height: 12px;
    border-radius: 50%;
    background: var(--tint, var(--paper-2));
    border: 1px solid color-mix(in srgb, var(--ink) 30%, transparent);
    justify-self: center;
    position: relative; z-index: 1;
    transition: transform .15s, border-color .15s, box-shadow .15s;
  }
  .spectrum-line-dot.is-active {
    border-color: var(--ink);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--ink) 16%, transparent);
    transform: scale(1.3);
  }

  /* ── 3. Spotlight (selected texture detail card) ─────────────── */
  /* The card has personality from TWO sources:
       1. The strong per-texture tint as the paper-color base. This is
          what gave the card its identity before patterns existed —
          it's preserved here at full strength.
       2. The per-texture pattern layered ON TOP via a ::before
          pseudo-element using mix-blend-mode: multiply. Multiply
          means the pattern's white/cream background DOESN'T fight
          the tint (white × tint = tint, unchanged), and only the
          pattern's darker shapes (mochi-blob outlines, crumb zigzags,
          string strands) darken the tint where they overlap —
          producing tinted pattern elements that read as the texture's
          motif sitting on the texture's color.
     Direct children get z-index: 1 so the kana, romaji, audio,
     description, and examples row all sit cleanly above the pattern
     layer. The pattern is decorative — pointer-events: none keeps
     mouse interaction landing on the content underneath. */
  .textures-spotlight {
    position: relative;
    margin: 0 0 clamp(28px, 3.2vw, 48px);
    background: color-mix(in srgb, var(--tint, var(--paper-2)) 78%, var(--paper-2) 22%);
    border-radius: 14px;
    padding: clamp(20px, 2.4vw, 36px);
    border: 1px solid color-mix(in srgb, var(--ink) 8%, transparent);
    /* Paper-float shadow stack — two layers for realism: a tight
       close shadow grounds the card just off the page, a wider soft
       ambient shadow does the "floating sheet" feeling. Ink-colored
       (not pure black) so it stays warm against the paper palette. */
    box-shadow:
      0 2px 4px -2px color-mix(in srgb, var(--ink) 14%, transparent),
      0 18px 36px -14px color-mix(in srgb, var(--ink) 22%, transparent);
    overflow: hidden;     /* clip the pattern to the rounded corners */
    isolation: isolate;   /* contain the blend-mode to this card */
  }
  .textures-spotlight::before {
    content: '';
    position: absolute;
    inset: 0;
    background-image: var(--spotlight-pattern, none);
    background-repeat: repeat;
    background-size: 360px auto;
    /* Multiply blend: pattern's cream background ≈ no change,
       pattern shapes darken the tinted base where they overlap. The
       result reads as "the texture's motif printed onto its own
       colored paper" rather than a transparency overlay. */
    mix-blend-mode: multiply;
    opacity: 0.85;
    pointer-events: none;
    z-index: 0;
  }
  /* Children sit above the pattern layer. */
  .textures-spotlight > * { position: relative; z-index: 1; }

  /* Mobile: shrink the tile so the pattern doesn't dwarf the card. */
  @media (max-width: 760px) {
    .textures-spotlight::before { background-size: 220px auto; }
  }
  /* High-contrast modes: drop the pattern entirely so the tint holds
     the card alone — patterned surfaces hurt low-vision users with
     high-contrast modes turned on. */
  @media (prefers-contrast: more) {
    .textures-spotlight::before { display: none; }
  }
  .textures-spotlight .textures-section-head {
    border-bottom: none;
    padding-bottom: 0;
    margin-bottom: clamp(16px, 1.8vw, 24px);
  }
  /* Two-column layout — image+brushstroke on the left, info column on
     the right, vertical-centered against the left column's taller
     height. Centering the info column instead of top-aligning it
     reclaims the empty space the title removal left behind and pairs
     the kana visually with the staple photo on the same baseline. */
  .textures-spotlight-body {
    display: grid;
    grid-template-columns: minmax(220px, 320px) 1fr;
    gap: clamp(20px, 2.6vw, 40px);
    align-items: center;
  }
  @media (max-width: 780px) {
    .textures-spotlight-body {
      grid-template-columns: 1fr;
      gap: 20px;
    }
  }
  /* Image floats free on the spotlight surface — no enclosing square.
     The brushstroke is the visual frame; the photo just sits beside
     it as the staple-food reference. */
  .textures-spotlight-image {
    width: 100%; aspect-ratio: 1 / 1;
    display: flex; align-items: center; justify-content: center;
    overflow: hidden;
  }
  .textures-spotlight-image img {
    width: 100%; height: 100%;
    object-fit: contain;
  }
  .textures-spotlight-info {
    display: flex; flex-direction: column;
    gap: clamp(10px, 1vw, 14px);
    min-width: 0;
  }
  /* Image-column wrapper — stacks the photo above the brushstroke
     signature so the two share the same column width and centerline.
     The brushstroke sits below the image because the image column is
     where the visual weight lives; pairing the glyph there keeps the
     info column free for the text hierarchy. */
  .textures-spotlight-imagecol {
    display: flex; flex-direction: column;
    gap: clamp(8px, 0.9vw, 14px);
    min-width: 0;
  }
  /* Brushstroke under the image — the signature that pairs with this
     texture. Capped at 112px tall (doubled from the previous 56px)
     so the stroke reads at the same visual weight as the photo
     above it. Tinted slightly lighter than full ink so the photo
     above stays the strongest visual element. */
  .textures-spotlight-glyph {
    width: 100%;
    color: color-mix(in srgb, var(--ink) 88%, transparent);
    margin: 0;
  }
  .textures-spotlight-glyph .texture-motion-line {
    width: 100%; height: auto; max-height: 112px;
  }
  .spotlight-kana {
    font-family: var(--font-title);
    font-size: clamp(2rem, 4vw + 0.4rem, 3.4rem);
    color: var(--ink);
    margin: 0; line-height: 1;
    letter-spacing: -0.01em;
  }
  .spotlight-romaji {
    font-family: var(--font-menu-en); font-style: italic;
    font-size: clamp(13px, 0.8vw + 6px, 16px);
    color: var(--ink-3);
    margin: -4px 0 0;
    letter-spacing: 0.04em;
  }
  .textures-spotlight-audio {
    align-self: flex-start;
    width: 44px; height: 44px;
    border-radius: 50%;
    padding: 0;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--ink);
    color: var(--paper);
    border: none;
    cursor: pointer;
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--ink) 12%, transparent);
    transition: transform .15s, box-shadow .15s;
  }
  .textures-spotlight-audio:hover {
    transform: translateY(-1px);
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--ink) 22%, transparent);
  }
  .textures-spotlight-audio:focus-visible {
    outline: 2px solid var(--ink);
    outline-offset: 3px;
  }
  .textures-spotlight-audio svg { width: 18px; height: 18px; }
  @media (prefers-reduced-motion: reduce) {
    .textures-spotlight-audio { transition: none; }
    .textures-spotlight-audio:hover { transform: none; }
  }
  /* Spotlight description — now carries the simple Japanese learning
     sentences (see texture.notesJa in data.js). Japanese text wants a
     touch more vertical breathing room than English in the same body
     font, so line-height bumps from 1.65 → 1.85. Font-size also
     bumps slightly (14-17px vs 13-15px) so the kana/kanji read
     comfortably at didactic-text size rather than caption size. */
  .spotlight-description {
    font-family: var(--font-body);
    font-size: clamp(14px, 0.8vw + 6px, 17px);
    color: var(--ink-2);
    line-height: 1.85;
    margin: 4px 0 0;
  }

  /* Bottom-of-card examples row — image-and-name, no card chrome.
     Sits below the spotlight body, divided by a hairline so the row
     reads as a coda to the texture's introduction rather than as a
     second section. Six items fit cleanly on desktop; flex shrinks
     each item so they fit on narrow viewports without wrapping. */
  .textures-spotlight-examples {
    display: flex;
    gap: clamp(8px, 1vw, 16px);
    margin-top: clamp(20px, 2.2vw, 32px);
    padding-top: clamp(14px, 1.6vw, 22px);
    border-top: 1px solid color-mix(in srgb, var(--ink) 10%, transparent);
    align-items: flex-start;
    justify-content: space-around;
  }
  /* Reference cards — NOT links. No cursor change on hover, no focus
     ring, no pointer affordance. The subtle hover lift is purely
     decorative (paper-on-paper drift, not an "click me" signal). */
  .spotlight-example-item {
    display: flex; flex-direction: column;
    align-items: center;
    gap: 6px;
    min-width: 0;
    flex: 1 1 0;
    max-width: 120px;
    color: inherit;
    text-align: center;
    transition: transform .15s ease-out;
  }
  .spotlight-example-item:hover {
    transform: translateY(-2px);
  }
  @media (prefers-reduced-motion: reduce) {
    .spotlight-example-item,
    .spotlight-example-item:hover { transition: none; transform: none; }
  }
  .spotlight-example-image {
    width: 100%; max-width: 88px;
    aspect-ratio: 1 / 1;
    display: flex; align-items: center; justify-content: center;
    overflow: hidden;
  }
  .spotlight-example-image img {
    width: 100%; height: 100%;
    object-fit: contain;
  }
  .spotlight-example-name {
    font-family: var(--font-title);
    font-size: clamp(11px, 0.65vw + 4px, 13px);
    color: var(--ink);
    line-height: 1.2;
    overflow-wrap: anywhere;
  }

  /* ── 4. Extra textures explainer (non-canonical) ───────────────
     Zig-zag scrollable list of the 8 descriptors that don't have a
     spectrum slot (やわらかい, なめらか, つるつる, ぷるぷる, プチプチ,
     シャリシャリ, クリーミー, ジューシー). Supporting cast — quieter
     than the spotlight: no card chrome, no patterned background, no
     audio button. Watercolor icon on one side, JP-first text + EN
     translation + example pills on the other. The side alternates
     row-by-row so the page has rhythm rather than a column-stack
     of left-aligned images. */
  .textures-extras {
    margin: clamp(28px, 3.2vw, 48px) 0;
  }
  .textures-extras .textures-section-head {
    border-bottom: 1px solid color-mix(in srgb, var(--ink) 12%, transparent);
    padding-bottom: clamp(12px, 1.4vw, 20px);
    margin-bottom: clamp(20px, 2.4vw, 32px);
  }
  .textures-extras .section-sub {
    line-height: 1.7;
  }
  .textures-extras .section-sub-en {
    font-family: var(--font-menu-en); font-style: italic;
    font-size: 0.92em;
    color: var(--ink-3);
  }
  .textures-extras-list {
    display: flex; flex-direction: column;
    gap: clamp(28px, 3.4vw, 56px);
  }
  /* Each row is a 2-col grid: image + body. .is-flipped swaps the
     visual order on every other row to produce the zig-zag rhythm
     without changing DOM order (so reading order stays consistent
     for screen readers). On mobile both rows stack image-first; the
     zig-zag is a desktop affordance. A hairline divider beneath each
     row (except the last) separates the entries so the section reads
     as a list, not as one continuous run-on flow. */
  .extra-texture-row {
    display: grid;
    grid-template-columns: minmax(140px, 200px) 1fr;
    gap: clamp(20px, 2.4vw, 40px);
    align-items: center;
    /* Each row carries balanced internal padding now that the bands
       are continuous (no hairline dividers, no list gap). The band
       fills top:0 → bottom:0 of the row box, so this padding IS the
       band's interior breathing room. */
    padding-top: clamp(28px, 3.2vw, 48px);
    padding-bottom: clamp(28px, 3.2vw, 48px);
  }

  /* Colored band behind each row. The renderer attaches:
       --row-tint        — solid color under the tile
       --row-bg-image    — url(...) tile of the closest canonical texture
       --row-bg-opacity  — 0..1 layer opacity
     Only rows with .has-bg get the pseudo. The band breaks out
     horizontally to fill the .main column via 100vw + transform centering;
     .main has overflow-x: hidden so it clips to the content area
     (sidebar stays clean). top:0 / bottom:0 fills the row's full box. */
  .extra-texture-row.has-bg {
    position: relative;
    isolation: isolate;
  }
  .extra-texture-row.has-bg::before {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    left: 50%;
    width: 100vw;
    transform: translateX(-50%);
    background-color: var(--row-tint, transparent);
    background-image: var(--row-bg-image, none);
    background-size: 240px auto;
    background-repeat: repeat;
    background-blend-mode: multiply;
    opacity: var(--row-bg-opacity, 0.32);
    z-index: -1;
    pointer-events: none;
  }
  /* Continuous bands — list gap zeroed and hairlines gone so each row's
     colored band touches the next, reading as horizontal stripes rather
     than dividers + whitespace. */
  .textures-extras-list { gap: 0; }
  .extra-texture-row.is-flipped {
    grid-template-columns: 1fr minmax(140px, 200px);
  }
  .extra-texture-row.is-flipped .extra-texture-row-image {
    grid-column: 2; grid-row: 1;
  }
  /* align-items: flex-end on the body is the actual fix here —
     text-align: right alone wasn't enough because .extra-texture-row-en
     carries a max-width: 56ch cap; with the cap, the EN block sized to
     56ch and ANCHORED at the left edge of the body, leaving the text
     right-aligned within the block but the block itself stranded far
     left of the kanji/JA above. flex-end lets each child block size
     to its content and align to the right edge of the body, so the
     EN block sits flush right next to its row's right margin. */
  .extra-texture-row.is-flipped .extra-texture-row-body {
    grid-column: 1; grid-row: 1;
    align-items: flex-end;
    text-align: right;
  }
  .extra-texture-row.is-flipped .extra-texture-row-examples {
    justify-content: flex-end;
  }
  @media (max-width: 720px) {
    .extra-texture-row,
    .extra-texture-row.is-flipped {
      grid-template-columns: 1fr;
      gap: 16px;
      text-align: left;
    }
    .extra-texture-row.is-flipped .extra-texture-row-image,
    .extra-texture-row.is-flipped .extra-texture-row-body {
      grid-column: 1; grid-row: auto;
      align-items: flex-start;
      text-align: left;
    }
    .extra-texture-row.is-flipped .extra-texture-row-examples {
      justify-content: flex-start;
    }
  }
  /* Image cell — watercolor icon floats on paper. No frame, no
     background, no shadow. The icon's own painted shadow + the
     pre-trimmed transparent border are the only chrome. */
  .extra-texture-row-image {
    display: flex; align-items: center; justify-content: center;
  }
  .extra-texture-row-image img {
    width: 100%; max-width: 200px;
    height: auto;
    display: block;
  }
  .extra-texture-row-body {
    display: flex; flex-direction: column;
    gap: clamp(6px, 0.8vw, 10px);
    min-width: 0;
  }
  /* Headword — kana big (brush title font); when a kanji exists the
     headword is the kanji with rt furigana, with the kana repeated
     below as a smaller reading subtitle. */
  .extra-texture-row-head {
    font-family: var(--font-title);
    font-size: clamp(1.6rem, 2.6vw + 0.4rem, 2.4rem);
    color: var(--ink);
    margin: 0; line-height: 1.1;
    letter-spacing: 0.01em;
    display: inline-flex; align-items: center;
    gap: clamp(8px, 0.9vw, 14px);
  }
  .extra-texture-row-head rt {
    font-size: 0.32em;
    font-family: var(--font-body);
    color: var(--ink-3);
    letter-spacing: 0.02em;
  }
  /* Small speaker button next to the headword — clicks fire the
     existing [data-speak] handler that wireTexturesPageHandlers
     already registers. Quieter than the spotlight audio (28px vs
     44px, paper background vs ink) because the extras section is
     supporting cast — the audio is an aid, not a hero affordance. */
  .extra-texture-row-audio {
    appearance: none;
    width: 32px; height: 32px;
    border-radius: 50%;
    padding: 0;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--paper-2);
    color: var(--ink-2);
    border: 1px solid color-mix(in srgb, var(--ink) 14%, transparent);
    cursor: pointer;
    flex-shrink: 0;
    transition: background .15s, color .15s, transform .15s;
  }
  .extra-texture-row-audio:hover {
    background: var(--paper);
    color: var(--ink);
    transform: translateY(-1px);
  }
  .extra-texture-row-audio:focus-visible {
    outline: 2px solid var(--ink);
    outline-offset: 3px;
  }
  .extra-texture-row-audio svg { width: 14px; height: 14px; }
  @media (prefers-reduced-motion: reduce) {
    .extra-texture-row-audio { transition: none; }
    .extra-texture-row-audio:hover { transform: none; }
  }
  /* On flipped rows: reverse the visual order of [headword + audio]
     so the speaker button sits on the LEFT of the headword (closest
     to the body's text column) instead of crowding the right edge
     between the headword and the image. flex-direction: row-reverse
     keeps the DOM order (word, button) consistent — only the visual
     flips — so screen-reader reading order remains "headword, then
     audio control." */
  .extra-texture-row.is-flipped .extra-texture-row-head {
    flex-direction: row-reverse;
  }
  .extra-texture-row-kana {
    font-family: var(--font-title);
    font-size: 16px;
    color: var(--ink-2);
    margin: 0;
  }
  .extra-texture-row-ja {
    font-family: var(--font-body);
    font-size: clamp(14px, 0.8vw + 6px, 15px);
    line-height: 1.7;
    color: var(--ink);
    margin: clamp(2px, 0.4vw, 6px) 0 0;
  }
  .extra-texture-row-en {
    font-family: var(--font-menu-en); font-style: italic;
    font-size: clamp(12px, 0.55vw + 6px, 13.5px);
    line-height: 1.55;
    color: var(--ink-3);
    margin: 0;
    max-width: 56ch;
  }
  /* Example list — small inline pills with image + kana, similar to
     the spotlight examples row but smaller. Wraps naturally; the
     is-flipped variant justifies them to the right edge to match
     the right-aligned text block. */
  .extra-texture-row-examples {
    display: flex; flex-wrap: wrap;
    gap: clamp(8px, 1vw, 14px);
    margin-top: clamp(8px, 1vw, 14px);
    justify-content: flex-start;
  }
  .extra-texture-example {
    display: inline-flex; align-items: center;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
  }
  .extra-texture-example-img {
    width: 48px; height: 48px;
    object-fit: contain;
    display: block;
  }
  .extra-texture-example-kana {
    font-family: var(--font-title);
    font-size: 12px;
    color: var(--ink-2);
    line-height: 1.2;
    text-align: center;
  }

  /* ── 5. English drawer + keyboard hint footer ──────────────────
     (Section number shifted from 4 → 5 to make room for the extra
     textures section above.) */
  .texture-drawer {
    margin: 0 0 clamp(16px, 1.8vw, 24px);
  }
  .texture-drawer-toggle {
    font-family: var(--font-menu-en);
    font-size: 12px; letter-spacing: 0.14em; text-transform: uppercase;
    color: var(--ink-3);
    cursor: pointer;
    padding: 8px 0;
    list-style: none;
  }
  .texture-drawer-toggle::-webkit-details-marker { display: none; }
  .texture-drawer-toggle::before {
    content: '▸ ';
    transition: transform .15s;
    display: inline-block;
  }
  .texture-drawer[open] .texture-drawer-toggle::before {
    transform: rotate(90deg);
  }
  .texture-drawer-body {
    padding: 12px 0 0;
  }
  .texture-drawer-body .en-gloss {
    font-family: var(--font-menu-en);
    font-size: 16px; color: var(--ink-2);
    margin: 0 0 8px;
  }
  .texture-drawer-body .notes {
    font-family: var(--font-body);
    font-size: 14px; line-height: 1.65;
    color: var(--ink-2);
    margin: 0;
  }
  .texture-keys-hint {
    margin: clamp(12px, 1.4vw, 20px) 0 0;
    font-family: var(--font-menu-en); font-style: italic;
    font-size: 11px; letter-spacing: 0.06em;
    color: var(--ink-4);
    text-align: center;
  }

  .flavors-frame {
    --flood:        var(--paper-2);
    --ink-on-flood: var(--ink);
    --chip:         var(--gold-soft);
    position: relative;
    min-height: 60vh;
    padding: 24px 32px 48px;
  }
  @media (max-width: 760px) {
    .flavors-frame { padding: 16px 16px 32px; }
  }

  /* ── Bento state ─────────────────────────────────────────────────
     The "encounter" surface. 10 paper cards at-a-glance.
     Grid: 5×2 desktop, 4×3 tablet, 2×5 mobile (never one column —
     pagination is banned per PRODUCT.md restriction #4).

     The two state-wraps are MUTUALLY EXCLUSIVE: bento mode shows the
     entire .flavors-bento-wrap (header + grid) and hides the immersion
     wrap; immersion mode does the inverse. The whole bento collapses,
     not just the card grid — otherwise the page title would leak
     through into the immersion view. */
  .flavors-frame[data-mode="bento"]    .flavors-immersion-wrap { display: none; }
  .flavors-frame[data-mode="immersion"] .flavors-bento-wrap     { display: none; }

  /* Bento sizing system — viewport-proportional clamps so the bento
     scales fluidly between narrow + wide viewports without hard-pixel
     breakpoints. Tuned so on a 1280×800 viewport the bento header
     plus the full first row of 5 cards fit above the fold, with the
     top edge of the second row peeking in as a "scroll for more"
     affordance. The clamps use vw for the rhythm and rem-anchored
     floor/ceiling so the design never collapses or balloons. */
  .flavors-bento-wrap {
    --bento-gap:        clamp(10px, 1.1vw, 18px);
    --bento-pad:        clamp(10px, 0.95vw, 14px);
    --bento-img-gap:    clamp(6px,  0.7vw, 12px);
    --bento-head-gap:   clamp(2px,  0.35vw, 6px);
    --bento-head-mb:    clamp(10px, 1.6vw, 24px);
  }
  .flavors-bento {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    gap: var(--bento-gap);
    max-width: 1280px;
    margin: 0 auto;
  }
  @media (max-width: 1100px) {
    .flavors-bento { grid-template-columns: repeat(4, 1fr); }
  }
  @media (max-width: 760px) {
    .flavors-bento { grid-template-columns: repeat(2, 1fr); }
  }

  /* Bento header — vertical stack with all spacing on the viewport-
     proportional scale. Title shrinks faster than before so the head
     stays compact on shorter viewports; subtitle caps at a single
     line of comfortable reading width. */
  .flavors-bento-head {
    display: flex; flex-direction: column; align-items: center;
    gap: var(--bento-head-gap);
    margin: 0 0 var(--bento-head-mb);
  }
  .flavors-bento-eyebrow {
    font-family: var(--font-menu-en);
    font-size: clamp(10px, 0.75vw, 12px);
    letter-spacing: 0.14em; text-transform: uppercase;
    color: var(--ink-3);
  }
  .flavors-bento-title {
    font-family: var(--font-title);
    font-size: clamp(1.6rem, 3.2vw + 0.4rem, 3rem);
    color: var(--ink);
    line-height: 1.05; margin: 0;
  }
  .flavors-bento-title rt {
    font-size: 0.32em;
    font-family: var(--font-body);
    color: var(--ink-3);
    letter-spacing: 0.02em;
  }
  .flavors-bento-sub {
    font-family: var(--font-menu-en); font-style: italic;
    font-size: clamp(12px, 0.85vw + 4px, 14px);
    color: var(--ink-2);
    max-width: 52ch; text-align: center;
    margin: 0;
  }

  /* Bento card — every card is its OWN flavor world. The card
     surface takes the flavor's full --flood color (matching the
     immersion view's canvas commit), text uses --ink-on-flood (the
     same contrast pair the immersion kana uses, so contrast is
     guaranteed by construction for both light + dark floods).

     The card contains TWO sibling buttons (.flavor-bento-enter
     covers ~95% of the card, .flavor-bento-audio is corner-positioned
     absolute), keeping HTML valid (no button-inside-button) while
     reading as one cohesive flavor tile. NO ENGLISH on the front —
     core pedagogical contract (PRODUCT.md §5 + restriction #1). */
  .flavor-bento-card {
    position: relative;
    display: flex; flex-direction: column;
    background: var(--flood);
    color: var(--ink-on-flood);
    border: 1px solid color-mix(in srgb, var(--ink-on-flood) 12%, transparent);
    border-radius: 10px;
    padding: var(--bento-pad, 14px);
    transition: transform .18s ease-out, box-shadow .18s ease-out;
    width: 100%;
    list-style: none;
  }
  /* Lift on hover. No background change — the flood IS the card's
     identity; swapping it on hover would lose that. Shadow is tone-
     matched to the ink so it reads against both light + dark floods. */
  .flavor-bento-card:hover {
    transform: translateY(-3px);
    box-shadow: 0 8px 24px -8px color-mix(in srgb, var(--ink-on-flood) 45%, transparent);
  }
  .flavor-bento-card:focus-within {
    outline: 2px solid var(--ink-on-flood);
    outline-offset: 3px;
  }
  @media (prefers-reduced-motion: reduce) {
    .flavor-bento-card { transition: none; }
    .flavor-bento-card:hover { transform: none; }
  }

  /* Inner enter button — the click affordance that drives the card
     into immersion. Covers the image + kana area. Button reset; the
     visual card belongs to the parent <li class="flavor-bento-card">. */
  .flavor-bento-enter {
    appearance: none;
    background: transparent; border: 0; padding: 0;
    margin: 0;
    color: inherit; font: inherit;
    text-align: left;
    cursor: pointer;
    display: flex; flex-direction: column;
    width: 100%;
  }
  .flavor-bento-enter:focus { outline: none; }
  .flavor-bento-enter:active .flavor-bento-image {
    transform: scale(0.985);
  }
  .flavor-bento-image {
    transition: transform .12s ease-out;
  }
  @media (prefers-reduced-motion: reduce) {
    .flavor-bento-enter:active .flavor-bento-image { transform: none; }
    .flavor-bento-image { transition: none; }
  }

  /* Image holder. Aspect-ratio 1:1. The food sits transparent on the
     flood directly — same as the immersion view's hero treatment, just
     scaled down. The drop-shadow is tone-matched to the ink-on-flood,
     so light floods get a dark food-shadow and dark floods get a cream
     halo — keeps the silhouette readable against either polarity. */
  .flavor-bento-image {
    width: 100%; aspect-ratio: 1 / 1;
    margin-bottom: var(--bento-img-gap, 12px);
    overflow: hidden;
  }
  .flavor-bento-image image-slot {
    width: 100%; height: 100%;
    display: block;
  }
  .flavor-bento-image image-slot::part(img),
  .flavor-bento-image image-slot::part(frame) {
    width: 100%; height: 100%;
    object-fit: contain;
  }
  /* Override the image-slot's built-in rgba(0,0,0,.04) frame backdrop —
     the food silhouette sits directly on the card's flood color, no
     faint dark rectangle dimming the chip/amai/etc. tint behind it. */
  .flavor-bento-image image-slot::part(frame) {
    background: transparent;
  }
  .flavor-bento-image image-slot::part(img) {
    filter: drop-shadow(0 4px 10px color-mix(in srgb, var(--ink-on-flood) 32%, transparent));
  }

  /* Kana label — the JP form, alone, no English nearby. Sized so the
     longest 5-char words (しょっぱい, つめたい with a stray, sawayaka)
     fit on one line at any card width. Color inherits from
     .flavor-bento-card (--ink-on-flood) so contrast is guaranteed
     against both light + dark floods. */
  .flavor-bento-kana {
    font-family: var(--font-title);
    /* clamp tuned so 5 kana chars fit on a single line at the 5-col
       desktop card width. Tracking pulled in slightly to help. */
    font-size: clamp(1.2rem, 1.5vw, 1.6rem);
    line-height: 1.2;
    color: var(--ink-on-flood);
    margin: 0;
    letter-spacing: 0.01em;
    /* Belt-and-suspenders: keep the kana on one line even if a card
       comes out slightly narrower than expected (e.g., a fonts-not-yet-
       loaded measurement quirk on first paint). */
    white-space: nowrap;
  }

  /* ── Immersion state ─────────────────────────────────────────────
     The "immerse" surface. Full-bleed color flood, thin rail of 10
     thumbs at top, hero image + huge kana centerpiece, audio button,
     related-foods placeholder strip, collapsed English drawer at the
     bottom. The flood color comes from --flood, --ink-on-flood,
     --chip — all three set on .flavors-frame as inline styles by the
     renderer when an immersion is mounted.

     Approach: when immersion is mounted, use :has() to expand the
     three enclosing containers (.main, .main-inner, .flavors-frame)
     so the wrap can naturally fill the .main column without
     positioning tricks. The sidebars (188 + 160 + 160 = 508px to
     the left) stay reachable; the wrap owns everything from sidebar's
     right edge to viewport's right edge.

     This is what "fullscreen and immersive" means in practice on a
     page-not-modal: the canvas commits to the flavor without burying
     the app nav. */
  .flavors-immersion-wrap {
    background: var(--flood);
    color: var(--ink-on-flood);
  }
  /* Three :has() rules expand the ancestor chain ONLY when an
     immersion is mounted in their descendant tree. They snap back
     to normal sizing when the bento returns.

     The .main rule uses the FULL ancestor chain
     (.app.show-vocab-sidebar .main + :has(#main-stepper:empty)) so it
     beats the line ~189 rule that restores padding-top: var(--pad-page)
     on stepper-empty vocab pages. That rule includes #main-stepper
     inside its :has(), giving it (1,4,0) specificity — class-only
     selectors lose. By chaining both :has() filters PLUS the immersion
     filter, this rule reaches (1,6,0) and wins.

     The scope is surgical: only matches when on a vocab page (.app.show-
     vocab-sidebar) with an empty stepper (#main-stepper:empty) AND an
     immersion-mode flavors frame in the descendant tree. Every other
     vocab page keeps its normal --pad-page padding. */
  .app.show-vocab-sidebar
    .main:has(#main-stepper:empty):has(.flavors-frame[data-mode="immersion"]) {
    padding: 0;
  }
  .main-inner:has(.flavors-frame[data-mode="immersion"]) {
    max-width: none;
    margin: 0;
  }
  .flavors-frame[data-mode="immersion"] {
    padding: 0;
    min-height: 100vh;
  }
  /* The wrap itself now owns the canvas. Background = flood, color =
     ink-on-flood, internal padding = --pad-page so content has the
     same breathing room every other vocab page gets. */
  .flavors-frame[data-mode="immersion"] .flavors-immersion-wrap {
    background: var(--flood);
    color: var(--ink-on-flood);
    padding: var(--pad-page) var(--pad-page) calc(var(--pad-page) * 1.2);
    min-height: 100vh;
    transition: background 500ms cubic-bezier(0.16, 1, 0.3, 1),
                color 500ms cubic-bezier(0.16, 1, 0.3, 1);
  }
  @media (max-width: 760px) {
    .flavors-frame[data-mode="immersion"] .flavors-immersion-wrap {
      padding: 14px 16px 32px;
    }
  }
  @media (prefers-reduced-motion: reduce) {
    .flavors-frame[data-mode="immersion"] .flavors-immersion-wrap { transition: none; }
  }

  /* Sticky top rail — redesigned 2026-05-26.
     Three-column grid:
       [back-button at left] [thumbs centered] [kbd hint at right]
     The 1fr/auto/1fr split keeps the thumbs visually centered
     regardless of the kbd-hint's content width. Back button at left,
     hint at right, thumbs in the geometric middle.

     Thumbs all carry a solid paper backing always (no more saturate
     filter that vanished into light floods). Active state = chip-
     colored ring + slight scale, so the "you are here" reads from
     across the rail. */
  .flavor-rail {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 12px;
    padding: 8px 0 14px;
    position: sticky; top: 0;
    z-index: 5;
    background: linear-gradient(to bottom, var(--flood) 82%, transparent 100%);
  }
  /* Back button — 2×2 grid icon (NOT a left arrow). Semantically
     "back to the bento overview of all flavors," distinguished
     visually from "walk one flavor left." Slightly wider than the
     thumbs (60×44 vs 44×44) so it reads as the rail's own affordance,
     not as a flavor sitting in the strip. */
  .flavor-rail-back {
    justify-self: start;
    width: 60px; height: 44px;
    border-radius: 10px;
    background: color-mix(in oklch, var(--flood) 65%, var(--ink-on-flood) 18%);
    border: 1px solid color-mix(in srgb, var(--ink-on-flood) 30%, transparent);
    color: var(--ink-on-flood);
    display: inline-flex; align-items: center; justify-content: center;
    cursor: pointer;
    transition: background .15s ease-out, transform .15s ease-out;
  }
  .flavor-rail-back:hover {
    background: color-mix(in oklch, var(--flood) 50%, var(--ink-on-flood) 28%);
    transform: translateY(-1px);
  }
  .flavor-rail-back svg { width: 22px; height: 22px; }
  .flavor-rail-back:focus-visible {
    outline: 2px solid var(--ink-on-flood);
    outline-offset: 3px;
  }
  @media (prefers-reduced-motion: reduce) {
    .flavor-rail-back { transition: none; }
    .flavor-rail-back:hover { transform: none; }
  }

  /* Thumb cluster — centered in the middle grid column. */
  .flavor-rail-thumbs {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 8px;
    overflow-x: auto;
    scrollbar-width: none;
    max-width: 100%;
  }
  .flavor-rail-thumbs::-webkit-scrollbar { display: none; }
  /* Ghost spacers at the start + end of the rail. Reserve interior
     space so the first/last thumb's active-state ring (2px box-shadow)
     and 1.06× scale don't clip against the scroll container's edge.
     Width matches the gap between thumbs so the spacing feel stays
     uniform across the row — the visible thumbs read as evenly
     buffered on both sides whether the active one sits in the middle
     or at an edge. aria-hidden + no interaction keeps them invisible
     to assistive tech and keyboard nav. */
  .flavor-rail-spacer {
    flex: 0 0 8px;
    height: 44px;     /* match .flavor-rail-thumb height */
    pointer-events: none;
  }
  /* Every thumb is just the transparent food silhouette sitting on
     the flood — no paper backing, no border, no rectangle behind it.
     Only the active thumb carries visual chrome: a chip-colored ring
     (box-shadow) marks "you are here." Hover gets a subtle scale lift
     so the click affordance reads without re-introducing the
     rectangle. */
  .flavor-rail-thumb {
    width: 44px; height: 44px;
    border-radius: 8px;
    background: transparent;
    border: 0;
    cursor: pointer;
    padding: 4px;
    /* overflow: hidden clips any baked-in edge artifacts (anti-aliased
       halos / one-pixel strokes on the source PNGs' canvas boundary)
       to the thumb's box. The active-state chip ring is a box-shadow,
       which renders OUTSIDE the element's box and is NOT affected by
       overflow clipping — so the ring still draws cleanly. */
    overflow: hidden;
    flex-shrink: 0;
    transition: transform .18s ease-out, box-shadow .18s ease-out;
  }
  .flavor-rail-thumb:hover {
    transform: translateY(-1px) scale(1.04);
  }
  .flavor-rail-thumb.is-active {
    /* The chip ring is the ONLY differentiator now — no fill, no
       border-color, just the stroke around the thumb. */
    box-shadow: 0 0 0 2px var(--chip),
                0 4px 12px -4px color-mix(in srgb, var(--ink-on-flood) 24%, transparent);
    transform: scale(1.06);
  }
  .flavor-rail-thumb image-slot,
  .flavor-rail-thumb image-slot::part(img),
  .flavor-rail-thumb image-slot::part(frame) {
    width: 100%; height: 100%;
    display: block;
    object-fit: contain;
  }
  /* Same image-slot frame backdrop override — let the thumb's own
     paper backing show through unobstructed. */
  .flavor-rail-thumb image-slot::part(frame) {
    background: transparent;
  }
  .flavor-rail-thumb:focus-visible {
    outline: 2px solid var(--chip);
    outline-offset: 3px;
  }
  @media (prefers-reduced-motion: reduce) {
    .flavor-rail-thumb { transition: none; }
    .flavor-rail-thumb:hover { transform: none; }
    .flavor-rail-thumb.is-active { transform: none; }
  }

  /* Keyboard hint — sits on the right of the rail. Only the walking
     keys live here; ESC + SPACE are in the bottom hint. The kbd
     elements visually echo physical keys (paper-on-ink chip).
     Hidden on touch-only devices and narrow viewports. */
  .flavor-rail-keys {
    justify-self: end;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-family: var(--font-menu-en);
    font-size: 10px;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: color-mix(in srgb, var(--ink-on-flood) 60%, transparent);
    white-space: nowrap;
  }
  .flavor-rail-keys kbd {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-family: inherit;
    font-size: 11px;
    min-width: 20px;
    padding: 2px 5px;
    border-radius: 4px;
    background: color-mix(in oklch, var(--flood) 65%, var(--ink-on-flood) 18%);
    border: 1px solid color-mix(in srgb, var(--ink-on-flood) 28%, transparent);
    color: var(--ink-on-flood);
    line-height: 1;
  }
  .flavor-rail-keys-sep {
    opacity: 0.55;
    margin: 0 2px;
  }
  @media (hover: none) {
    .flavor-rail-keys { display: none; }
    .flavor-rail { grid-template-columns: auto 1fr; }
  }
  @media (max-width: 900px) {
    .flavor-rail-keys { display: none; }
    .flavor-rail { grid-template-columns: auto 1fr; }
  }

  /* Immersion body — hero image left, kana centerpiece right.
     Stacked on mobile.

     Sizing system: viewport-proportional clamps (same approach as the
     bento) so the layout adapts fluidly. The previous min-height:50vh
     was forcing the body to fill half the viewport, which pushed the
     "この 味の 食べ物" row below the fold. Removed — the body now
     collapses to its natural content height, so the cards row reads
     above the fold on standard viewports. */
  .flavors-immersion-wrap {
    --imm-pad-top:    clamp(8px,  1.4vw, 20px);
    --imm-col-gap:    clamp(20px, 3vw,   40px);
    --imm-stack-gap:  clamp(8px,  1.2vw, 18px);
    --imm-img-cap:    clamp(220px, 28vw, 360px);
    --imm-kana-size:  clamp(2.5rem, 6vw, 6rem);
  }
  .flavors-immersion-body {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--imm-col-gap);
    align-items: center;
    max-width: 1280px;
    margin: var(--imm-pad-top) auto 0;
    /* The element carries tabindex="-1" so the JS focus()-on-mount can
       arm the keyboard handlers without requiring a click. tabindex=-1
       means the user can't tab TO it, so suppressing the visible focus
       outline is safe — there's no focus-indicator-missing a11y risk. */
    outline: none;
  }
  @media (max-width: 900px) {
    .flavors-immersion-body {
      grid-template-columns: 1fr;
    }
  }

  /* Hero image. With transparent food images at images/vocab/*.webp,
     the food sits DIRECTLY on the flood — no paper card around it.
     This is the committal version of the brief's "color flood as
     encoding" rule: the food and the flavor share one canvas, not
     paper-on-flood with a food card on top.

     A CSS filter drop-shadow on the rendered <img> gives the food a
     soft cast shadow against the flood — depth without chrome.

     Sized via --imm-img-cap so the hero proportionally shrinks on
     shorter viewports — the cards row can sit fully above the fold. */
  .flavor-immersion-image {
    width: 100%; max-width: var(--imm-img-cap, 360px);
    aspect-ratio: 1 / 1;
    justify-self: end;
    /* No background, no border-radius, no box-shadow — the food
       silhouette IS the visual element. */
  }
  @media (max-width: 900px) {
    .flavor-immersion-image { justify-self: center; max-width: 280px; }
  }
  .flavor-immersion-image image-slot,
  .flavor-immersion-image image-slot::part(img),
  .flavor-immersion-image image-slot::part(frame) {
    width: 100%; height: 100%;
    display: block;
    /* contain (not cover) so the food's transparent edges aren't
       cropped — the silhouette breathes into the flood. */
    object-fit: contain;
  }
  /* Override the image-slot's built-in rgba(0,0,0,.04) frame backdrop
     so the food silhouette sits clean on the flood, not on a faint
     dark rectangle. */
  .flavor-immersion-image image-slot::part(frame) {
    background: transparent;
  }
  /* Drop-shadow goes on the inner <img> (filter, not box-shadow) so
     it follows the food's silhouette, not a rectangular bounding box.
     Subtle: the same ink color as the rest of the chrome, mixed into
     the flood for a tone-matched shadow rather than a hard black. */
  .flavor-immersion-image image-slot::part(img) {
    filter: drop-shadow(0 12px 24px color-mix(in srgb, var(--ink-on-flood) 28%, transparent));
  }

  /* Kana centerpiece — the huge JP form. Sized via clamp so the
     character lands as a *visual gestalt* at any viewport — a
     learner sees a shape, not a string to decode.

     One-line guarantee: the kana element uses white-space: nowrap so
     5-char words (しょっぱい, つめたい, さわやか) never wrap to a
     second line, even on narrow viewports. The clamp ceiling is
     calibrated so 5 kana × 0.95em per char ≈ 4.75 × 6rem (96px) =
     ~456px wide, which fits comfortably in the right column of a
     1280px-max body grid (column = ~580px after gap). */
  .flavor-immersion-identity {
    display: flex; flex-direction: column; align-items: flex-start;
    gap: var(--imm-stack-gap, 18px);
    color: var(--ink-on-flood);
  }
  @media (max-width: 900px) {
    .flavor-immersion-identity { align-items: center; text-align: center; }
  }
  .flavor-immersion-kana {
    font-family: var(--font-title);
    font-size: var(--imm-kana-size, clamp(2.5rem, 6vw, 6rem));
    line-height: 1;
    margin: 0;
    letter-spacing: -0.01em;
    white-space: nowrap;
    /* Kanji + okurigana stack inside this element — use a ruby for
       any kanji that needs furigana support. */
  }
  .flavor-immersion-kana ruby rt {
    font-family: var(--font-body);
    font-size: 0.22em;
    letter-spacing: 0.05em;
  }
  .flavor-immersion-food {
    font-family: var(--font-body);
    font-size: clamp(1rem, 1.4vw, 1.25rem);
    line-height: 1.3;
    margin: 0;
    color: color-mix(in srgb, var(--ink-on-flood) 78%, transparent);
    font-style: italic;
  }

  /* Audio button — icon-only circle. The speaker glyph is enough —
     no Japanese label, no English. Sized to match the kana's gravity
     (56×56 — a focal action, not a chrome control). Same color
     logic as before (ink-on-flood bg, flood as icon color) for
     contrast safety; chip ring stays for flavor identity. */
  .flavor-immersion-audio {
    width: 56px; height: 56px;
    border-radius: 50%;
    padding: 0;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--ink-on-flood);
    color: var(--flood);
    border: 1px solid color-mix(in srgb, var(--chip) 70%, var(--ink-on-flood) 30%);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--chip) 22%, transparent);
    cursor: pointer;
    transition: transform .15s ease-out, box-shadow .15s ease-out;
  }
  .flavor-immersion-audio:hover {
    transform: translateY(-1px);
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--chip) 36%, transparent);
  }
  .flavor-immersion-audio:focus-visible {
    outline: 2px solid var(--ink-on-flood);
    outline-offset: 4px;
  }
  .flavor-immersion-audio svg { width: 22px; height: 22px; }
  @media (prefers-reduced-motion: reduce) {
    .flavor-immersion-audio { transition: none; }
    .flavor-immersion-audio:hover { transform: none; }
  }

  /* Related-foods placeholder strip. Phase 3 will populate this
     with real edible thumbnails (the reverse-browse cross-link).
     Phase 1 ships an honest "Phase 3 coming" notice rather than
     leaving the section empty. */
  .flavor-related {
    max-width: 1280px;
    margin: 48px auto 0;
    padding-top: 24px;
    border-top: 1px solid color-mix(in srgb, var(--ink-on-flood) 18%, transparent);
  }
  /* English notes sit above the food-cards row, where the Japanese +
     English header used to live. The notes used to live in a closed
     drawer at the bottom of the page — moving them up promotes them
     from "click to read" to "always-visible context" without
     introducing new chrome. Width capped to a comfortable reading
     measure so long .notes paragraphs don't stretch across the full
     immersion width. */
  .flavor-related-notes {
    margin: 0 0 20px;
    max-width: 70ch;
    display: grid;
    gap: 8px;
  }
  .flavor-related-notes .en-gloss {
    font-family: var(--font-menu-en);
    font-size: 18px; font-weight: 500;
    color: var(--ink-on-flood);
    margin: 0;
    line-height: 1.3;
  }
  .flavor-related-notes .notes {
    font-family: var(--font-menu-en); font-style: italic;
    font-size: 15px; line-height: 1.65;
    color: color-mix(in srgb, var(--ink-on-flood) 80%, transparent);
    margin: 0;
  }
  .flavor-related-placeholder {
    padding: 22px;
    border: 1px dashed color-mix(in srgb, var(--ink-on-flood) 28%, transparent);
    border-radius: 8px;
    font-family: var(--font-menu-en); font-style: italic;
    color: color-mix(in srgb, var(--ink-on-flood) 60%, transparent);
    font-size: 14px;
    text-align: center;
  }
  /* Cross-link row from flavor immersion → edibles detail. The cards
     sit on the flood, with a paper-cream backing so the food images
     (transparent PNGs) have something to lift off against. Click any
     card to jump straight into that edible's detail spread.

     Grid auto-fills as many ~120px cards per row as the canvas can
     hold, wrapping to additional rows when more cards exist. No
     horizontal scroll — every related food is visible at a glance. */
  .flavor-related-row {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    gap: 12px;
  }
  .flavor-related-card {
    display: flex; flex-direction: column; gap: 8px;
    background: color-mix(in oklch, var(--paper-2) 88%, var(--flood) 12%);
    border: 1px solid color-mix(in srgb, var(--ink-on-flood) 18%, transparent);
    border-radius: 8px;
    padding: 10px 10px 12px;
    cursor: pointer;
    transition: transform .15s, box-shadow .15s, background .15s;
    text-align: left;
    font: inherit; color: inherit;
  }
  .flavor-related-card:hover {
    background: var(--paper-2);
    transform: translateY(-2px);
    box-shadow: 0 6px 16px -6px color-mix(in srgb, var(--ink-on-flood) 40%, transparent);
  }
  .flavor-related-card:focus-visible {
    outline: 2px solid var(--ink-on-flood);
    outline-offset: 2px;
  }
  /* Example variant — standalone PNG references that don't live in
     the edibles database (mazui's rotten/moldy set is the canonical
     case). Rendered as non-clickable <div>, so cursor/hover/focus
     affordances are suppressed; the card is purely informational. */
  .flavor-related-card.is-example {
    cursor: default;
  }
  .flavor-related-card.is-example:hover {
    background: color-mix(in oklch, var(--paper-2) 88%, var(--flood) 12%);
    transform: none;
    box-shadow: none;
  }
  /* Verb-prefix split on example cards. The verb (kusatta/kabita/
     shioreta) renders slightly smaller and color-tinted so the
     learner sees at a glance which "gone-bad" form pairs with the
     food. The noun keeps the default ink weight — that's the actual
     food name they're learning. */
  .flavor-related-name .example-verb {
    font-family: var(--font-title);
    font-size: 14px;
    font-weight: 500;
    line-height: 1.2;
    overflow-wrap: anywhere;
    display: inline;
    margin-right: 2px;
  }
  .flavor-related-name .example-noun {
    font-family: var(--font-title);
    font-size: 18px;
    color: var(--ink);
    line-height: 1.2;
    overflow-wrap: anywhere;
    display: inline;
  }
  /* Verb color tokens — three classes shared by the example-card
     verb prefix AND the chip in the verb-notes section below. The
     hues are chosen for legibility against the mazui flood (a pale
     yellow-green #d8d9b0): yellow-green sits in the warm side of
     the spectrum, teal stretches cooler, forest-green grounds it. */
  .verb-kusatta  { color: #8a7a14; }   /* mustardy yellow-green */
  .verb-kabita   { color: #1e7575; }   /* teal */
  .verb-shioreta { color: #3e7a30; }   /* forest green */

  /* Verb-grammar notes section. Sits below the FLAVOR_EXAMPLES row,
     explaining the three "this food has gone bad" verbs the cards
     are labeled with. Each row carries an inline chip in the verb's
     tinted color matching the card labels above, plus the meaning
     and what kinds of food the verb pairs with. */
  .flavor-verb-notes {
    margin-top: 18px;
    padding: 14px 16px;
    background: color-mix(in oklch, var(--paper-2) 92%, var(--flood) 8%);
    border: 1px solid color-mix(in srgb, var(--ink-on-flood) 14%, transparent);
    border-radius: 8px;
  }
  .flavor-verb-intro {
    margin: 0 0 12px;
    font-family: var(--font-menu-en);
    font-style: italic;
    font-size: 13px;
    color: var(--ink-2);
  }
  .flavor-verb-list {
    list-style: none;
    margin: 0; padding: 0;
    display: flex; flex-direction: column;
    gap: 10px;
  }
  .flavor-verb-row {
    display: grid;
    grid-template-columns: minmax(120px, auto) 1fr auto;
    align-items: baseline;
    gap: 16px;
  }
  @media (max-width: 760px) {
    .flavor-verb-row {
      grid-template-columns: 1fr;
      gap: 4px;
    }
  }
  .flavor-verb-chip {
    display: inline-flex; align-items: baseline;
    gap: 8px;
    padding: 4px 10px;
    background: var(--paper);
    border: 1px solid color-mix(in srgb, currentColor 24%, transparent);
    border-radius: 999px;
    font-weight: 500;
  }
  .flavor-verb-kanji {
    font-family: var(--font-title);
    font-size: 16px;
  }
  .flavor-verb-romaji {
    font-family: var(--font-menu-en);
    font-style: italic;
    font-size: 11px;
    letter-spacing: 0.05em;
    color: color-mix(in srgb, currentColor 70%, var(--ink-2));
  }
  .flavor-verb-meaning {
    font-family: var(--font-body);
    font-size: 14px;
    color: var(--ink);
    line-height: 1.4;
  }
  .flavor-verb-pairs {
    font-family: var(--font-menu-en);
    font-style: italic;
    font-size: 12px;
    color: var(--ink-3);
    text-align: right;
  }
  @media (max-width: 760px) {
    .flavor-verb-pairs { text-align: left; }
  }
  .flavor-related-image {
    width: 100%; aspect-ratio: 1 / 1;
    overflow: hidden;
  }
  .flavor-related-image image-slot,
  .flavor-related-image image-slot::part(img),
  .flavor-related-image image-slot::part(frame) {
    width: 100%; height: 100%; display: block;
    object-fit: contain;
  }
  .flavor-related-image image-slot::part(frame) {
    background: transparent;
  }
  .flavor-related-image image-slot::part(img) {
    filter: drop-shadow(0 2px 5px color-mix(in srgb, var(--ink) 18%, transparent));
  }
  .flavor-related-name {
    display: flex; flex-direction: column; gap: 2px;
    min-width: 0;
  }
  .flavor-related-name .ja {
    font-family: var(--font-title);
    font-size: 18px;
    color: var(--ink);
    line-height: 1.2;
    overflow-wrap: anywhere;
  }
  @media (prefers-reduced-motion: reduce) {
    .flavor-related-card { transition: none; }
    .flavor-related-card:hover { transform: none; }
  }

  /* Drawer — collapsed English + cultural notes. Native <details>
     so a11y is free. Sits at the bottom of the immersion view.
     Opening it raises the visual weight: surface-200 paper-deep,
     stronger border. */
  .flavor-drawer {
    max-width: 1280px;
    margin: 32px auto 0;
    border-top: 1px solid color-mix(in srgb, var(--ink-on-flood) 14%, transparent);
    padding-top: 16px;
  }
  .flavor-drawer-toggle {
    display: inline-flex; align-items: center; gap: 8px;
    cursor: pointer;
    font-family: var(--font-menu-en);
    font-size: 12px; letter-spacing: 0.12em; text-transform: uppercase;
    color: color-mix(in srgb, var(--ink-on-flood) 70%, transparent);
    list-style: none;
    user-select: none;
    padding: 6px 0;
  }
  .flavor-drawer-toggle::-webkit-details-marker { display: none; }
  .flavor-drawer-toggle::before {
    content: '▸';
    transition: transform .2s ease-out;
    display: inline-block;
    font-size: 14px;
  }
  .flavor-drawer[open] .flavor-drawer-toggle::before {
    transform: rotate(90deg);
  }
  .flavor-drawer-body {
    margin-top: 12px;
    padding: 18px 22px;
    background: color-mix(in oklch, var(--flood) 75%, var(--ink-on-flood) 8%);
    border-radius: 8px;
    border: 1px solid color-mix(in srgb, var(--ink-on-flood) 14%, transparent);
    display: grid; gap: 12px;
    max-width: 70ch;
  }
  .flavor-drawer-body .en-gloss {
    font-family: var(--font-menu-en);
    font-size: 18px; font-weight: 500;
    color: var(--ink-on-flood);
    margin: 0;
  }
  .flavor-drawer-body .notes {
    font-family: var(--font-menu-en); font-style: italic;
    font-size: 15px; line-height: 1.6;
    color: color-mix(in srgb, var(--ink-on-flood) 85%, transparent);
    margin: 0;
  }

  /* Keyboard hint — small footer note, visible only on desktop
     pointer devices. Mobile users don't have keys. */
  .flavor-keys-hint {
    max-width: 1280px;
    margin: 28px auto 0;
    padding-top: 12px;
    font-family: var(--font-menu-en);
    font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase;
    color: color-mix(in srgb, var(--ink-on-flood) 50%, transparent);
    text-align: center;
  }
  @media (hover: none) {
    .flavor-keys-hint { display: none; }
  }

  /* Cross-fade transition between bento and immersion. Applied by
     the renderer briefly during state swap (.is-fading-out → swap
     → fade-in via default opacity:1 transition). Targets the actual
     wrap elements rendered by flavorsPageHTML — .flavors-bento-wrap
     and .flavors-immersion-wrap. Used by exitFlavorImmersion (back
     to bento) — the richer enter choreography below replaces this
     for bento → immersion entries. */
  .flavors-frame.is-fading-out > .flavors-bento-wrap,
  .flavors-frame.is-fading-out > .flavors-immersion-wrap {
    opacity: 0;
    transition: opacity 240ms cubic-bezier(0.4, 0, 1, 1);
  }
  .flavors-frame > .flavors-bento-wrap,
  .flavors-frame > .flavors-immersion-wrap {
    opacity: 1;
    transition: opacity 260ms cubic-bezier(0, 0, 0.2, 1);
  }

  /* ── Bento → Immersion entrance choreography ──────────────────
     Sequenced cascade triggered when the user clicks a flavor card.
     Phase A (handled in JS, ~280ms): bento cards cascade-exit with
     per-card stagger via --exit-i; the frame's .is-leaving-bento
     class scopes the exit animation. Phase B (handled in CSS below):
     after the DOM swap, the new immersion frame mounts with
     .is-entering and a wave of CSS animations fire with increasing
     animation-delay values:
        300ms flood    — color fills the canvas
        400ms hero     — the staple image dissolves + scales in
        600ms kana     — the big kanji+furigana rises from below
        720ms romaji   — canonical-food kana fades in
        820ms audio    — speaker button springs in
        920ms notes    — English gloss + cultural note fade in
       1000ms cards    — food example cards stagger from below
                         (per-card 40ms via --enter-i)
       1300ms rail     — flavor thumbs slide in from the right one
                         at a time (per-thumb 55ms via --enter-i)
     The class is removed by JS after 2200ms so subsequent flavor
     switches don't replay this — they get the lighter switchFlavor
     path (no choreography, just the color shift). */

  /* Phase A: bento card cascade exit. Cards scale down with a slight
     downward drift + opacity fade, staggered by --exit-i so the wave
     reads as a deliberate "pages closing" gesture rather than all
     cards vanishing in sync. */
  .flavors-frame.is-leaving-bento .flavor-bento-card {
    animation: flavors-bento-card-exit 280ms cubic-bezier(0.55, 0, 0.7, 0.2) forwards;
    animation-delay: calc(var(--exit-i, 0) * 14ms);
  }
  @keyframes flavors-bento-card-exit {
    0%   { opacity: 1; transform: scale(1)    translateY(0);   }
    100% { opacity: 0; transform: scale(0.88) translateY(-12px); }
  }

  /* Phase B — every entering element gets `both` as the last token in
     the animation shorthand so animation-fill-mode is applied as part
     of the shorthand itself (NOT as a separate rule — the shorthand
     resets all unspecified animation properties to their defaults,
     which would wipe a standalone fill-mode declaration that comes
     before it). With `both` set, each element holds its 0% keyframe
     state during the animation-delay window (so no flash of fully-
     visible content before the cue), and holds its 100% keyframe
     state after the animation finishes (so the page stays settled).

     Flood — the immersion wrap fades in over 400ms, carrying the
     flavor's flood color with it (the wrap already has --flood
     applied as background). Starts at 0ms so phase A's tail blends
     into phase B's head — by the time the bento's last cards finish
     fading, the new color is already half in. */
  .flavors-frame.is-entering .flavors-immersion-wrap {
    animation: flavors-flood-in 400ms cubic-bezier(0.16, 1, 0.3, 1) 0ms both;
  }
  @keyframes flavors-flood-in {
    0%   { opacity: 0; transform: translateY(8px); }
    100% { opacity: 1; transform: translateY(0); }
  }

  /* Hero image — the staple food dissolves in with a gentle scale.
     Starts 100ms after the flood begins so the eye lands on color
     first, hero second. */
  .flavors-frame.is-entering .flavor-immersion-image {
    animation: flavors-hero-in 360ms cubic-bezier(0.16, 1, 0.3, 1) 100ms both;
  }
  @keyframes flavors-hero-in {
    0%   { opacity: 0; transform: scale(0.92); }
    100% { opacity: 1; transform: scale(1); }
  }

  /* Kana title rises with a subtle bezier overshoot (y2 > 1) for a
     cute landing flourish. */
  .flavors-frame.is-entering .flavor-immersion-kana {
    animation: flavors-kana-in 320ms cubic-bezier(0.34, 1.4, 0.64, 1) 300ms both;
  }
  @keyframes flavors-kana-in {
    0%   { opacity: 0; transform: translateY(24px); }
    100% { opacity: 1; transform: translateY(0); }
  }

  /* Romaji + audio — sequential, 100ms apart, both gentle slide-ups
     (audio with a soft scale-overshoot to land like a button press). */
  .flavors-frame.is-entering .flavor-immersion-food {
    animation: flavors-soft-in 280ms cubic-bezier(0.16, 1, 0.3, 1) 420ms both;
  }
  .flavors-frame.is-entering .flavor-immersion-audio {
    animation: flavors-audio-in 280ms cubic-bezier(0.34, 1.4, 0.64, 1) 520ms both;
  }
  @keyframes flavors-soft-in {
    0%   { opacity: 0; transform: translateY(12px); }
    100% { opacity: 1; transform: translateY(0); }
  }
  @keyframes flavors-audio-in {
    0%   { opacity: 0; transform: scale(0.6); }
    100% { opacity: 1; transform: scale(1); }
  }

  /* Notes (English gloss + cultural note) — quiet fade-up. */
  .flavors-frame.is-entering .flavor-related-notes {
    animation: flavors-soft-in 320ms cubic-bezier(0.16, 1, 0.3, 1) 620ms both;
  }

  /* Food cards — cascade from below with per-card stagger via
     --enter-i. 40ms × ~12 cards = ~480ms wave for the cascade. */
  .flavors-frame.is-entering .flavor-related-card {
    animation: flavors-card-in 320ms cubic-bezier(0.16, 1, 0.3, 1) both;
    animation-delay: calc(700ms + var(--enter-i, 0) * 40ms);
  }
  @keyframes flavors-card-in {
    0%   { opacity: 0; transform: translateY(20px) scale(0.96); }
    100% { opacity: 1; transform: translateY(0)   scale(1); }
  }

  /* Top rail — last to arrive. Back button + key-hint fade in
     together; the 10 flavor thumbs slide in from the right one at a
     time. 55ms × 10 thumbs = 550ms wave. */
  .flavors-frame.is-entering .flavor-rail-back {
    animation: flavors-soft-in 280ms cubic-bezier(0.16, 1, 0.3, 1) 1100ms both;
  }
  .flavors-frame.is-entering .flavor-rail-keys {
    animation: flavors-soft-in 280ms cubic-bezier(0.16, 1, 0.3, 1) 1200ms both;
  }
  .flavors-frame.is-entering .flavor-rail-thumb {
    animation: flavors-rail-thumb-in 380ms cubic-bezier(0.16, 1, 0.3, 1) both;
    animation-delay: calc(1300ms + var(--enter-i, 0) * 55ms);
  }
  @keyframes flavors-rail-thumb-in {
    0%   { opacity: 0; transform: translateX(40px) scale(0.85); }
    100% { opacity: 1; transform: translateX(0)    scale(1); }
  }

  /* ── Reverse choreography: immersion → bento (exitFlavorImmersion) */
  /* Phase A: is-leaving-immersion — the immersion content unwinds.
     Rail thumbs slide right + fade out with REVERSE stagger (the
     last-rendered thumb leaves first via 9 - --enter-i × 30ms);
     hero + identity collapse, food cards drop + fade, wrap fades
     toward the underlying paper. Each shorthand carries `both` to
     hold the FROM keyframe state through any inherent setup window. */
  .flavors-frame.is-leaving-immersion .flavor-rail-thumb {
    animation: flavors-rail-thumb-out 280ms cubic-bezier(0.55, 0, 0.7, 0.2) both;
    animation-delay: calc((9 - var(--enter-i, 0)) * 25ms);
  }
  @keyframes flavors-rail-thumb-out {
    0%   { opacity: 1; transform: translateX(0)    scale(1); }
    100% { opacity: 0; transform: translateX(40px) scale(0.85); }
  }
  .flavors-frame.is-leaving-immersion .flavor-rail-back,
  .flavors-frame.is-leaving-immersion .flavor-rail-keys {
    animation: flavors-fade-down 220ms cubic-bezier(0.55, 0, 0.7, 0.2) both;
    animation-delay: 50ms;
  }
  .flavors-frame.is-leaving-immersion .flavor-related-card {
    animation: flavors-card-out 240ms cubic-bezier(0.55, 0, 0.7, 0.2) both;
    /* Reverse stagger: tail cards leave first. Capped at index 14
       so a 30-item flavor doesn't push the wave past phase B. */
    animation-delay: calc(min(var(--enter-i, 0), 14) * 18ms);
  }
  @keyframes flavors-card-out {
    0%   { opacity: 1; transform: translateY(0)    scale(1); }
    100% { opacity: 0; transform: translateY(16px) scale(0.96); }
  }
  .flavors-frame.is-leaving-immersion .flavor-related-notes {
    animation: flavors-fade-down 240ms cubic-bezier(0.55, 0, 0.7, 0.2) 100ms both;
  }
  .flavors-frame.is-leaving-immersion .flavor-immersion-audio {
    animation: flavors-audio-out 220ms cubic-bezier(0.55, 0, 0.7, 0.2) 160ms both;
  }
  @keyframes flavors-audio-out {
    0%   { opacity: 1; transform: scale(1); }
    100% { opacity: 0; transform: scale(0.6); }
  }
  .flavors-frame.is-leaving-immersion .flavor-immersion-food {
    animation: flavors-fade-down 240ms cubic-bezier(0.55, 0, 0.7, 0.2) 180ms both;
  }
  .flavors-frame.is-leaving-immersion .flavor-immersion-kana {
    animation: flavors-kana-out 280ms cubic-bezier(0.55, 0, 0.7, 0.2) 200ms both;
  }
  @keyframes flavors-kana-out {
    0%   { opacity: 1; transform: translateY(0); }
    100% { opacity: 0; transform: translateY(18px); }
  }
  .flavors-frame.is-leaving-immersion .flavor-immersion-image {
    animation: flavors-hero-out 280ms cubic-bezier(0.55, 0, 0.7, 0.2) 220ms both;
  }
  @keyframes flavors-hero-out {
    0%   { opacity: 1; transform: scale(1); }
    100% { opacity: 0; transform: scale(0.92); }
  }
  .flavors-frame.is-leaving-immersion .flavors-immersion-wrap {
    animation: flavors-flood-out 340ms cubic-bezier(0.55, 0, 0.7, 0.2) 160ms both;
  }
  @keyframes flavors-flood-out {
    0%   { opacity: 1; transform: translateY(0); }
    100% { opacity: 0; transform: translateY(6px); }
  }
  @keyframes flavors-fade-down {
    0%   { opacity: 1; transform: translateY(0); }
    100% { opacity: 0; transform: translateY(10px); }
  }

  /* Phase B: is-entering-bento — bento cards cascade in (reverse of
     the bento-card-exit they did on the way to immersion). Header
     rises gently above. */
  .flavors-frame.is-entering-bento .flavors-bento-head {
    animation: flavors-soft-in 280ms cubic-bezier(0.16, 1, 0.3, 1) 0ms both;
  }
  .flavors-frame.is-entering-bento .flavor-bento-card {
    animation: flavors-bento-card-in 360ms cubic-bezier(0.16, 1, 0.3, 1) both;
    animation-delay: calc(80ms + var(--exit-i, 0) * 28ms);
  }
  @keyframes flavors-bento-card-in {
    0%   { opacity: 0; transform: scale(0.92) translateY(16px); }
    100% { opacity: 1; transform: scale(1)    translateY(0); }
  }

  /* ── Within-immersion swap: switchFlavor (rail click, arrow keys) */
  /* Phase A: is-switching-out — body content exits (no rail). */
  .flavors-frame.is-switching-out .flavor-related-card {
    animation: flavors-card-out 220ms cubic-bezier(0.55, 0, 0.7, 0.2) both;
    animation-delay: calc(min(var(--enter-i, 0), 14) * 14ms);
  }
  .flavors-frame.is-switching-out .flavor-related-notes {
    animation: flavors-fade-down 200ms cubic-bezier(0.55, 0, 0.7, 0.2) 40ms both;
  }
  .flavors-frame.is-switching-out .flavor-immersion-audio {
    animation: flavors-audio-out 200ms cubic-bezier(0.55, 0, 0.7, 0.2) 80ms both;
  }
  .flavors-frame.is-switching-out .flavor-immersion-food {
    animation: flavors-fade-down 200ms cubic-bezier(0.55, 0, 0.7, 0.2) 90ms both;
  }
  .flavors-frame.is-switching-out .flavor-immersion-kana {
    animation: flavors-kana-out 220ms cubic-bezier(0.55, 0, 0.7, 0.2) 110ms both;
  }
  .flavors-frame.is-switching-out .flavor-immersion-image {
    animation: flavors-hero-out 220ms cubic-bezier(0.55, 0, 0.7, 0.2) 130ms both;
  }

  /* Phase B: is-switching-in — body content re-enters. Rail does
     NOT animate (it just updates its is-active state on the new
     render). Shorter cascade than the full bento→immersion entry. */
  .flavors-frame.is-switching-in .flavor-immersion-image {
    animation: flavors-hero-in 320ms cubic-bezier(0.16, 1, 0.3, 1) 60ms both;
  }
  .flavors-frame.is-switching-in .flavor-immersion-kana {
    animation: flavors-kana-in 300ms cubic-bezier(0.34, 1.4, 0.64, 1) 160ms both;
  }
  .flavors-frame.is-switching-in .flavor-immersion-food {
    animation: flavors-soft-in 260ms cubic-bezier(0.16, 1, 0.3, 1) 240ms both;
  }
  .flavors-frame.is-switching-in .flavor-immersion-audio {
    animation: flavors-audio-in 260ms cubic-bezier(0.34, 1.4, 0.64, 1) 300ms both;
  }
  .flavors-frame.is-switching-in .flavor-related-notes {
    animation: flavors-soft-in 280ms cubic-bezier(0.16, 1, 0.3, 1) 360ms both;
  }
  .flavors-frame.is-switching-in .flavor-related-card {
    animation: flavors-card-in 280ms cubic-bezier(0.16, 1, 0.3, 1) both;
    animation-delay: calc(420ms + min(var(--enter-i, 0), 14) * 30ms);
  }

  @media (prefers-reduced-motion: reduce) {
    .flavors-frame.is-fading-out > .flavors-bento-wrap,
    .flavors-frame.is-fading-out > .flavors-immersion-wrap,
    .flavors-frame > .flavors-bento-wrap,
    .flavors-frame > .flavors-immersion-wrap {
      transition: none;
      opacity: 1;
    }
    /* All choreography keyframes are zeroed so reduced-motion users
       get instant swaps. JS skips the class-add tagging entirely
       (see enterFlavorImmersion / exitFlavorImmersion / switchFlavor),
       this is the belt-and-suspenders guard. */
    .flavors-frame.is-leaving-bento .flavor-bento-card,
    .flavors-frame.is-entering .flavors-immersion-wrap,
    .flavors-frame.is-entering .flavor-immersion-image,
    .flavors-frame.is-entering .flavor-immersion-kana,
    .flavors-frame.is-entering .flavor-immersion-food,
    .flavors-frame.is-entering .flavor-immersion-audio,
    .flavors-frame.is-entering .flavor-related-notes,
    .flavors-frame.is-entering .flavor-related-card,
    .flavors-frame.is-entering .flavor-rail-back,
    .flavors-frame.is-entering .flavor-rail-keys,
    .flavors-frame.is-entering .flavor-rail-thumb,
    .flavors-frame.is-leaving-immersion .flavor-rail-thumb,
    .flavors-frame.is-leaving-immersion .flavor-rail-back,
    .flavors-frame.is-leaving-immersion .flavor-rail-keys,
    .flavors-frame.is-leaving-immersion .flavor-related-card,
    .flavors-frame.is-leaving-immersion .flavor-related-notes,
    .flavors-frame.is-leaving-immersion .flavor-immersion-audio,
    .flavors-frame.is-leaving-immersion .flavor-immersion-food,
    .flavors-frame.is-leaving-immersion .flavor-immersion-kana,
    .flavors-frame.is-leaving-immersion .flavor-immersion-image,
    .flavors-frame.is-leaving-immersion .flavors-immersion-wrap,
    .flavors-frame.is-entering-bento .flavors-bento-head,
    .flavors-frame.is-entering-bento .flavor-bento-card,
    .flavors-frame.is-switching-out .flavor-related-card,
    .flavors-frame.is-switching-out .flavor-related-notes,
    .flavors-frame.is-switching-out .flavor-immersion-audio,
    .flavors-frame.is-switching-out .flavor-immersion-food,
    .flavors-frame.is-switching-out .flavor-immersion-kana,
    .flavors-frame.is-switching-out .flavor-immersion-image,
    .flavors-frame.is-switching-in .flavor-immersion-image,
    .flavors-frame.is-switching-in .flavor-immersion-kana,
    .flavors-frame.is-switching-in .flavor-immersion-food,
    .flavors-frame.is-switching-in .flavor-immersion-audio,
    .flavors-frame.is-switching-in .flavor-related-notes,
    .flavors-frame.is-switching-in .flavor-related-card {
      animation: none;
    }
  }

  /* ── End Flavors page ────────────────────────────────────────── */

  /* ─────────────────────────────────────────────────────────────────
     Edibles choreography (mirrors the flavors bento↔immersion idea
     but scaled down — no flood-color, no kana glyph fly-in, just a
     stagger swap between two grids). Two pairs:
       categories → items  (.is-leaving-categories / .is-entering-items)
       items → categories  (.is-leaving-items / .is-entering-categories)
     Every animation shorthand keeps `both` as the last token so the
     element starts at its keyframe `from` state during animation-delay.
     Forgetting that token = elements visible behind the animation.
     ───────────────────────────────────────────────────────────────── */

  /* Phase A: leaving categories — tiles fall away with a small lift+fade,
     forward stagger so the first tile leaves first. */
  .edibles-frame.is-leaving-categories .edibles-category-tile {
    animation: edibles-tile-out 280ms cubic-bezier(0.7, 0, 0.84, 0) both;
    animation-delay: calc(var(--enter-i, 0) * 22ms);
  }
  .edibles-frame.is-leaving-categories .edibles-head {
    animation: edibles-soft-out 240ms cubic-bezier(0.7, 0, 0.84, 0) both;
  }
  /* Phase B: entering items — cards rise and fade in with a forward
     stagger. Capped at 14 so long item lists don't slow the page. */
  .edibles-frame.is-entering-items .edibles-item-card {
    animation: edibles-card-in 320ms cubic-bezier(0.16, 1, 0.3, 1) both;
    animation-delay: calc(min(var(--enter-i, 0), 14) * 28ms);
  }
  .edibles-frame.is-entering-items .edibles-back {
    animation: edibles-soft-in 280ms cubic-bezier(0.16, 1, 0.3, 1) both;
  }
  /* Phase A: leaving items — reverse stagger so the last item leaves
     first (mirrors the entry stagger backwards). */
  .edibles-frame.is-leaving-items .edibles-item-card {
    animation: edibles-card-out 240ms cubic-bezier(0.7, 0, 0.84, 0) both;
    animation-delay: calc((14 - min(var(--enter-i, 0), 14)) * 18ms);
  }
  .edibles-frame.is-leaving-items .edibles-back {
    animation: edibles-soft-out 220ms cubic-bezier(0.7, 0, 0.84, 0) both;
  }
  /* Phase B: entering categories — tiles cascade back in. */
  .edibles-frame.is-entering-categories .edibles-category-tile {
    animation: edibles-tile-in 360ms cubic-bezier(0.16, 1, 0.3, 1) both;
    animation-delay: calc(var(--enter-i, 0) * 28ms);
  }
  .edibles-frame.is-entering-categories .edibles-head {
    animation: edibles-soft-in 320ms cubic-bezier(0.16, 1, 0.3, 1) both;
  }

  @keyframes edibles-tile-out {
    from { opacity: 1; transform: translateY(0) scale(1); }
    to   { opacity: 0; transform: translateY(-14px) scale(0.965); }
  }
  @keyframes edibles-tile-in {
    from { opacity: 0; transform: translateY(18px) scale(0.94); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
  }
  @keyframes edibles-card-out {
    from { opacity: 1; transform: translateY(0) scale(1); }
    to   { opacity: 0; transform: translateY(20px) scale(0.96); }
  }
  @keyframes edibles-card-in {
    from { opacity: 0; transform: translateY(22px) scale(0.96); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
  }
  @keyframes edibles-soft-out {
    from { opacity: 1; }
    to   { opacity: 0; }
  }
  @keyframes edibles-soft-in {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: translateY(0); }
  }

  @media (prefers-reduced-motion: reduce) {
    .edibles-frame.is-leaving-categories .edibles-category-tile,
    .edibles-frame.is-leaving-categories .edibles-head,
    .edibles-frame.is-entering-items .edibles-item-card,
    .edibles-frame.is-entering-items .edibles-back,
    .edibles-frame.is-leaving-items .edibles-item-card,
    .edibles-frame.is-leaving-items .edibles-back,
    .edibles-frame.is-entering-categories .edibles-category-tile,
    .edibles-frame.is-entering-categories .edibles-head {
      animation: none;
    }
  }

  /* ─────────────────────────────────────────────────────────────────
     Edibles page-entry choreography (.is-entering-edibles).
     Plays only when the user lands on edibles from the sidebar (or
     first paint). Bigger production than the in-page categories↔items
     swap — the head fades up first, then each tile rises with a small
     3D tilt, then the glyph kanji scales-pop inside it, then the three
     stack images bloom out from the glyph to their resting positions,
     then the furigana fades in last.

     Every `animation:` shorthand keeps `both` as the last token so
     animation-fill-mode is set as part of the shorthand. Forgetting
     that = elements visible at opacity:1 during their animation-delay
     window — the same bug we already fixed once on the flavors page.
     ───────────────────────────────────────────────────────────────── */
  .edibles-frame.is-entering-edibles .edibles-head {
    animation: edibles-head-rise 520ms cubic-bezier(0.16, 1, 0.3, 1) 60ms both;
  }
  @keyframes edibles-head-rise {
    from { opacity: 0; transform: translateY(14px); }
    to   { opacity: 1; transform: translateY(0); }
  }

  /* Tile itself — small rise + tilt-in. Forward stagger. Perspective
     has to live on the PARENT (the grid) for rotateX on the tile to
     render as a real 3D tilt; perspective on the rotating element
     itself has no effect, the rotation collapses to a 2D Y-skew. */
  .edibles-frame.is-entering-edibles .edibles-categories {
    perspective: 1100px;
    perspective-origin: 50% 0%;
  }
  .edibles-frame.is-entering-edibles .edibles-category-tile {
    animation: edibles-tile-bloom 540ms cubic-bezier(0.34, 1.4, 0.64, 1) both;
    animation-delay: calc(240ms + var(--enter-i, 0) * 70ms);
    transform-style: preserve-3d;
    will-change: transform, opacity;
  }
  @keyframes edibles-tile-bloom {
    0%   { opacity: 0; transform: translateY(28px) scale(0.92) rotateX(8deg); }
    60%  { opacity: 1; transform: translateY(-2px) scale(1.01) rotateX(0); }
    100% { opacity: 1; transform: translateY(0)    scale(1)    rotateX(0); }
  }

  /* Kanji glyph — scales in with a soft rebound, anchored to the
     tile's animation so it lands just as the tile finishes settling. */
  .edibles-frame.is-entering-edibles .edibles-category-glyph {
    animation: edibles-glyph-press 480ms cubic-bezier(0.34, 1.5, 0.64, 1) both;
    animation-delay: calc(420ms + var(--enter-i, 0) * 70ms);
  }
  @keyframes edibles-glyph-press {
    0%   { opacity: 0; transform: scale(0.55); }
    55%  { opacity: 1; transform: scale(1.08); }
    100% { opacity: 1; transform: scale(1); }
  }

  /* The three stack images bloom OUT from the glyph (start at center,
     slide to their resting positions). Each position uses its own
     keyframe because the stack-image rule already sets data-pos-specific
     translate transforms at rest; we have to land back on those values.

     Resting transforms come from the existing styles, captured here so
     the 100% frame matches the no-animation default exactly. If those
     resting transforms change, update these `to` frames too. */
  /* Stack images bloom OUT from the glyph (start at the tile's center
     scaled small, slide to their resting positions). The resting
     transforms are hardcoded in the `to` frames so the animation lands
     exactly where the static rule sits — without that, the element
     snaps from translate(0,0) to its resting offset the instant the
     class is removed. If the resting transforms in the static rules
     above (line ~7593-7606) change, mirror the change here. */
  .edibles-frame.is-entering-edibles .edibles-category-stack-img[data-pos="left"] {
    animation: edibles-stack-bloom-left 600ms cubic-bezier(0.16, 1, 0.3, 1) both;
    animation-delay: calc(580ms + var(--enter-i, 0) * 70ms);
  }
  .edibles-frame.is-entering-edibles .edibles-category-stack-img[data-pos="right"] {
    animation: edibles-stack-bloom-right 600ms cubic-bezier(0.16, 1, 0.3, 1) both;
    animation-delay: calc(640ms + var(--enter-i, 0) * 70ms);
  }
  .edibles-frame.is-entering-edibles .edibles-category-stack-img[data-pos="center"] {
    animation: edibles-stack-bloom-center 540ms cubic-bezier(0.34, 1.4, 0.64, 1) both;
    animation-delay: calc(700ms + var(--enter-i, 0) * 70ms);
  }
  @keyframes edibles-stack-bloom-left {
    0%   { opacity: 0; transform: translateX(0)     rotate(0)    scale(0.5); }
    100% { opacity: 1; transform: translateX(-36px) rotate(-9deg) scale(1); }
  }
  @keyframes edibles-stack-bloom-right {
    0%   { opacity: 0; transform: translateX(0)    rotate(0)   scale(0.5); }
    100% { opacity: 1; transform: translateX(36px) rotate(9deg) scale(1); }
  }
  @keyframes edibles-stack-bloom-center {
    0%   { opacity: 0; transform: translateY(0)    scale(0.5); }
    100% { opacity: 1; transform: translateY(-4px) scale(1); }
  }

  /* Furigana fades in last so the tile feels labeled, not pre-labeled. */
  .edibles-frame.is-entering-edibles .edibles-category-furigana {
    animation: edibles-furigana-fade 360ms cubic-bezier(0.16, 1, 0.3, 1) both;
    animation-delay: calc(760ms + var(--enter-i, 0) * 70ms);
  }
  @keyframes edibles-furigana-fade {
    from { opacity: 0; transform: translateY(4px); }
    to   { opacity: 1; transform: translateY(0); }
  }

  @media (prefers-reduced-motion: reduce) {
    .edibles-frame.is-entering-edibles .edibles-head,
    .edibles-frame.is-entering-edibles .edibles-category-tile,
    .edibles-frame.is-entering-edibles .edibles-category-glyph,
    .edibles-frame.is-entering-edibles .edibles-category-stack-img[data-pos="left"],
    .edibles-frame.is-entering-edibles .edibles-category-stack-img[data-pos="right"],
    .edibles-frame.is-entering-edibles .edibles-category-stack-img[data-pos="center"],
    .edibles-frame.is-entering-edibles .edibles-category-furigana {
      animation: none;
    }
  }

  /* ─────────────────────────────────────────────────────────────────
     Items ↔ Detail "shared element" choreography. The clicked card and
     the detail hero are the same picture rendered twice; the animation
     simulates one item picked up out of the grid and brought close.

     Outbound (items → detail): the clicked card hops UP and scales to
     ~1.18 while sibling cards fade+shrink around it (so the eye stays
     on the hero), then swap, then the detail hero "settles" from 1.18
     down to 1.0 — visually continuous with the card's last frame.

     Inbound (detail → items): hero shrinks back toward the card size,
     swap, the matching card "lands" with a soft hop while its siblings
     stagger-fade back in.

     Implementation notes:
       • The clicked card carries `.is-zoom-target`; that rule has
         strictly higher specificity than the sibling-cascade rule
         (one extra class), so it wins on the clicked card regardless
         of source order. z-index lift via the target class so the hop
         renders above neighbors.
       • The detail-hero scale tail-frame (1.0) matches its static
         resting transform exactly, so when the class clears there's
         no snap.
       • Every animation shorthand keeps `both` as the last token so
         animation-fill-mode is held during the delay/post-anim window.
     ───────────────────────────────────────────────────────────────── */

  /* Phase A — items leaving (sibling fade + hop on the target) */
  .edibles-frame.is-zoom-out-items .edibles-item-card {
    animation: edibles-card-fall-out 260ms cubic-bezier(0.7, 0, 0.84, 0) both;
    animation-delay: calc(var(--enter-i, 0) * 10ms);
  }
  .edibles-frame.is-zoom-out-items .edibles-item-card.is-zoom-target {
    /* Smooth ease curve (no overshoot). The previous (0.34, 1.4, 0.64, 1)
       had y2 > 1, which combined with a 3-point keyframe (0 → 45% → 100%)
       made the card overshoot mid-flight and "settle" between frames —
       reading as a stutter. Save the bouncy curve for the land-hop on
       the way back (where the rebound is earned). */
    animation: edibles-card-hop-out 380ms cubic-bezier(0.32, 0.72, 0, 1) both;
    animation-delay: 0ms;
    z-index: 10;
    position: relative;
    transform-origin: center bottom;
  }
  .edibles-frame.is-zoom-out-items .edibles-back {
    animation: edibles-soft-out 220ms cubic-bezier(0.7, 0, 0.84, 0) both;
  }
  @keyframes edibles-card-fall-out {
    from { opacity: 1; transform: translateY(0)   scale(1); }
    to   { opacity: 0; transform: translateY(10px) scale(0.94); }
  }
  /* Monotonic 0 → 100%: card lifts and scales continuously while
     fading. No mid-point keyframe so the smooth ease curve isn't
     fighting an intermediate target. */
  @keyframes edibles-card-hop-out {
    from { opacity: 1; transform: translateY(0)    scale(1); }
    to   { opacity: 0; transform: translateY(-34px) scale(1.32); }
  }

  /* Phase B — detail entering. Hero settles from 1.18 → 1.0 to read
     as "the card we just picked up". Body cluster stagger-rises after. */
  .edibles-frame.is-zoom-in-detail .edibles-detail-hero {
    animation: edibles-hero-settle 560ms cubic-bezier(0.16, 1, 0.3, 1) 40ms both;
  }
  .edibles-frame.is-zoom-in-detail .edibles-detail-backs {
    animation: edibles-soft-in 360ms cubic-bezier(0.16, 1, 0.3, 1) 80ms both;
  }
  .edibles-frame.is-zoom-in-detail .edibles-detail-identity {
    animation: edibles-detail-rise 460ms cubic-bezier(0.16, 1, 0.3, 1) 200ms both;
  }
  .edibles-frame.is-zoom-in-detail .edibles-detail-notes {
    animation: edibles-detail-rise 460ms cubic-bezier(0.16, 1, 0.3, 1) 300ms both;
  }
  .edibles-frame.is-zoom-in-detail .edibles-detail-badges {
    animation: edibles-detail-rise 460ms cubic-bezier(0.16, 1, 0.3, 1) 380ms both;
  }
  .edibles-frame.is-zoom-in-detail .edibles-detail-similar {
    animation: edibles-detail-rise 520ms cubic-bezier(0.16, 1, 0.3, 1) 480ms both;
  }
  @keyframes edibles-hero-settle {
    0%   { opacity: 0; transform: scale(1.18); }
    100% { opacity: 1; transform: scale(1); }
  }
  @keyframes edibles-detail-rise {
    from { opacity: 0; transform: translateY(12px); }
    to   { opacity: 1; transform: translateY(0); }
  }

  /* Phase A — detail leaving (mirror of zoom-in). Hero shrinks back
     down, body cluster drops + fades. */
  .edibles-frame.is-zoom-out-detail .edibles-detail-hero {
    animation: edibles-hero-shrink 320ms cubic-bezier(0.7, 0, 0.84, 0) 40ms both;
  }
  .edibles-frame.is-zoom-out-detail .edibles-detail-backs {
    animation: edibles-soft-out 200ms cubic-bezier(0.7, 0, 0.84, 0) both;
  }
  .edibles-frame.is-zoom-out-detail .edibles-detail-identity {
    animation: edibles-detail-drop 240ms cubic-bezier(0.7, 0, 0.84, 0) both;
  }
  .edibles-frame.is-zoom-out-detail .edibles-detail-notes {
    animation: edibles-detail-drop 240ms cubic-bezier(0.7, 0, 0.84, 0) 40ms both;
  }
  .edibles-frame.is-zoom-out-detail .edibles-detail-badges {
    animation: edibles-detail-drop 240ms cubic-bezier(0.7, 0, 0.84, 0) 80ms both;
  }
  .edibles-frame.is-zoom-out-detail .edibles-detail-similar {
    animation: edibles-detail-drop 240ms cubic-bezier(0.7, 0, 0.84, 0) 100ms both;
  }
  @keyframes edibles-hero-shrink {
    0%   { opacity: 1; transform: scale(1); }
    100% { opacity: 0; transform: scale(1.2); }
  }
  @keyframes edibles-detail-drop {
    from { opacity: 1; transform: translateY(0); }
    to   { opacity: 0; transform: translateY(10px); }
  }

  /* Phase B — items entering from detail back. The returning card
     lands first with a hop (mirror of hop-out), then siblings cascade.
     The hop starts from 1.36/translateY(-34px) — the same end-state
     the hop-out reached — so the eye perceives continuity. */
  .edibles-frame.is-zoom-in-items .edibles-item-card {
    animation: edibles-card-fall-in 380ms cubic-bezier(0.16, 1, 0.3, 1) both;
    animation-delay: calc(180ms + var(--enter-i, 0) * 12ms);
  }
  .edibles-frame.is-zoom-in-items .edibles-item-card.is-zoom-target {
    animation: edibles-card-land-hop 560ms cubic-bezier(0.34, 1.5, 0.64, 1) both;
    animation-delay: 0ms;
    z-index: 10;
    position: relative;
  }
  .edibles-frame.is-zoom-in-items .edibles-back {
    animation: edibles-soft-in 360ms cubic-bezier(0.16, 1, 0.3, 1) 60ms both;
  }
  @keyframes edibles-card-fall-in {
    from { opacity: 0; transform: translateY(8px) scale(0.96); }
    to   { opacity: 1; transform: translateY(0)   scale(1); }
  }
  @keyframes edibles-card-land-hop {
    0%   { opacity: 0; transform: translateY(-34px) scale(1.36); }
    60%  { opacity: 1; transform: translateY(-6px)  scale(1.06); }
    100% { opacity: 1; transform: translateY(0)     scale(1); }
  }

  /* ─────────────────────────────────────────────────────────────────
     Detail ↔ Detail walk: slide carousel.
     The ONLY edibles transition that doesn't use the shared-element
     hop. Walk = sequential cycling, so it should read as "the
     carousel scrolled" — current frame slides off one side, next
     frame slides in from the opposite side.

     The .edibles-detail container animates as a whole — its internal
     elements come along for the ride. This is intentionally simpler
     than the multi-phase enter/exit cascades elsewhere: walking is
     about pace and continuity, not theater.
     ───────────────────────────────────────────────────────────────── */
  .edibles-frame.is-sliding-out-left .edibles-detail {
    animation: edibles-detail-slide-out-left 320ms cubic-bezier(0.65, 0, 0.35, 1) both;
    will-change: transform, opacity;
  }
  .edibles-frame.is-sliding-out-right .edibles-detail {
    animation: edibles-detail-slide-out-right 320ms cubic-bezier(0.65, 0, 0.35, 1) both;
    will-change: transform, opacity;
  }
  .edibles-frame.is-sliding-in-from-right .edibles-detail {
    animation: edibles-detail-slide-in-from-right 420ms cubic-bezier(0.16, 1, 0.3, 1) both;
    will-change: transform, opacity;
  }
  .edibles-frame.is-sliding-in-from-left .edibles-detail {
    animation: edibles-detail-slide-in-from-left 420ms cubic-bezier(0.16, 1, 0.3, 1) both;
    will-change: transform, opacity;
  }
  @keyframes edibles-detail-slide-out-left {
    from { opacity: 1; transform: translateX(0); }
    to   { opacity: 0; transform: translateX(-32%); }
  }
  @keyframes edibles-detail-slide-out-right {
    from { opacity: 1; transform: translateX(0); }
    to   { opacity: 0; transform: translateX(32%); }
  }
  @keyframes edibles-detail-slide-in-from-right {
    from { opacity: 0; transform: translateX(32%); }
    to   { opacity: 1; transform: translateX(0); }
  }
  @keyframes edibles-detail-slide-in-from-left {
    from { opacity: 0; transform: translateX(-32%); }
    to   { opacity: 1; transform: translateX(0); }
  }

  /* ─────────────────────────────────────────────────────────────────
     Similar-card click — same shared-element pattern as items → detail.
     The clicked similar card is the same picture as the next hero;
     it hops UP and scales while the rest of the detail page fades
     down around it. Then the regular .is-zoom-in-detail handles the
     arrival on the new detail frame.

     The :not(.is-zoom-target) qualifier on the sibling-cascade rule
     is what lets the target card opt-out of the fade-down and run
     the hop-out keyframe instead. Without it, both rules would
     match the target (same specificity sans the negation), with
     unpredictable source-order winner.
     ───────────────────────────────────────────────────────────────── */
  .edibles-frame.is-launching-similar .edibles-detail-hero,
  .edibles-frame.is-launching-similar .edibles-detail-backs,
  .edibles-frame.is-launching-similar .edibles-detail-identity,
  .edibles-frame.is-launching-similar .edibles-detail-notes,
  .edibles-frame.is-launching-similar .edibles-detail-badges,
  .edibles-frame.is-launching-similar .edibles-similar-head,
  .edibles-frame.is-launching-similar .edibles-similar-card:not(.is-zoom-target) {
    animation: edibles-detail-drop 320ms cubic-bezier(0.7, 0, 0.84, 0) both;
  }
  .edibles-frame.is-launching-similar .edibles-similar-card.is-zoom-target {
    animation: edibles-similar-hop-out 460ms cubic-bezier(0.34, 1.4, 0.64, 1) both;
    z-index: 10;
    position: relative;
    will-change: transform, opacity;
  }
  /* The similar-row uses overflow-x: auto for horizontal scrolling,
     which (per CSS spec) clamps overflow-y to clip too — the hopping
     card was pinned inside its box. Naïvely switching to overflow:
     visible fixed that but also exposed ALL cards that were
     scroll-hidden off the right edge (the user saw them flash in
     during the animation window).
     The right tool here is clip-path: a final masking step that
     ignores overflow's "both axes must agree" rule. Combine:
       overflow: visible       — render children beyond the box
       clip-path: inset(...)   — clip horizontally at the row's edges
                                 but EXPAND vertically (negative
                                 inset = clip extends outward) so the
                                 hop can rise above the row.
     inset() order is top right bottom left; -120px top gives the
     card room to translate -50px + scale up; -40px bottom is a
     small buffer in case the bouncy curve dips slightly. */
  .edibles-frame.is-launching-similar .edibles-similar-row {
    overflow: visible;
    clip-path: inset(-120px 0 -40px 0);
  }
  @keyframes edibles-similar-hop-out {
    0%   { opacity: 1; transform: translateY(0)    scale(1); }
    45%  { opacity: 1; transform: translateY(-22px) scale(1.22); }
    100% { opacity: 0; transform: translateY(-50px) scale(1.4); }
  }

  @media (prefers-reduced-motion: reduce) {
    .edibles-frame.is-sliding-out-left .edibles-detail,
    .edibles-frame.is-sliding-out-right .edibles-detail,
    .edibles-frame.is-sliding-in-from-right .edibles-detail,
    .edibles-frame.is-sliding-in-from-left .edibles-detail,
    .edibles-frame.is-launching-similar .edibles-detail-hero,
    .edibles-frame.is-launching-similar .edibles-detail-backs,
    .edibles-frame.is-launching-similar .edibles-detail-identity,
    .edibles-frame.is-launching-similar .edibles-detail-notes,
    .edibles-frame.is-launching-similar .edibles-detail-badges,
    .edibles-frame.is-launching-similar .edibles-similar-head,
    .edibles-frame.is-launching-similar .edibles-similar-card,
    .edibles-frame.is-launching-similar .edibles-similar-card.is-zoom-target {
      animation: none;
    }
  }

  @media (prefers-reduced-motion: reduce) {
    .edibles-frame.is-zoom-out-items .edibles-item-card,
    .edibles-frame.is-zoom-out-items .edibles-item-card.is-zoom-target,
    .edibles-frame.is-zoom-out-items .edibles-back,
    .edibles-frame.is-zoom-in-detail .edibles-detail-hero,
    .edibles-frame.is-zoom-in-detail .edibles-detail-backs,
    .edibles-frame.is-zoom-in-detail .edibles-detail-identity,
    .edibles-frame.is-zoom-in-detail .edibles-detail-notes,
    .edibles-frame.is-zoom-in-detail .edibles-detail-badges,
    .edibles-frame.is-zoom-in-detail .edibles-detail-similar,
    .edibles-frame.is-zoom-out-detail .edibles-detail-hero,
    .edibles-frame.is-zoom-out-detail .edibles-detail-backs,
    .edibles-frame.is-zoom-out-detail .edibles-detail-identity,
    .edibles-frame.is-zoom-out-detail .edibles-detail-notes,
    .edibles-frame.is-zoom-out-detail .edibles-detail-badges,
    .edibles-frame.is-zoom-out-detail .edibles-detail-similar,
    .edibles-frame.is-zoom-in-items .edibles-item-card,
    .edibles-frame.is-zoom-in-items .edibles-item-card.is-zoom-target,
    .edibles-frame.is-zoom-in-items .edibles-back {
      animation: none;
    }
  }

  /* ─────────────────────────────────────────────────────────────────
     Textures page-entry choreography (.is-entering-textures).
     Editorial register — slow, deliberate, ink-settling. Total window
     ~2.6s; the user is here to read, not to scan. Each phase clears
     before the next one starts so the page breathes.

     Phases:
       0   ms — eyebrow fades up
       180 ms — title fades up + scales softly
       420 ms — sub-text fades up
       640 ms — spectrum hairline DRAWS left → right (scaleX)
       900 ms — spectrum tiles bloom in, sweeping with the hairline
      1320 ms — spotlight card lifts; staple-image scales from 1.08 → 1
      1580 ms — spotlight glyph/kana/audio fade up
      1820 ms — drawer fades in
      2020 ms — "Other textures" section head fades up
      2240 ms — extras section rows rise one by one with their hairlines
     ───────────────────────────────────────────────────────────────── */

  /* Title cluster */
  .textures-page.is-entering-textures .textures-page-eyebrow {
    animation: textures-fade-rise 600ms cubic-bezier(0.16, 1, 0.3, 1) 0ms both;
  }
  .textures-page.is-entering-textures .textures-page-title {
    animation: textures-title-press 700ms cubic-bezier(0.16, 1, 0.3, 1) 180ms both;
  }
  .textures-page.is-entering-textures .textures-page-sub {
    animation: textures-fade-rise 600ms cubic-bezier(0.16, 1, 0.3, 1) 420ms both;
  }
  @keyframes textures-fade-rise {
    from { opacity: 0; transform: translateY(10px); }
    to   { opacity: 1; transform: translateY(0); }
  }
  @keyframes textures-title-press {
    0%   { opacity: 0; transform: translateY(8px) scale(1.04); letter-spacing: 0.04em; }
    100% { opacity: 1; transform: translateY(0)   scale(1);    letter-spacing: 0; }
  }

  /* Spectrum — left/right edge labels fade in, the hairline draws
     across, and the tiles bloom in stagger-following the hairline. */
  .textures-page.is-entering-textures .spectrum-edge-soft {
    animation: textures-fade-rise 500ms cubic-bezier(0.16, 1, 0.3, 1) 620ms both;
  }
  .textures-page.is-entering-textures .spectrum-edge-hard {
    animation: textures-fade-rise 500ms cubic-bezier(0.16, 1, 0.3, 1) 720ms both;
  }
  .textures-page.is-entering-textures .textures-spectrum-line {
    animation: textures-line-draw 800ms cubic-bezier(0.65, 0, 0.35, 1) 640ms both;
    transform-origin: left center;
  }
  @keyframes textures-line-draw {
    from { opacity: 0; transform: scaleX(0); }
    to   { opacity: 1; transform: scaleX(1); }
  }
  .textures-page.is-entering-textures .textures-spectrum-tile-wrap {
    animation: textures-tile-rise 520ms cubic-bezier(0.16, 1, 0.3, 1) both;
    animation-delay: calc(900ms + var(--enter-i, 0) * 50ms);
  }
  @keyframes textures-tile-rise {
    from { opacity: 0; transform: translateY(14px) scale(0.95); }
    to   { opacity: 1; transform: translateY(0)   scale(1); }
  }

  /* Spotlight — the card itself lifts gently, then the staple image
     does an "ink press" (scale 1.06 → 1.0 with opacity fade), then the
     info column fades up element-by-element. */
  .textures-page.is-entering-textures .textures-spotlight {
    animation: textures-fade-rise 700ms cubic-bezier(0.16, 1, 0.3, 1) 1320ms both;
  }
  .textures-page.is-entering-textures .textures-spotlight-image {
    animation: textures-ink-press 900ms cubic-bezier(0.16, 1, 0.3, 1) 1420ms both;
  }
  @keyframes textures-ink-press {
    0%   { opacity: 0; transform: scale(1.08); }
    100% { opacity: 1; transform: scale(1); }
  }
  .textures-page.is-entering-textures .textures-spotlight-glyph {
    animation: textures-fade-rise 600ms cubic-bezier(0.16, 1, 0.3, 1) 1520ms both;
  }
  .textures-page.is-entering-textures .spotlight-kana {
    animation: textures-fade-rise 600ms cubic-bezier(0.16, 1, 0.3, 1) 1580ms both;
  }
  .textures-page.is-entering-textures .spotlight-romaji {
    animation: textures-fade-rise 600ms cubic-bezier(0.16, 1, 0.3, 1) 1680ms both;
  }
  .textures-page.is-entering-textures .textures-spotlight-audio {
    animation: textures-fade-rise 500ms cubic-bezier(0.34, 1.4, 0.64, 1) 1780ms both;
  }
  .textures-page.is-entering-textures .spotlight-description {
    animation: textures-fade-rise 700ms cubic-bezier(0.16, 1, 0.3, 1) 1820ms both;
  }
  .textures-page.is-entering-textures .textures-spotlight-examples {
    animation: textures-fade-rise 700ms cubic-bezier(0.16, 1, 0.3, 1) 1960ms both;
  }

  /* Drawer (En + cultural notes) */
  .textures-page.is-entering-textures .texture-drawer {
    animation: textures-fade-rise 600ms cubic-bezier(0.16, 1, 0.3, 1) 1980ms both;
  }

  /* "Other textures" section — head first, then the zig-zag rows
     with row-by-row delay. The image + body inside each row fade up
     together (no per-row internal stagger — keeps the page from
     dragging past its window). */
  .textures-page.is-entering-textures .textures-section-head {
    animation: textures-fade-rise 600ms cubic-bezier(0.16, 1, 0.3, 1) 2020ms both;
  }
  .textures-page.is-entering-textures .extra-texture-row {
    animation: textures-fade-rise 600ms cubic-bezier(0.16, 1, 0.3, 1) both;
    animation-delay: calc(2240ms + var(--enter-i, 0) * 90ms);
  }

  /* Keys hint sits at the very foot of the page; fades in dead last. */
  .textures-page.is-entering-textures .texture-keys-hint {
    animation: textures-fade-rise 500ms cubic-bezier(0.16, 1, 0.3, 1) 2600ms both;
  }

  @media (prefers-reduced-motion: reduce) {
    .textures-page.is-entering-textures .textures-page-eyebrow,
    .textures-page.is-entering-textures .textures-page-title,
    .textures-page.is-entering-textures .textures-page-sub,
    .textures-page.is-entering-textures .spectrum-edge-soft,
    .textures-page.is-entering-textures .spectrum-edge-hard,
    .textures-page.is-entering-textures .textures-spectrum-line,
    .textures-page.is-entering-textures .textures-spectrum-tile-wrap,
    .textures-page.is-entering-textures .textures-spotlight,
    .textures-page.is-entering-textures .textures-spotlight-image,
    .textures-page.is-entering-textures .textures-spotlight-glyph,
    .textures-page.is-entering-textures .spotlight-kana,
    .textures-page.is-entering-textures .spotlight-romaji,
    .textures-page.is-entering-textures .textures-spotlight-audio,
    .textures-page.is-entering-textures .spotlight-description,
    .textures-page.is-entering-textures .textures-spotlight-examples,
    .textures-page.is-entering-textures .texture-drawer,
    .textures-page.is-entering-textures .textures-section-head,
    .textures-page.is-entering-textures .extra-texture-row,
    .textures-page.is-entering-textures .texture-keys-hint {
      animation: none;
    }
  }

  /* ─────────────────────────────────────────────────────────────────
     Edibles database (Phase 3 of the Flavors & Textures sub-system).
     Three views in one container, gated by data-mode:
       data-mode="categories"  → 8 category tiles
       data-mode="items"       → grid of items in the active category
       data-mode="detail"      → single item spread w/ flavor + texture
                                 badges, season, notes drawer

     Unlike the Flavors page, this surface does NOT flood color — the
     items live on the paper canvas, with chip-tinted highlights for
     flavor/texture badges. Editorial chrome inherited from the wider
     app; no signature decisions specific to the database, by design
     (browse + lookup, not contemplation).
     ───────────────────────────────────────────────────────────────── */
  .edibles-frame {
    padding: 24px 32px 48px;
    max-width: 1280px;
    margin: 0 auto;
  }
  @media (max-width: 760px) {
    .edibles-frame { padding: 16px 16px 32px; }
  }

  /* View gating — show one view at a time. */
  .edibles-frame[data-mode="categories"] .edibles-items,
  .edibles-frame[data-mode="categories"] .edibles-detail,
  .edibles-frame[data-mode="items"]      .edibles-categories,
  .edibles-frame[data-mode="items"]      .edibles-detail,
  .edibles-frame[data-mode="detail"]     .edibles-categories,
  .edibles-frame[data-mode="detail"]     .edibles-items {
    display: none;
  }

  /* Page header — eyebrow + title + scene line. Shared across the
     categories view and the items view (suppressed in detail view
     where the item spread is its own thing). */
  .edibles-head {
    display: flex; flex-direction: column; align-items: center;
    gap: 6px; margin: 0 0 32px;
  }
  .edibles-eyebrow {
    font-family: var(--font-menu-en);
    font-size: 12px; letter-spacing: 0.14em; text-transform: uppercase;
    color: var(--ink-3);
  }
  /* Japanese-content variant. Uppercase + wide letter-spacing read
     fine on Roman text ("edibles · 食材") but trash Japanese kana
     (kana doesn't have casing, and wide tracking looks broken on the
     dense glyphs). Switch to the title serif, body-natural size, and
     no transform. Used on the items-view header where the eyebrow
     now carries the category kana (くだもの, やさい, etc). */
  .edibles-eyebrow.is-kana {
    font-family: var(--font-title);
    font-size: 16px;
    letter-spacing: 0.04em;
    text-transform: none;
    color: var(--ink-2);
  }
  .edibles-title {
    font-family: var(--font-title);
    font-size: clamp(2rem, 4vw, 3.2rem);
    color: var(--ink); margin: 0; line-height: 1;
  }
  /* Furigana on the edibles title — small kana above the kanji. The
     0.32em ratio matches the textures-page-title rt pattern so both
     editorial titles read the same way (eye lands on the kanji,
     reading sits above as a hint). */
  .edibles-title rt {
    font-size: 0.32em;
    font-family: var(--font-body);
    color: var(--ink-3);
    letter-spacing: 0.02em;
  }
  .edibles-sub {
    font-family: var(--font-menu-en); font-style: italic;
    font-size: 14px; color: var(--ink-2);
    max-width: 52ch; text-align: center;
  }
  .edibles-frame[data-mode="detail"] .edibles-head { display: none; }
  @media (max-width: 760px) {
    .edibles-head { margin-bottom: 22px; }
  }

  /* Container for the row of back buttons on the detail spread. On
     normal arrival, it carries one button (back to category). When
     the user came from a flavor's "foods that taste like X" row, a
     second button appears next to it labeled with the flavor's kana
     + chip color. Both wrap to a new line on narrow viewports. */
  .edibles-detail-backs {
    display: flex; flex-wrap: wrap; gap: 8px;
    margin-bottom: 18px;
  }
  /* Flavor-variant back button: same pill shape as the category one,
     leading flavorball image tinted in the source flavor's color. The
     button is otherwise identical so it sits cleanly next to the
     category back button without competing for attention. */
  .edibles-back.edibles-back-to-flavor {
    margin: 0;
  }
  .edibles-back-ball {
    width: 22px; height: 22px;
    object-fit: contain;
    display: block;
    flex-shrink: 0;
  }
  .edibles-back.edibles-back-to-flavor:hover {
    background: color-mix(in oklch, var(--paper) 80%, var(--chip) 20%);
    color: var(--ink);
    border-color: var(--chip);
  }
  /* Kana variant — used in the detail-view back row where labels are
     Japanese (category kana + flavor kana). Drops uppercase / wide
     letter-spacing (both ugly applied to hiragana), swaps to the title
     font, and locks a uniform pill height so the two buttons line up
     even though one carries a 16px arrow and the other a 22px ball. */
  .edibles-back.edibles-back-kana {
    font-family: var(--font-title);
    font-size: 16px;
    text-transform: none;
    letter-spacing: 0.02em;
    min-height: 40px;
    padding: 6px 14px 6px 10px;
    color: var(--ink);
  }
  .edibles-back.edibles-back-kana svg {
    width: 18px; height: 18px;
  }

  /* Walk buttons (前 / 次) — cycle through items in the same category,
     wrap at either end. Sit beside the back button on the same row.
     Each carries the neighbor's image thumbnail (so the user previews
     where they're going) plus a brushy kanji ruby for the direction
     word and a thin arrow on the outer edge. The image is small (28px)
     so the whole pill stays a peer of the back button, not a competing
     hero. */
  .edibles-back.edibles-back-walk {
    display: inline-flex; align-items: center;
    gap: 6px;
    padding: 4px 12px 4px 8px;
    font-family: var(--font-title);
    font-size: 15px;
    text-transform: none;
    letter-spacing: 0;
    min-height: 40px;
    color: var(--ink);
  }
  .edibles-back-walk .walk-arrow {
    font-family: var(--font-menu-en);
    font-size: 16px;
    color: var(--ink-3);
    line-height: 1;
    flex-shrink: 0;
  }
  .edibles-back-walk .walk-img {
    width: 28px; height: 28px;
    object-fit: contain;
    display: block;
    flex-shrink: 0;
  }
  .edibles-back-walk .walk-label {
    font-family: var(--font-title);
    font-size: 15px;
    color: var(--ink);
    line-height: 1.15;
  }
  .edibles-back-walk .walk-label rt {
    font-size: 0.42em;
    font-family: var(--font-body);
    color: var(--ink-3);
    letter-spacing: 0.02em;
  }
  .edibles-back-walk:hover .walk-arrow { color: var(--ink); }
  /* Asymmetric padding: a touch more on the side the arrow lives on
     so the chevron breathes against the rounded edge. */
  .edibles-back-walk-next {
    padding: 4px 8px 4px 12px;
  }

  /* ── Back button — shared across items and detail views. ──
     A grid icon for items→categories ("back to all categories"),
     a left chevron for detail→items ("back to this category"). The
     two are styled identically and absolutely positioned at the top-
     left of the .edibles-frame body. */
  .edibles-back {
    display: inline-flex; align-items: center; gap: 8px;
    margin: 0 0 18px;
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 999px;
    padding: 8px 14px 8px 10px;
    color: var(--ink-2);
    font-family: var(--font-menu-en);
    font-size: 12px; letter-spacing: 0.10em; text-transform: uppercase;
    cursor: pointer;
    transition: background .15s, color .15s, transform .15s;
  }
  .edibles-back:hover {
    background: var(--paper);
    color: var(--ink);
    transform: translateY(-1px);
  }
  .edibles-back svg { width: 16px; height: 16px; }
  /* New "← 食材の世界" composition. The button keeps the 4-square
     grid icon on the left as the surface-context affordance ("you
     are leaving the items grid, back to the categories grid"), then
     a left arrow as the back-direction signal, then the destination
     name in Japanese. The label has to override the parent's English-
     ALL-CAPS styling (uppercase + wide letter-spacing wreck Japanese
     glyphs) and switch to the brush title font. */
  .edibles-back-grid { flex-shrink: 0; }
  .edibles-back-arrow {
    font-family: var(--font-menu-en);
    font-size: 16px; font-weight: 600;
    line-height: 1;
    color: var(--ink-2);
    margin: 0 2px;
  }
  .edibles-back-label {
    font-family: var(--font-title);
    font-size: 15px;
    color: var(--ink);
    text-transform: none;
    letter-spacing: 0.01em;
    line-height: 1.15;
  }
  .edibles-back-label rt {
    font-size: 0.42em;
    font-family: var(--font-body);
    color: var(--ink-3);
    letter-spacing: 0.02em;
  }
  .edibles-back:hover .edibles-back-arrow,
  .edibles-back:hover .edibles-back-label { color: var(--ink); }
  .edibles-back:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
  }
  @media (prefers-reduced-motion: reduce) {
    .edibles-back { transition: none; }
    .edibles-back:hover { transform: none; }
  }

  /* ── View 1: Category browse ──────────────────────────────────
     8 paper tiles, 4×2 grid on desktop. Empty categories (items.length
     === 0) render dimmed with a coming-soon badge. */
  .edibles-categories {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 18px;
  }
  @media (max-width: 1100px) {
    .edibles-categories { grid-template-columns: repeat(3, 1fr); }
  }
  @media (max-width: 760px) {
    .edibles-categories { grid-template-columns: repeat(2, 1fr); gap: 12px; }
  }
  .edibles-category-tile {
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 10px;
    padding: 22px 18px;
    display: flex; flex-direction: column; align-items: center;
    gap: 8px;
    cursor: pointer;
    transition: transform .15s, box-shadow .15s, background .15s;
    text-align: center;
    font: inherit; color: inherit;
    position: relative;
  }
  .edibles-category-tile:hover {
    background: var(--paper);
    transform: translateY(-2px);
    box-shadow: 0 4px 14px -6px rgba(60, 40, 10, 0.18);
  }
  .edibles-category-tile:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
  }
  .edibles-category-tile.is-empty {
    opacity: 0.6;
    cursor: default;
  }
  .edibles-category-tile.is-empty:hover {
    transform: none; box-shadow: none; background: var(--paper-2);
  }

  /* Stacked item thumbnails — three representative items per category.
     Transparent PNGs sit directly on the tile (no white card, no border).
     A drop-shadow filter follows the silhouette so the depth still
     reads. Center thumb is largest and z-index frontmost; left/right
     are smaller, offset, and rotated. */
  .edibles-category-stack {
    position: relative;
    width: 100%;
    height: 100px;
    margin-bottom: 4px;
    display: flex;
    justify-content: center;
    align-items: center;
    pointer-events: none;
  }
  .edibles-category-stack-img {
    position: absolute;
    width: 77px; height: 77px;
    background: transparent;
    border: none;
    padding: 0;
    filter: drop-shadow(0 3px 6px rgba(60, 40, 10, 0.28));
    display: flex; align-items: center; justify-content: center;
    transition: transform .25s cubic-bezier(.2,.7,.2,1);
    will-change: transform;
  }
  .edibles-category-stack-img image-slot {
    width: 100%; height: 100%; display: block;
  }
  .edibles-category-stack-img image-slot::part(frame) {
    background: transparent;
    border: none;
    box-shadow: none;
  }
  .edibles-category-stack-img[data-pos="left"]   {
    transform: translateX(-36px) rotate(-9deg);
    z-index: 1;
  }
  .edibles-category-stack-img[data-pos="right"]  {
    transform: translateX(36px)  rotate(9deg);
    z-index: 2;
  }
  .edibles-category-stack-img[data-pos="center"] {
    transform: translateY(-4px);
    z-index: 3;
    width: 86px; height: 86px;
    filter: drop-shadow(0 4px 9px rgba(60, 40, 10, 0.32));
  }
  .edibles-category-tile:hover .edibles-category-stack-img[data-pos="left"]   {
    transform: translateX(-48px) translateY(-2px) rotate(-12deg);
  }
  .edibles-category-tile:hover .edibles-category-stack-img[data-pos="right"]  {
    transform: translateX(48px)  translateY(-2px) rotate(12deg);
  }
  .edibles-category-tile:hover .edibles-category-stack-img[data-pos="center"] {
    transform: translateY(-7px) scale(1.04);
  }
  .edibles-category-tile.is-empty .edibles-category-stack { display: none; }
  @media (prefers-reduced-motion: reduce) {
    .edibles-category-stack-img { transition: none; }
    .edibles-category-tile:hover .edibles-category-stack-img[data-pos="left"],
    .edibles-category-tile:hover .edibles-category-stack-img[data-pos="right"],
    .edibles-category-tile:hover .edibles-category-stack-img[data-pos="center"] {
      transform: none;
    }
    .edibles-category-stack-img[data-pos="left"]   { transform: translateX(-36px) rotate(-9deg); }
    .edibles-category-stack-img[data-pos="right"]  { transform: translateX(36px)  rotate(9deg); }
    .edibles-category-stack-img[data-pos="center"] { transform: translateY(-4px); }
  }
  .edibles-category-glyph {
    font-family: var(--font-title);
    font-size: 56px;
    line-height: 1;
    color: var(--ink);
  }
  .edibles-category-furigana {
    font-family: var(--font-body);
    font-size: 20px; letter-spacing: 0.05em;
    color: var(--ink-2);
    margin-top: 2px;
  }
  @media (prefers-reduced-motion: reduce) {
    .edibles-category-tile { transition: none; }
    .edibles-category-tile:hover { transform: none; }
  }

  /* ── View 2: Item grid in a category ──────────────────────────
     Denser than the categories grid — items are smaller, more per
     row. Each card carries image + kana + 2-3 small flavor chips.  */
  .edibles-items {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    gap: 16px;
  }
  @media (max-width: 1100px) {
    .edibles-items { grid-template-columns: repeat(4, 1fr); }
  }
  @media (max-width: 760px) {
    .edibles-items { grid-template-columns: repeat(2, 1fr); gap: 12px; }
  }
  .edibles-item-card {
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 10px;
    padding: 12px 12px 14px;
    display: flex; flex-direction: column; gap: 10px;
    cursor: pointer;
    transition: transform .15s, box-shadow .15s, background .15s;
    text-align: left;
    font: inherit; color: inherit;
  }
  .edibles-item-card:hover {
    background: var(--paper);
    transform: translateY(-2px);
    box-shadow: 0 4px 14px -6px rgba(60, 40, 10, 0.18);
  }
  .edibles-item-card:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
  }
  .edibles-item-image {
    width: 100%; aspect-ratio: 1 / 1;
    overflow: hidden;
  }
  .edibles-item-image image-slot,
  .edibles-item-image image-slot::part(img),
  .edibles-item-image image-slot::part(frame) {
    width: 100%; height: 100%; display: block;
    object-fit: contain;
  }
  /* Override the image-slot's built-in rgba(0,0,0,.04) frame backdrop —
     we want the transparent food to sit directly on the card's paper
     surface, no faint dark rectangle behind it. */
  .edibles-item-image image-slot::part(frame) {
    background: transparent;
  }
  .edibles-item-image image-slot::part(img) {
    filter: drop-shadow(0 2px 6px color-mix(in srgb, var(--ink) 18%, transparent));
  }
  /* Hard-coded multiply-blend escape hatch — see EDIBLES_MULTIPLY_IDS
     in JS. For items whose PNG has a baked-in soft shadow or a light
     body that fights the drop-shadow filter (bananas), we drop the
     drop-shadow and let mix-blend-mode: multiply melt the halo into
     the paper. Same rule applies to the detail hero and similar cards. */
  .edibles-item-image[data-blend="multiply"] image-slot::part(img),
  .edibles-detail-hero[data-blend="multiply"] image-slot::part(img),
  .edibles-similar-image[data-blend="multiply"] image-slot::part(img) {
    mix-blend-mode: multiply;
    filter: none;
  }
  .edibles-item-name {
    display: flex; flex-direction: column; gap: 2px;
  }
  .edibles-item-kana {
    font-family: var(--font-title);
    font-size: 22px;
    color: var(--ink);
    line-height: 1.2;
  }
  .edibles-item-chips {
    display: flex; flex-wrap: wrap; gap: 4px;
    margin-top: 2px;
  }
  /* Small chip pip — flavorball image + kana label. The flavorball
     PNG (transparent, circular, color built into the asset) sits as
     a tiny visual identifier of which flavor this item carries.
     Browser scales the PNG down at render — Phase 3 polish can
     re-export the flavorballs at 32×32 to drop the per-page weight. */
  .edibles-item-pip {
    display: inline-flex; align-items: center; gap: 6px;
    font-family: var(--font-body); font-size: 12px;
    color: var(--ink-2);
    line-height: 1;
  }
  .edibles-item-pip-ball {
    width: 22px; height: 22px;
    display: inline-block;
    object-fit: contain;
    flex-shrink: 0;
  }
  @media (prefers-reduced-motion: reduce) {
    .edibles-item-card { transition: none; }
    .edibles-item-card:hover { transform: none; }
  }

  /* ── View 3: Item detail spread ──────────────────────────────
     Two-column on desktop: hero image left (large square),
     identity + badges + notes on the right. Stacked on mobile. */
  .edibles-detail {
    display: grid;
    grid-template-columns: 5fr 7fr;
    gap: 48px;
    align-items: start;
  }
  @media (max-width: 900px) {
    .edibles-detail { grid-template-columns: 1fr; gap: 28px; }
  }
  .edibles-detail-hero {
    width: 100%; max-width: 480px;
    aspect-ratio: 1 / 1;
    justify-self: end;
  }
  @media (max-width: 900px) {
    .edibles-detail-hero { justify-self: center; max-width: 360px; }
  }
  .edibles-detail-hero image-slot,
  .edibles-detail-hero image-slot::part(img),
  .edibles-detail-hero image-slot::part(frame) {
    width: 100%; height: 100%; display: block;
    object-fit: contain;
  }
  /* Override the image-slot's built-in rgba(0,0,0,.04) frame backdrop —
     the detail hero is a clean spread; the food silhouette floats on
     the canvas paper, not on a faint dark rectangle. */
  .edibles-detail-hero image-slot::part(frame) {
    background: transparent;
  }
  .edibles-detail-hero image-slot::part(img) {
    filter: drop-shadow(0 16px 28px color-mix(in srgb, var(--ink) 24%, transparent));
  }

  .edibles-detail-body {
    display: flex; flex-direction: column; gap: 20px;
    min-width: 0;
  }
  /* Identity stack: kanji huge, kana below, romaji small, en italic. */
  .edibles-detail-identity {
    display: flex; flex-direction: column; gap: 6px;
  }
  .edibles-detail-kanji {
    font-family: var(--font-title);
    font-size: clamp(3rem, 6vw, 5.5rem);
    line-height: 1;
    color: var(--ink); margin: 0;
    letter-spacing: -0.01em;
  }
  .edibles-detail-kanji ruby rt {
    font-family: var(--font-body);
    font-size: 0.25em;
    letter-spacing: 0.05em;
  }
  /* When there's no kanji, render the kana big instead (loanwords). */
  .edibles-detail-kanji.is-kana-only {
    font-size: clamp(2.5rem, 5vw, 4.2rem);
  }
  .edibles-detail-kana {
    font-family: var(--font-title);
    font-size: 22px;
    color: var(--ink-2);
    margin: 0;
  }
  .edibles-detail-romaji {
    font-family: var(--font-menu-en); font-style: italic;
    font-size: 14px; color: var(--ink-3);
    margin: 0;
    letter-spacing: 0.04em;
  }

  /* Badge rows — flavor (clickable, dot + kana) and texture (display
     only, kana on tinted paper). Season is a third row. The row
     labels are now Japanese kanji + furigana (味 / 食感 / 季節) so
     the card teaches the words the learner needs; a small `?` legend
     in the top-right corner of the badges box maps each kanji to its
     English counterpart for first-time visitors. */
  .edibles-detail-badges {
    position: relative;
    display: flex; flex-direction: column; gap: 14px;
    padding: 16px 18px;
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 10px;
  }
  .edibles-badge-row {
    display: flex; align-items: center; flex-wrap: wrap;
    gap: 8px;
  }
  .edibles-badge-label {
    font-family: var(--font-title);
    font-size: 15px;
    color: var(--ink);
    flex-shrink: 0;
    min-width: 64px;
    line-height: 1.15;
    /* Reset inherited English-label styling. */
    letter-spacing: 0;
    text-transform: none;
  }
  .edibles-badge-label rt {
    font-size: 0.42em;
    font-family: var(--font-body);
    color: var(--ink-3);
    letter-spacing: 0.02em;
  }

  /* Reusable hover-revealed reading. Add to any ruby that should
     surface its furigana at large size on hover/focus — paper-tinted
     chip floats above the kanji. The small rt above stays as the
     always-visible reading; this tooltip is the "make it bigger"
     for users who want to confirm the pronunciation without
     squinting. Used on the detail card's badge labels (味/食感/季節)
     and the 似ている 食材 header. Generic enough to apply anywhere
     a ruby element appears. */
  ruby.hover-furigana {
    position: relative;
    cursor: help;
  }
  ruby.hover-furigana::after {
    content: attr(data-fur);
    position: absolute;
    bottom: calc(100% + 6px);
    left: 50%;
    transform: translateX(-50%);
    font-family: var(--font-title);
    font-size: 18px;
    line-height: 1.2;
    color: var(--ink);
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    padding: 6px 12px;
    border-radius: 8px;
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
    transition: opacity .15s ease-out;
    z-index: 20;
    box-shadow: 0 6px 16px -6px color-mix(in srgb, var(--ink) 26%, transparent);
  }
  ruby.hover-furigana:hover::after,
  ruby.hover-furigana:focus-visible::after {
    opacity: 1;
  }
  @media (prefers-reduced-motion: reduce) {
    ruby.hover-furigana::after { transition: none; }
  }

  /* Sibling pattern for English glosses on non-canonical texture
     descriptors (ジューシー, やわらかい, etc. — items not in the
     textures book). Reads from data-tooltip instead of data-fur, uses
     italic Garamond English so the tooltip reads as a translation,
     not as a furigana reading. Same paper-tinted chip + same hover/
     focus reveal pattern as .hover-furigana, scoped to a different
     class so the two can coexist on a card without stylistic
     collision. */
  .has-en-tooltip {
    position: relative;
    cursor: help;
  }
  .has-en-tooltip::after {
    content: attr(data-tooltip);
    position: absolute;
    bottom: calc(100% + 6px);
    left: 50%;
    transform: translateX(-50%);
    font-family: var(--font-menu-en); font-style: italic;
    font-size: 13px;
    line-height: 1.35;
    color: var(--ink-2);
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    padding: 6px 12px;
    border-radius: 8px;
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
    transition: opacity .15s ease-out;
    z-index: 20;
    box-shadow: 0 6px 16px -6px color-mix(in srgb, var(--ink) 26%, transparent);
  }
  .has-en-tooltip:hover::after,
  .has-en-tooltip:focus-visible::after {
    opacity: 1;
  }
  @media (prefers-reduced-motion: reduce) {
    .has-en-tooltip::after { transition: none; }
  }

  /* Top-right "?" legend — a small icon button that on hover/focus
     reveals a tooltip mapping each kanji label to its English
     translation. Always reachable, never in the way. */
  .edibles-badge-legend {
    position: absolute;
    top: 8px; right: 8px;
    width: 22px; height: 22px;
    display: inline-flex; align-items: center; justify-content: center;
    appearance: none;
    background: transparent;
    border: 1px solid color-mix(in srgb, var(--ink) 16%, transparent);
    border-radius: 50%;
    padding: 0;
    cursor: help;
    color: var(--ink-3);
    font-family: var(--font-menu-en);
    font-size: 12px;
    line-height: 1;
    transition: background .15s, color .15s, border-color .15s;
  }
  .edibles-badge-legend:hover,
  .edibles-badge-legend:focus-visible {
    background: var(--paper);
    color: var(--ink);
    border-color: var(--ink-3);
    outline: none;
  }
  .edibles-badge-legend-icon {
    display: block;
    pointer-events: none;
  }
  .edibles-badge-legend-tooltip {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    display: none;
    flex-direction: column;
    gap: 4px;
    min-width: 160px;
    padding: 10px 12px;
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    border-radius: 8px;
    box-shadow: 0 8px 24px -8px color-mix(in srgb, var(--ink) 28%, transparent);
    text-align: left;
    cursor: default;
    z-index: 5;
  }
  .edibles-badge-legend:hover .edibles-badge-legend-tooltip,
  .edibles-badge-legend:focus-visible .edibles-badge-legend-tooltip,
  .edibles-badge-legend:focus-within .edibles-badge-legend-tooltip {
    display: flex;
  }
  .edibles-badge-legend-tooltip .legend-row {
    display: flex; align-items: baseline;
    gap: 10px;
    justify-content: space-between;
  }
  .edibles-badge-legend-tooltip .legend-ja {
    font-family: var(--font-title);
    font-size: 15px;
    color: var(--ink);
    line-height: 1.1;
  }
  .edibles-badge-legend-tooltip .legend-en {
    font-family: var(--font-menu-en); font-style: italic;
    font-size: 12px;
    color: var(--ink-3);
    letter-spacing: 0.04em;
  }
  /* Flavor badge: chip-color dot + kana label, clickable, jumps to
     the flavor immersion. The dot color comes from the flavor's own
     --chip token (looked up by id in the renderer). */
  .edibles-flavor-badge {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 6px 12px 6px 8px;
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    border-radius: 999px;
    font-family: var(--font-body);
    font-size: 14px;
    color: var(--ink);
    cursor: pointer;
    transition: background .15s, border-color .15s, transform .15s;
  }
  .edibles-flavor-badge:hover {
    background: color-mix(in oklch, var(--paper) 80%, var(--badge-chip, var(--gold-soft)) 20%);
    border-color: var(--badge-chip, var(--gold-soft));
    transform: translateY(-1px);
  }
  .edibles-flavor-badge:focus-visible {
    outline: 2px solid var(--badge-chip, var(--accent));
    outline-offset: 2px;
  }
  .edibles-flavor-badge-ball {
    width: 28px; height: 28px;
    display: inline-block;
    object-fit: contain;
    flex-shrink: 0;
    /* Slight negative margin lets the ball overhang the padding so the
       round shape reads at full size against the badge's pill outline. */
    margin: -4px 0;
  }
  /* Texture badge. Two variants:
     - Display-only span (defensive fallback for textures not yet in
       the textures book — e.g. やわらかい which doesn't have a
       texture page).
     - Clickable button (.is-link) — opens the textures immersion
       view for that texture. Now carries a motion-line brushstroke
       glyph in front of the kana plus the texture's own pattern
       wallpaper as the badge background, so the pill becomes a
       miniature of the textures-page spotlight identity.
     The pattern is mix-blended via the ::before pseudo-element so the
     tint-colored base shows through where the pattern is transparent
     (same approach as the spotlight card). Children sit above via
     z-index. */
  .edibles-texture-badge {
    padding: 6px 12px;
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    border-radius: 999px;
    font-family: var(--font-body);
    font-size: 14px;
    color: var(--ink);
  }
  /* Icon-bearing variant of the display-only pill — non-canonical
     textures with a registered icon get a small image in front of
     their kana, the same way the clickable canonical pills carry
     their brushstroke glyph. inline-flex aligns the image baseline
     with the text. Image is shipped pre-trimmed (sharp's trim() in
     scripts) so no surrounding transparent halo. */
  .edibles-texture-badge.has-extra-icon {
    display: inline-flex; align-items: center;
    gap: 6px;
    padding: 4px 12px 4px 6px;
  }
  .edibles-texture-badge .extra-texture-icon {
    width: 24px; height: 24px;
    object-fit: contain;
    flex-shrink: 0;
    display: block;
  }
  .edibles-texture-badge .extra-texture-kana {
    line-height: 1.2;
  }
  button.edibles-texture-badge.is-link {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    display: inline-flex; align-items: center;
    gap: 6px;
    padding: 4px 12px 4px 6px;
    background: color-mix(in srgb, var(--tint, var(--paper)) 70%, var(--paper) 30%);
    border-color: color-mix(in srgb, var(--ink) 14%, var(--paper-edge));
    cursor: pointer;
    transition: transform .12s ease-out, box-shadow .15s, border-color .15s;
    font: inherit;
  }
  button.edibles-texture-badge.is-link::before {
    content: '';
    position: absolute;
    inset: 0;
    background-image: var(--texture-pattern, none);
    background-repeat: repeat;
    background-size: 120px auto;
    mix-blend-mode: multiply;
    opacity: 0.7;
    pointer-events: none;
    z-index: 0;
  }
  .edibles-texture-badge-glyph {
    position: relative; z-index: 1;
    width: 28px; height: 18px;
    display: inline-flex; align-items: center; justify-content: center;
    flex-shrink: 0;
    color: var(--ink);
  }
  .edibles-texture-badge-glyph .texture-motion-line {
    width: 100%; height: 100%;
  }
  .edibles-texture-badge-kana {
    position: relative; z-index: 1;
    line-height: 1.2;
  }
  button.edibles-texture-badge.is-link:hover {
    border-color: color-mix(in srgb, var(--ink) 35%, var(--paper-edge));
    transform: translateY(-1px);
    box-shadow: 0 4px 12px -4px color-mix(in srgb, var(--ink) 25%, transparent);
  }
  button.edibles-texture-badge.is-link:focus-visible {
    outline: 2px solid var(--ink-2);
    outline-offset: 2px;
  }
  @media (prefers-reduced-motion: reduce) {
    button.edibles-texture-badge.is-link { transition: none; }
    button.edibles-texture-badge.is-link:hover { transform: none; }
  }
  @media (prefers-contrast: more) {
    button.edibles-texture-badge.is-link::before { display: none; }
  }
  /* Season chip: even quieter. */
  .edibles-season-chip {
    padding: 4px 10px;
    background: transparent;
    border: 1px dashed color-mix(in srgb, var(--ink-3) 50%, transparent);
    border-radius: 4px;
    font-family: var(--font-menu-en); font-style: italic;
    font-size: 12px;
    color: var(--ink-2);
  }
  @media (prefers-reduced-motion: reduce) {
    .edibles-flavor-badge { transition: none; }
    .edibles-flavor-badge:hover { transform: none; }
  }

  /* Notes block — long-form prose about the item. Editorial body
     copy: serif italic English, comfortable measure. */
  .edibles-detail-notes {
    font-family: var(--font-menu-en); font-style: italic;
    font-size: 15px; line-height: 1.6;
    color: var(--ink-2);
    max-width: 64ch;
    margin: 0;
  }

  /* ── Similar items reverse-browse row (Phase 4) ─────────────
     Items elsewhere in the database sharing a flavor or texture
     with the current item. Same paper-tile aesthetic as the items
     grid, but compact (110px wide cards) and horizontally scrolling
     so a wider connective set can sit in a single row. */
  .edibles-detail-similar {
    margin-top: 16px;
  }
  .edibles-similar-head {
    display: flex; align-items: baseline; gap: 12px;
    margin-bottom: 12px;
  }
  .edibles-similar-head .ja {
    font-family: var(--font-title);
    font-size: 18px;
    color: var(--ink);
  }
  .edibles-similar-head .en {
    font-family: var(--font-menu-en); font-style: italic;
    font-size: 12px;
    color: var(--ink-3);
    letter-spacing: 0.02em;
  }
  .edibles-similar-row {
    display: flex; gap: 12px;
    overflow-x: auto;
    padding-bottom: 8px;
    scrollbar-width: thin;
    scrollbar-color: var(--paper-edge) transparent;
  }
  .edibles-similar-card {
    flex: 0 0 120px;
    display: flex; flex-direction: column; gap: 8px;
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 8px;
    padding: 10px 10px 12px;
    cursor: pointer;
    transition: transform .15s, box-shadow .15s, background .15s;
    text-align: left;
    font: inherit; color: inherit;
  }
  .edibles-similar-card:hover {
    background: var(--paper);
    transform: translateY(-2px);
    box-shadow: 0 4px 14px -6px rgba(60, 40, 10, 0.20);
  }
  .edibles-similar-card:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
  }
  .edibles-similar-image {
    width: 100%; aspect-ratio: 1 / 1;
    overflow: hidden;
  }
  .edibles-similar-image image-slot,
  .edibles-similar-image image-slot::part(img),
  .edibles-similar-image image-slot::part(frame) {
    width: 100%; height: 100%; display: block;
    object-fit: contain;
  }
  .edibles-similar-image image-slot::part(frame) {
    background: transparent;
  }
  .edibles-similar-image image-slot::part(img) {
    filter: drop-shadow(0 2px 5px color-mix(in srgb, var(--ink) 18%, transparent));
  }
  .edibles-similar-name {
    display: flex; flex-direction: column; gap: 2px;
    min-width: 0;
  }
  .edibles-similar-name .ja {
    font-family: var(--font-title);
    font-size: 14px;
    color: var(--ink);
    line-height: 1.2;
    overflow-wrap: anywhere;
  }
  .edibles-similar-name .en {
    font-family: var(--font-menu-en); font-style: italic;
    font-size: 11px;
    color: var(--ink-3);
    overflow-wrap: anywhere;
  }
  @media (prefers-reduced-motion: reduce) {
    .edibles-similar-card { transition: none; }
    .edibles-similar-card:hover { transform: none; }
  }

  /* Empty-state for unpopulated categories (when the user clicks a
     coming-soon tile). */
  .edibles-empty-grid {
    padding: 48px 24px;
    text-align: center;
    font-family: var(--font-menu-en); font-style: italic;
    color: var(--ink-3);
    background: var(--paper-2);
    border: 1px dashed var(--paper-edge);
    border-radius: 10px;
  }
  /* ── End Edibles page ────────────────────────────────────────── */

  /* ── Food card dialog ────────────────────────────────────────────
     Backdrop dims the page, modal sits centered with the dish image,
     name (JP + reading + EN), and a 2-3 sentence cultural note. */
  .food-modal-backdrop {
    position: fixed; inset: 0;
    background: rgba(40,28,12,0.42);
    backdrop-filter: blur(2px);
    display: flex; align-items: center; justify-content: center;
    padding: 24px;
    z-index: 1000;
    animation: food-modal-fade .15s ease-out;
  }
  @keyframes food-modal-fade {
    from { opacity: 0; }
    to   { opacity: 1; }
  }
  .food-modal {
    position: relative;
    background: var(--paper-2);
    border: 1px solid var(--gold-soft);
    border-radius: 8px;
    box-shadow:
      0 0 0 6px var(--paper-2),
      0 0 0 7px var(--gold-soft),
      0 24px 56px rgba(60,40,10,0.32);
    max-width: 520px;
    width: 100%;
    padding: 28px 30px 26px;
    outline: none;
    animation: food-modal-pop .18s ease-out;
  }
  @keyframes food-modal-pop {
    from { opacity: 0; transform: translateY(6px) scale(0.98); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
  }
  .food-modal-close {
    position: absolute; top: 6px; right: 10px;
    background: transparent;
    border: none;
    font-family: var(--serif);
    font-size: 26px;
    line-height: 1;
    color: var(--ink-3);
    cursor: pointer;
    padding: 6px 10px;
    border-radius: 4px;
    transition: color .15s, background .15s;
  }
  .food-modal-close:hover {
    color: var(--ink);
    background: var(--gold-soft);
  }
  .food-modal-head {
    display: grid;
    grid-template-columns: 96px 1fr;
    gap: 18px;
    align-items: center;
    padding-bottom: 16px;
    border-bottom: 1px dashed rgba(141,102,48,0.28);
  }
  .food-modal-svg {
    width: 96px; height: 96px;
    background:
      repeating-linear-gradient(135deg,
        rgba(141,102,48,0.045) 0 6px,
        transparent 6px 12px),
      var(--paper);
    border: 1px solid var(--paper-edge);
    border-radius: 6px;
    display: flex; align-items: center; justify-content: center;
    padding: 8px;
  }
  .food-modal-svg img {
    width: 100%; height: 100%;
    object-fit: contain;
    display: block;
  }
  .food-modal-title {
    min-width: 0;
    display: flex; flex-direction: column;
    gap: 2px;
  }
  .food-modal-section {
    font-family: var(--serif); font-style: italic;
    font-size: 11px;
    letter-spacing: 0.08em;
    text-transform: lowercase;
    color: var(--ink-4);
    margin-bottom: 4px;
  }
  .food-modal-ja {
    font-family: var(--serif-jp); font-weight: 600;
    font-size: 24px;
    color: var(--ink);
    line-height: 1.15;
    letter-spacing: 0.02em;
  }
  .food-modal-kana {
    font-family: var(--serif-jp);
    font-size: 13px;
    color: var(--ink-3);
    font-style: italic;
  }
  .food-modal-en {
    font-family: var(--serif); font-style: italic;
    font-size: 14px;
    color: var(--ink-2);
    margin-top: 4px;
  }
  .food-modal-body {
    padding-top: 16px;
    display: flex; flex-direction: column;
    gap: 12px;
  }
  .food-modal-body p {
    margin: 0;
  }
  /* Japanese is the primary body text — full ink weight, Mincho. */
  .food-modal-ja-body {
    font-family: var(--serif-jp);
    font-size: 15px;
    line-height: 1.75;
    color: var(--ink);
    letter-spacing: 0.01em;
  }
  /* English is the quieter gloss below — smaller, lighter color,
     italic. Reads as a side-note instead of the main message. */
  .food-modal-en-body {
    font-family: var(--serif);
    font-style: italic;
    font-size: 12px;
    line-height: 1.55;
    color: var(--ink-4);
    padding-top: 4px;
    border-top: 1px dotted rgba(141,102,48,0.18);
  }
  @media (max-width: 560px) {
    .food-modal { padding: 22px 22px 20px; }
    .food-modal-head { grid-template-columns: 72px 1fr; gap: 14px; }
    .food-modal-svg { width: 72px; height: 72px; }
    .food-modal-ja { font-size: 20px; }
  }

  /* ─── two-column cheatsheet ──────────────────────────────────────────
     Image-left, items-right layout for the vocabulary cheatsheets so the
     learner can read the kanji while still seeing the illustration. Image
     column sticks to a fixed proportion; items column scrolls if it has to.
     On narrow viewports we collapse to single-column and the items move into
     a drawer (see .vocab-drawer below). */
  .book-frame.cheatsheet-2col {
    display: grid;
    /* Image column gets 3x the items column — items halved from the original
       50% so the illustration can breathe. The kana-nowrap rule below keeps
       the (hiragana) atomic when the narrower items column forces a wrap. */
    grid-template-columns: minmax(0, 3fr) minmax(0, 1fr);
    column-gap: 8px;
    row-gap: 24px;
    align-items: stretch;
    /* Tighter side padding than the default .book-frame so the image gets
       more room. Right padding pulled in further (12px) — the items
       scrollbar sits close to the card edge — while the left side keeps a
       comfortable 32px for the image to breathe. */
    padding-left: 32px;
    padding-right: 12px;
  }
  .book-frame.cheatsheet-2col .sheet-col-image {
    display: flex; flex-direction: column; gap: 16px;
    min-width: 0;
    min-height: 0;
  }
  .book-frame.cheatsheet-2col .sheet-col-image .book-title {
    margin: 0;
    flex-shrink: 0;
  }
  .book-frame.cheatsheet-2col .sheet-image {
    margin: 0;
    flex: 1;
    min-height: 0;
    overflow: hidden;
    /* When hotspots are present, the image becomes the positioning
       parent so absolute hotspots can layer on top with percent coords. */
    position: relative;
  }

  /* ─── interactive hotspots on cheatsheet images ───────────────────────
     The painted number circles in the artwork are static. We layer
     transparent button-targets on top of them at percent-coordinates
     from the data — hover lights up both the hotspot and the matching
     vocab row, and a tooltip with romaji + cultural note appears.
     Click falls through to the existing popover behavior (handled
     elsewhere). The hotspot is purely additive — invisible until you
     touch it. */
  .sheet-hotspots {
    position: absolute; inset: 0;
    pointer-events: none;
  }
  .sheet-hotspot {
    pointer-events: auto;
    position: absolute;
    width: 44px; height: 44px;
    margin: -22px 0 0 -22px;        /* center on the (x,y) anchor */
    background: transparent;
    border: none;
    padding: 0;
    cursor: pointer;
    border-radius: 50%;
    outline: none;
    transition: transform .15s;
  }
  .sheet-hotspot:hover { transform: scale(1.08); }
  /* The ring is the only thing visible at rest, and even then it sits at
     very low opacity so it doesn\'t fight the painting. On hover or sync,
     it brightens and pulses to draw the eye. */
  .sheet-hotspot-ring {
    position: absolute; inset: 0;
    border-radius: 50%;
    border: 2px solid var(--gold);
    opacity: 0;
    box-shadow: 0 0 0 0 rgba(180,138,74,0.35);
    transition: opacity .18s, box-shadow .18s;
  }
  .sheet-hotspot:hover .sheet-hotspot-ring,
  .sheet-hotspot.is-active .sheet-hotspot-ring {
    opacity: 0.95;
    animation: hotspot-pulse 1.6s ease-out infinite;
  }
  @keyframes hotspot-pulse {
    0%   { box-shadow: 0 0 0 0 rgba(180,138,74,0.45); }
    70%  { box-shadow: 0 0 0 14px rgba(180,138,74,0.00); }
    100% { box-shadow: 0 0 0 0 rgba(180,138,74,0.00); }
  }
  @media (prefers-reduced-motion: reduce) {
    .sheet-hotspot:hover .sheet-hotspot-ring,
    .sheet-hotspot.is-active .sheet-hotspot-ring { animation: none; }
  }
  /* Tooltip — a small paper card with rich context. Positioned below
     the hotspot by default; if it would clip the image\'s right edge,
     we flip it leftward via a modifier set by JS. Width is capped so
     long notes wrap; the kanji line stays on one row. */
  .sheet-hotspot-tip {
    position: absolute;
    left: 50%;
    top: calc(100% + 10px);
    transform: translateX(-50%) translateY(-4px);
    width: 240px;
    padding: 12px 14px;
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 8px;
    box-shadow: 0 12px 32px rgba(100,72,30,0.22), 0 0 0 1px var(--gold-soft);
    opacity: 0;
    pointer-events: none;
    transition: opacity .15s, transform .15s;
    z-index: 10;
    text-align: left;
    display: grid;
    grid-template-columns: auto 1fr;
    column-gap: 10px;
    row-gap: 4px;
  }
  .sheet-hotspot:hover .sheet-hotspot-tip,
  .sheet-hotspot.is-pinned .sheet-hotspot-tip {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
    pointer-events: none;
  }
  .sheet-hotspot.tip-up .sheet-hotspot-tip {
    top: auto; bottom: calc(100% + 10px);
  }
  .sheet-hotspot.tip-left .sheet-hotspot-tip {
    left: auto; right: 50%;
    transform: translateX(50%) translateY(0);
  }
  .ht-num {
    grid-row: 1 / span 4;
    align-self: start;
    font-family: var(--serif); font-style: italic;
    font-size: 22px;
    color: var(--gold-dark);
    line-height: 1;
    padding-top: 2px;
  }
  .ht-kanji {
    font-family: var(--serif-jp); font-weight: 700;
    font-size: 19px;
    color: var(--ink);
    line-height: 1.1;
  }
  .ht-reading {
    font-family: var(--serif-jp);
    font-size: 12px;
    color: var(--ink-3);
    line-height: 1.2;
  }
  .ht-romaji {
    font-family: var(--serif); font-style: italic;
    color: var(--ink-3);
    margin-left: 2px;
  }
  .ht-en {
    font-family: var(--serif); font-style: italic;
    font-size: 12px;
    color: var(--ink-2);
    margin-top: 2px;
  }
  .ht-note {
    font-family: var(--serif);
    font-size: 12px;
    color: var(--ink-2);
    line-height: 1.5;
    margin-top: 6px;
    padding-top: 6px;
    border-top: 1px dashed var(--paper-edge);
  }

  /* When a hotspot is hovered, the synced vocab row pops slightly. We
     use a strong gold-soft background so the eye finds it instantly,
     without obscuring the kanji. Mirror state for when row is hovered. */
  .vocab-row.is-synced {
    background: rgba(180,138,74,0.18);
    box-shadow: inset 3px 0 0 var(--gold);
  }
  /* Desktop: lock the whole vocab page to 100vh so nothing scrolls outside
     the items column / sidebars. Flexbox on main-inner lets the pager sit
     at the bottom naturally while the book-frame stretches to fill the
     remaining height — no magic offsets needed. The tight right edge lives
     on .book-frame.cheatsheet-2col now, so .main keeps the standard page
     padding on all four sides.
     Exception: the food vocabulary gallery is a long reference list
     (~35 cards across 7 sections), so it needs natural page scroll.
     The :has() selector lifts the 100vh clamp only when the gallery is
     showing — every other vocab page keeps the cheatsheet lock. */
  @media (min-width: 901px) {
    /* The 100vh "lock to viewport" layout applies ONLY to pages whose
       body is a `.book-frame.cheatsheet-2col` — those have an internal
       scrolling items column built in, so the surrounding chrome
       (sidebar, page strip, stepper) needs to lock to the viewport
       to keep that internal scroll meaningful.
       Every other vocab page type — explanation (intro-frame),
       menu-reference (menu-ref-frame), food gallery, usage,
       sentences, Fast Food hub bodies that don't render a cheatsheet
       — flows naturally with page-level scroll. */
    /* Cheatsheet layout — used to lock .main to 100vh and rely on
       internal column scrolling. That meant any items below the fold
       were hidden unless the user knew to scroll the inner panel. The
       lock is removed: now the WHOLE page scrolls (cheatsheet image +
       items + everything else), which is the standard browser scroll
       behavior the user expects. The cheatsheet keeps its 2-col layout
       but flows naturally with the page. Main-inner stays full-width
       (max-width: none) so the cheatsheet uses the available column,
       and the inner panels rely on natural sizing instead of flex:1
       height fills. */
    .app.show-vocab-sidebar:has(.book-frame.cheatsheet-2col) .main-inner {
      max-width: none;
      margin: 0;
    }
    /* (The stepper used to live inside .main-inner and needed
       flex-shrink:0 here. It's now in #main-stepper outside main-inner,
       so this selector no longer matches — kept the comment as a
       breadcrumb in case the cheatsheet flex math regresses.) */
    /* Fast Food hub — tighter top padding so the selector cards sit
       close to the page top instead of behind 56px of empty space. */
    .app.show-vocab-sidebar:has(.ff-hub) .main {
      padding-top: 12px;
    }
  }
  .book-frame.cheatsheet-2col .vocab-grid {
    margin: 0;
    /* Single column inside the narrow content panel — the items column is
       half the deck width, so a 2-up internal grid would be cramped.
       Internal scrolling was removed (page now scrolls naturally), so
       the column flows to its natural height instead of fighting for a
       fixed-viewport box. The image on the left scrolls with the page. */
    grid-template-columns: 1fr;
    gap: 2px 0;
    padding-right: 8px;
  }
  /* Subtle scrollbar — same paper-on-paper feel as the rest of the chrome. */
  .book-frame.cheatsheet-2col .vocab-grid::-webkit-scrollbar { width: 6px; }
  .book-frame.cheatsheet-2col .vocab-grid::-webkit-scrollbar-track { background: transparent; }
  .book-frame.cheatsheet-2col .vocab-grid::-webkit-scrollbar-thumb {
    background: var(--paper-edge);
    border-radius: 3px;
  }
  .book-frame.cheatsheet-2col .vocab-grid::-webkit-scrollbar-thumb:hover {
    background: var(--gold-soft);
  }
  /* Drawer items list — also single column, mobile-width. */
  .vocab-drawer-body .vocab-grid {
    grid-template-columns: 1fr;
    gap: 2px 0;
  }

  /* ─── mobile drawer + FAB ───────────────────────────────────────────
     On narrow screens the items list moves into a drawer that slides up
     from the bottom (closer to thumb reach than a side drawer). The FAB
     opens it. Image takes the full main column underneath. */
  .vocab-fab {
    display: none;
    position: fixed;
    right: 18px;
    bottom: 18px;
    z-index: 60;
    width: 56px; height: 56px;
    border-radius: 28px;
    background: var(--gold-dark);
    color: var(--paper);
    border: none;
    box-shadow: 0 8px 24px rgba(60,40,10,0.32);
    cursor: pointer;
    font-family: var(--serif-jp); font-weight: 700;
    font-size: 22px;
    line-height: 1;
    transition: transform .15s ease, background .15s;
  }
  .vocab-fab:hover { background: var(--ink); }
  .vocab-fab:active { transform: scale(0.96); }
  .vocab-fab .vocab-fab-count {
    position: absolute;
    top: -4px; right: -4px;
    background: var(--paper);
    color: var(--gold-dark);
    border: 1px solid var(--gold);
    border-radius: 10px;
    font-family: var(--serif); font-size: 11px;
    padding: 1px 6px;
    line-height: 1.2;
    min-width: 18px;
    text-align: center;
  }
  .vocab-drawer-backdrop {
    display: none;
    position: fixed; inset: 0;
    background: rgba(20,12,4,0.4);
    z-index: 70;
    opacity: 0;
    transition: opacity .2s ease;
  }
  .vocab-drawer-backdrop.is-open {
    display: block;
    opacity: 1;
  }
  .vocab-drawer {
    position: fixed;
    left: 0; right: 0; bottom: 0;
    z-index: 80;
    max-height: 78vh;
    background: var(--paper);
    border-top: 1px solid var(--paper-edge);
    border-radius: 14px 14px 0 0;
    box-shadow: 0 -12px 32px rgba(60,40,10,0.20);
    transform: translateY(100%);
    transition: transform .25s cubic-bezier(.4,.0,.2,1);
    display: flex; flex-direction: column;
  }
  .vocab-drawer.is-open { transform: translateY(0); }
  .vocab-drawer-handle {
    height: 28px;
    display: flex; align-items: center; justify-content: center;
    cursor: grab;
    flex-shrink: 0;
  }
  .vocab-drawer-handle::before {
    content: '';
    width: 36px; height: 4px;
    border-radius: 2px;
    background: var(--paper-edge);
  }
  .vocab-drawer-head {
    display: flex; align-items: baseline; justify-content: space-between;
    padding: 0 20px 12px;
    border-bottom: 1px solid var(--paper-edge);
    flex-shrink: 0;
  }
  .vocab-drawer-head .title {
    font-family: var(--serif-jp); font-weight: 600;
    font-size: 18px; color: var(--ink);
  }
  .vocab-drawer-close {
    background: none; border: none;
    font-family: var(--serif); font-size: 13px;
    color: var(--ink-3); cursor: pointer;
    padding: 4px 8px;
  }
  .vocab-drawer-close:hover { color: var(--gold-dark); }
  .vocab-drawer-body {
    overflow-y: auto;
    padding: 14px 20px 24px;
    -webkit-overflow-scrolling: touch;
  }
  /* FAB + drawer are mobile-only. On desktop the items live in column 2. */
  @media (max-width: 900px) {
    .book-frame.cheatsheet-2col { grid-template-columns: 1fr; }
    .book-frame.cheatsheet-2col .vocab-grid { display: none; }
    .vocab-fab { display: inline-flex; align-items: center; justify-content: center; }
  }

  /* ─── restaurant scene engine ────────────────────────────────────────
     Card-stack interactive ordering experience. Each scene is one step at
     a time inside .scene-frame — paper-on-paper, restrained chrome. The
     NPC has a glyph avatar (no character art needed). Japanese always
     primary; English fades in on hover/tap of any .jp-line. */
  .scene-frame {
    background: var(--paper-2);
    /* Halftone screentone — a faint dot pattern that whispers "manga page"
       under the content. Pure SVG data-URL keeps it crisp at any zoom. */
    background-image:
      radial-gradient(rgba(141,102,48,0.06) 0.7px, transparent 1px);
    background-size: 14px 14px;
    background-position: 0 0;
    border: 1px solid var(--paper-edge);
    box-shadow: 0 8px 24px rgba(100,72,30,0.10);
    border-radius: 8px;
    /* Roomier card — explicitly fills the available main column. Without
       width:100% the flex layout was sizing the frame to its widest child,
       not the column. */
    width: 100%;
    padding: 28px 36px 32px;
    /* Cap the entire card to viewport so the page itself never scrolls.
       Internal panels (menu list, bowl vocab, receipt) scroll where
       needed; everything else fits inside the cap. */
    max-height: calc(100vh - 24px);
    margin: 12px auto;
    display: flex; flex-direction: column;
    min-height: min(560px, calc(100vh - 24px));
    position: relative;
    /* Bump the inherited text size for everything inside the frame. */
    font-size: 16px;
  }

  /* Ruby (furigana) rendering — small gray reading guide above kanji.
     We let the browser's native ruby do the work; just tune sizing and
     color so it doesn't shout. */
  .scene-frame ruby { ruby-position: over; }
  .scene-frame ruby rt {
    font-size: 0.55em;
    color: var(--ink-4);
    font-weight: 400;
    letter-spacing: 0.02em;
    /* The Mincho/Shippori fonts handle ruby better than the serif default */
    font-family: var(--serif-jp);
  }
  /* When furigana is showing, give the line a touch more line-height so
     the rt doesn't crowd the kanji above. */
  .scene-frame .jp-line.has-furi {
    line-height: 1.85;
  }
  .scene-frame-stub { min-height: 260px; align-items: center; justify-content: center; text-align: center; }
  .scene-stub-eyebrow {
    font-family: var(--serif); font-style: italic; font-size: 11px;
    letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-4);
  }
  .scene-stub-title { font-family: var(--serif-jp); font-weight: 600; font-size: 26px; margin: 8px 0 12px; }
  .scene-stub-text { font-family: var(--serif); font-size: 14px; color: var(--ink-3); max-width: 420px; }

  .scene-head {
    display: grid; grid-template-columns: 1fr auto 1fr;
    align-items: center; gap: 12px;
    padding-bottom: 14px;
    border-bottom: 1px dashed rgba(141,102,48,0.18);
    margin-bottom: 18px;
  }
  .scene-head-place {
    font-family: var(--serif-jp); font-size: 13px; color: var(--ink-3);
  }
  .scene-head-progress {
    font-family: var(--serif); font-size: 13px; color: var(--ink-3);
    letter-spacing: 0.02em;
    text-align: center;
  }
  .scene-step-num { font-weight: 600; color: var(--ink); }
  .scene-step-sep { color: var(--ink-4); margin: 0 4px; }
  .scene-head-right {
    display: flex; gap: 6px; justify-content: flex-end;
  }
  .scene-control {
    width: 28px; height: 28px; border-radius: 14px;
    border: 1px solid var(--paper-edge);
    background: var(--paper);
    color: var(--ink-3); cursor: pointer;
    font-family: var(--serif); font-size: 14px; line-height: 1;
    transition: background .12s, color .12s, border-color .12s;
  }
  .scene-control:hover:not(:disabled) {
    background: var(--gold-soft); color: var(--gold-dark); border-color: var(--gold);
  }
  .scene-control:disabled { opacity: 0.35; cursor: not-allowed; }

  .scene-body {
    flex: 1; display: flex; flex-direction: column; gap: 16px;
    /* Safety net — if a step has unusually long content (long NPC speech,
       many choices, etc.) and per-panel scrolls don't cover it, body
     scrolls. The .scene-menu-foot is position:sticky below, so the
       continue button stays reachable. */
    overflow-y: auto;
    overflow-x: hidden;
    min-width: 0;
    min-height: 0;
    scrollbar-width: thin;
    scrollbar-color: var(--paper-edge) transparent;
  }
  /* The receive step is laid out to fit exactly in the card. Suppress
     the safety-net body scroll so sub-pixel rounding doesn't trigger
     a phantom scrollbar — internal panels (vocab list) still scroll. */
  .scene-body:has(.scene-receive) { overflow-y: hidden; }
  .scene-body::-webkit-scrollbar { width: 6px; }
  .scene-body::-webkit-scrollbar-track { background: transparent; }
  .scene-body::-webkit-scrollbar-thumb {
    background: var(--paper-edge);
    border-radius: 3px;
  }
  .scene-body::-webkit-scrollbar-thumb:hover { background: var(--gold-soft); }

  /* Bilingual line — JA visible, EN revealed on hover/tap. Tooltip uses
     the data-en attribute so we don't have to duplicate strings in DOM. */
  .jp-line {
    position: relative;
    cursor: help;
    border-bottom: 1px dotted transparent;
    transition: border-color .15s;
  }
  .jp-line[data-en]:not([data-en=""]):hover,
  .jp-line[data-en]:not([data-en=""]):focus,
  .jp-line[data-en]:not([data-en=""]):active {
    border-bottom-color: rgba(141,102,48,0.40);
  }
  .jp-line[data-en]:not([data-en=""])::after {
    content: attr(data-en);
    position: absolute;
    left: 0; top: calc(100% + 4px);
    font-family: var(--serif); font-style: italic;
    font-size: 12px; color: var(--ink-4);
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    padding: 5px 9px; border-radius: 4px;
    /* Wrap long English translations across multiple lines instead of
       clipping with an ellipsis — the whole sentence has to be readable
       for the tooltip to do its job. Width caps at 480px / 40vw so it
       doesn't run off narrow viewports, and `width: max-content` lets
       short tooltips stay compact instead of always rendering 480 wide. */
    white-space: normal;
    width: max-content;
    max-width: min(480px, 40vw);
    line-height: 1.45;
    box-shadow: 0 4px 12px rgba(60,40,10,0.10);
    opacity: 0; pointer-events: none;
    transform: translateY(-2px);
    transition: opacity .15s, transform .15s;
    z-index: 5;
  }
  .jp-line[data-en]:not([data-en=""]):hover::after,
  .jp-line[data-en]:not([data-en=""]):focus::after,
  .jp-line[data-en]:not([data-en=""]):active::after {
    opacity: 1; transform: translateY(0);
  }
  /* Flip tooltip upward for jp-lines inside the sticky foot — otherwise
     the tooltip drops below the card edge and gets clipped. Covers the
     menu/dialogue/receive foots that share the sticky-bottom pattern. */
  .scene-menu-foot .jp-line[data-en]:not([data-en=""])::after,
  .scene-receive .receive-foot .jp-line[data-en]:not([data-en=""])::after,
  .scene-next .jp-line[data-en]:not([data-en=""])::after {
    top: auto;
    bottom: calc(100% + 6px);
    transform: translateY(2px);
  }
  .scene-menu-foot .jp-line[data-en]:not([data-en=""]):hover::after,
  .scene-menu-foot .jp-line[data-en]:not([data-en=""]):focus::after,
  .scene-menu-foot .jp-line[data-en]:not([data-en=""]):active::after,
  .scene-receive .receive-foot .jp-line[data-en]:not([data-en=""]):hover::after,
  .scene-receive .receive-foot .jp-line[data-en]:not([data-en=""]):focus::after,
  .scene-receive .receive-foot .jp-line[data-en]:not([data-en=""]):active::after,
  .scene-next .jp-line[data-en]:not([data-en=""]):hover::after,
  .scene-next .jp-line[data-en]:not([data-en=""]):focus::after,
  .scene-next .jp-line[data-en]:not([data-en=""]):active::after {
    transform: translateY(0);
  }

  /* NPC speech — avatar circle on the left, paper bubble on the right. */
  .scene-npc {
    display: flex; gap: 14px; align-items: flex-start;
  }
  .scene-npc-avatar {
    flex-shrink: 0;
    width: 56px; height: 56px;
    border-radius: 28px;
    background: var(--gold-soft);
    color: var(--gold-dark);
    font-family: var(--serif-jp); font-weight: 700;
    font-size: 28px;
    display: flex; align-items: center; justify-content: center;
    line-height: 1;
    box-shadow: inset 0 0 0 1px var(--gold);
  }
  .scene-npc-bubble {
    flex: 1;
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    border-radius: 4px 14px 14px 14px;
    padding: 18px 22px;
    position: relative;
  }
  /* Long NPC speech flows into two columns instead of growing the bubble
     tall enough to push the rest of the body. The column break keeps the
     overall card vertical-budget compact while preserving readability. */
  .scene-npc-bubble.is-long {
    column-count: 2;
    column-gap: 36px;
    column-fill: balance;
  }
  .scene-npc-line {
    font-family: var(--serif-jp); font-size: 22px; color: var(--ink);
    line-height: 1.55;
  }

  /* Typewriter reveal — character-by-character fade-in for dialogue and
     narrative lines. Each "atom" is either one character or a full ruby
     block (so readings reveal as a unit, not mid-word). */
  .typewriter-atom {
    opacity: 0;
    transition: opacity .16s ease-out;
  }
  .typewriter-atom.is-shown { opacity: 1; }
  @media (prefers-reduced-motion: reduce) {
    .typewriter-atom { opacity: 1; transition: none; }
  }

  .scene-narrative {
    font-family: var(--serif); font-style: italic;
    font-size: 16px; color: var(--ink-3);
    line-height: 1.65;
    padding: 4px 6px;
  }
  .scene-player-line {
    align-self: flex-end;
    max-width: 80%;
    background: color-mix(in srgb, var(--paper-2) 60%, var(--paper) 40%);
    border: 1px solid var(--paper-edge);
    border-radius: 14px 4px 14px 14px;
    padding: 16px 20px;
  }
  .scene-player-line .scene-player {
    font-family: var(--serif-jp); font-size: 21px; color: var(--ink-2);
  }

  /* Choices — "what you might say." These are the player's lines, so
     they hug the right edge of the card (mirroring a chat app: your
     side on the right, their side on the left). Capped at 40% of the
     card width on desktop; wider on mobile so the buttons stay tappable. */
  .scene-choices {
    display: flex; flex-direction: column; gap: 10px; margin-top: 8px;
    max-width: 40%;
    margin-left: auto;
    align-self: flex-end;
    min-width: min(280px, 100%);
  }
  @media (max-width: 760px) {
    .scene-choices { max-width: 100%; min-width: 0; align-self: stretch; }
  }
  .scene-choice {
    text-align: left;
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    border-radius: 5px;
    padding: 14px 18px;
    cursor: pointer;
    display: grid; grid-template-columns: 1fr auto; column-gap: 16px; row-gap: 3px;
    transition: background .12s, border-color .12s, transform .1s;
  }
  .scene-choice:hover {
    background: color-mix(in srgb, var(--gold-soft) 30%, var(--paper) 70%);
    border-color: var(--gold);
    transform: translateY(-1px);
  }
  .scene-choice .ja {
    grid-column: 1; font-family: var(--serif-jp); font-size: 20px; color: var(--ink);
    line-height: 1.45;
  }
  .scene-choice .kana {
    grid-column: 2; grid-row: 1;
    font-family: var(--serif); font-style: italic; font-size: 13px;
    color: var(--ink-4); letter-spacing: 0.03em;
    align-self: end;
  }
  .scene-choice .en {
    grid-column: 1 / -1;
    font-family: var(--serif); font-style: italic; font-size: 14px;
    color: var(--ink-3);
    line-height: 1.4;
  }

  /* Menu — paper-typeset list with dot-leader connecting kanji to price.
     Single scroll: the body owns it. The menu itself just flows; long
     menus push the body scroll, not an inner one. Width caps at 60% of
     the card on wide screens — keeps the menu page-like and centered —
     and expands toward 100% as the window narrows. */
  .scene-menu {
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    border-radius: 4px;
    padding: 18px 22px;
    max-width: 60%;
    margin: 0 auto;
    width: 100%;
  }
  @media (max-width: 1100px) { .scene-menu { max-width: 80%; } }
  @media (max-width: 760px)  { .scene-menu { max-width: 100%; } }
  .scene-menu-list { /* flows naturally; body handles overflow */ }
  .scene-menu-head {
    font-family: var(--serif-jp); font-size: 16px; color: var(--ink-3);
    text-align: center;
    padding-bottom: 12px;
    border-bottom: 1px solid var(--paper-edge);
    margin-bottom: 14px;
    letter-spacing: 0.08em;
  }
  .scene-menu-list { list-style: none; padding: 0; margin: 0; }
  .scene-menu-row {
    display: grid;
    grid-template-columns: minmax(0, auto) minmax(0, auto) minmax(0, 1fr) minmax(0, auto) minmax(0, auto);
    align-items: baseline;
    gap: 12px;
    padding: 11px 8px;
    border-bottom: 1px dotted rgba(141,102,48,0.18);
    cursor: pointer;
    border-radius: 3px;
    transition: background .12s;
    min-width: 0;
  }
  .scene-menu-row > * { min-width: 0; }
  .m-kanji, .m-kana { overflow-wrap: anywhere; word-break: break-word; }
  .scene-menu-row:last-child { border-bottom: none; }
  .scene-menu-row:hover { background: color-mix(in srgb, var(--gold-soft) 18%, transparent); }
  /* Rows are keyboard-operable (role="button" + tabindex) — same focus ring
     convention as the other interactive tiles. Invisible to mouse users. */
  .scene-menu-row:focus-visible {
    outline: 2px solid var(--ink);
    outline-offset: 2px;
  }
  .scene-menu-row.is-picked { background: color-mix(in srgb, var(--gold-soft) 28%, transparent); }
  .m-kanji { font-family: var(--serif-jp); font-size: 20px; color: var(--ink); font-weight: 500; }
  .m-kana  { font-family: var(--serif-jp); font-size: 14px; color: var(--ink-4); }
  .m-dots  { border-bottom: 1px dotted var(--paper-edge); height: 1px; margin-bottom: 5px; }
  .m-price { font-family: var(--serif); font-size: 16px; color: var(--ink-2); font-variant-numeric: tabular-nums; }
  .m-mark  { width: 18px; text-align: center; color: var(--gold-dark); font-weight: 700; font-size: 16px; }

  /* ── Brand menu board (fast food) ────────────────────────────────
     KFC, McDonald's, and friends. Per-chain colors are injected via
     inline --brand-* custom properties on the .scene-menu-brand
     wrapper (see menuStepHTML). The board itself is sans-serif and
     loud — sits inside the same paper-toned card the rest of the
     scene uses, so the contrast between counter-service and sit-down
     reads at a glance. */
  .scene-menu-brand {
    background: var(--brand-primary, #c00);
    color: var(--brand-accent, #fff);
    border: 1px solid color-mix(in srgb, var(--brand-primary, #c00) 80%, black 20%);
    border-radius: 10px;
    padding: 18px 18px 14px;
    box-shadow: 0 6px 18px rgba(60,40,10,0.18), inset 0 1px 0 rgba(255,255,255,0.08);
    max-width: 100%;
    margin: 0;
    /* The base .scene-menu has a max-width: 60% on it; reset that here
       since the brand board is meant to fill the column. */
    width: 100%;
  }
  .scene-menu-brand-head {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: 12px;
    padding-bottom: 10px;
    margin-bottom: 12px;
    border-bottom: 1px solid color-mix(in srgb, var(--brand-accent, #fff) 30%, transparent);
  }
  .scene-menu-brand-head .brand-eyebrow {
    font-family: var(--mono, 'JetBrains Mono', monospace);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.16em;
    color: color-mix(in srgb, var(--brand-accent, #fff) 70%, transparent);
  }
  .scene-menu-brand-head .brand-label {
    font-family: var(--serif-jp);
    font-weight: 700;
    font-size: 18px;
    color: var(--brand-accent, #fff);
    letter-spacing: 0.04em;
  }
  /* 5-column grid by default so product photos read at a glance — same
     density the real KFC and McDonald's menu boards use. Steps down to
     3 on medium screens and 2 on narrow viewports. */
  .scene-menu-brand-grid {
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    gap: 6px;
  }
  @media (max-width: 1100px) {
    .scene-menu-brand-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  }
  @media (max-width: 640px) {
    .scene-menu-brand-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 5px; }
  }
  .brand-item {
    position: relative;
    display: grid;
    grid-template-columns: 1fr auto;
    grid-template-rows: auto auto;
    column-gap: 10px;
    align-items: baseline;
    padding: 10px 14px 12px;
    background: rgba(255,255,255,0.07);
    border: 1px solid rgba(255,255,255,0.10);
    border-radius: 6px;
    cursor: pointer;
    font: inherit; color: inherit;
    text-align: left;
    transition: background .12s, border-color .12s, transform .08s;
  }
  .brand-item:hover {
    background: rgba(255,255,255,0.14);
    border-color: var(--brand-badge, #fff);
  }
  .brand-item:active { transform: translateY(1px); }
  .brand-item.is-picked {
    background: var(--brand-badge, #fff);
    color: color-mix(in srgb, var(--brand-primary, #000) 85%, black 15%);
    border-color: var(--brand-badge, #fff);
  }
  .brand-item.is-picked .brand-item-kana {
    color: color-mix(in srgb, var(--brand-primary, #000) 60%, black 40%);
  }
  .brand-item-name {
    grid-column: 1; grid-row: 1;
    font-family: var(--serif-jp); font-weight: 700;
    font-size: 16px;
    line-height: 1.2;
    color: inherit;
    overflow-wrap: anywhere;
  }
  .brand-item-kana {
    grid-column: 1; grid-row: 2;
    font-family: var(--serif-jp);
    font-size: 11px;
    color: color-mix(in srgb, currentColor 65%, transparent);
    margin-top: 2px;
    overflow-wrap: anywhere;
  }
  .brand-item-price {
    grid-column: 2; grid-row: 1 / span 2;
    align-self: center;
    font-family: var(--serif);
    font-weight: 700;
    font-size: 18px;
    font-variant-numeric: tabular-nums;
    background: var(--brand-badge, #fff);
    color: color-mix(in srgb, var(--brand-primary, #000) 85%, black 15%);
    padding: 4px 10px;
    border-radius: 999px;
    line-height: 1;
    white-space: nowrap;
  }
  .brand-item.is-picked .brand-item-price {
    background: color-mix(in srgb, var(--brand-primary, #000) 88%, black 12%);
    color: var(--brand-accent, #fff);
  }
  .brand-item.is-drink {
    /* Drinks get a subtle visual distinction — a smaller, more chip-y
       look so the meal items lead the eye first. */
    background: rgba(255,255,255,0.04);
  }
  .brand-item-mark {
    position: absolute;
    top: 4px; right: 8px;
    font-size: 14px;
    font-weight: 700;
    color: color-mix(in srgb, var(--brand-primary, #000) 88%, black 12%);
  }

  /* ── Brand item with 16:9 product image (KFC items, etc.) ────────
     Layout shifts to: image on top, name/kana on the next row, price
     as a chip floating in the bottom-right corner. The image fills
     the full card width and uses object-fit: cover so real product
     shots crop into a clean 16:9 banner. */
  .brand-item.has-image {
    display: flex;
    flex-direction: column;
    padding: 0;
    overflow: hidden;
  }
  .brand-item-image {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 9;
    background: color-mix(in srgb, var(--brand-primary, #000) 80%, black 20%);
    overflow: hidden;
  }
  .brand-item-image img {
    position: absolute; inset: 0;
    width: 100%; height: 100%;
    object-fit: cover;
    display: block;
  }
  .brand-item-image img[data-placeholder] {
    opacity: 0.7;
    object-fit: contain;
  }
  /* Subtle gradient at the bottom of the image so the price chip stays
     readable when it sits over a busy product shot. */
  .brand-item.has-image .brand-item-image::after {
    content: "";
    position: absolute;
    left: 0; right: 0; bottom: 0;
    height: 50%;
    pointer-events: none;
    background: linear-gradient(to top,
      color-mix(in srgb, var(--brand-primary, #000) 65%, transparent) 0%,
      transparent 100%);
  }
  /* Re-layout the text region for image cards: name + kana stack
     below the image. Cards now sit 5-up so the typography is
     compact — line-height tight, padding minimal, long names allowed
     to wrap to two lines (sandwich, twister, etc.) instead of being
     clipped. */
  .brand-item.has-image .brand-item-name {
    grid-column: unset; grid-row: unset;
    padding: 6px 8px 0;
    font-size: 12px;
    line-height: 1.2;
  }
  .brand-item.has-image .brand-item-kana {
    grid-column: unset; grid-row: unset;
    padding: 1px 8px 8px;
    margin-top: 0;
    font-size: 10px;
    line-height: 1.15;
  }
  /* Price floats over the image in the corner — like the price tags
     on actual KFC menu boards. Smaller chip to suit narrower cards. */
  .brand-item.has-image .brand-item-price {
    position: absolute;
    bottom: 6px; right: 6px;
    grid-column: unset; grid-row: unset;
    z-index: 2;
    font-size: 12px;
    padding: 3px 8px;
    box-shadow: 0 2px 6px rgba(0,0,0,0.25);
  }
  /* Drinks with images render a touch smaller — same aspect ratio but
     the badge dims so the meal items lead the eye. */
  .brand-item.has-image.is-drink .brand-item-image {
    aspect-ratio: 16 / 9;
  }
  .brand-item.has-image.is-picked .brand-item-image::after {
    background: linear-gradient(to top,
      color-mix(in srgb, var(--brand-badge, #fff) 50%, transparent) 0%,
      transparent 100%);
  }
  .brand-item.has-image .brand-item-mark {
    top: 8px; left: 8px; right: auto;
    background: var(--brand-badge, #fff);
    color: color-mix(in srgb, var(--brand-primary, #000) 88%, black 12%);
    border-radius: 50%;
    width: 24px; height: 24px;
    display: flex; align-items: center; justify-content: center;
    box-shadow: 0 2px 6px rgba(0,0,0,0.25);
  }

  /* ── Sizes step (KFC chicken buckets, fries S/M/L) ───────────────
     One section per sized item. Each section has a header with the
     dish name, then a grid of size cards. Card = bucket SVG (or a
     big letter for fries S/M/L) + JP label + kana + EN + price chip.
     The whole step lives inside the same paper-toned card body
     everything else uses; nothing brand-colored here so attention
     lands squarely on the size vocabulary. */
  .sizes-step {
    display: flex; flex-direction: column;
    gap: 22px;
    margin: 14px 0 0;
  }
  .sizes-block {
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    border-radius: 6px;
    padding: 14px 16px 16px;
  }
  .sizes-block-head {
    display: flex; align-items: baseline; gap: 12px;
    padding-bottom: 10px;
    margin-bottom: 12px;
    border-bottom: 1px dashed rgba(141,102,48,0.22);
  }
  .sizes-block-head .sizes-block-ja {
    font-family: var(--serif-jp); font-weight: 600;
    font-size: 17px;
    color: var(--ink);
  }
  .sizes-block-head .sizes-block-en {
    font-family: var(--serif); font-style: italic;
    font-size: 13px;
    color: var(--ink-3);
  }
  .sizes-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    gap: 10px;
  }
  .size-card {
    position: relative;
    display: flex; flex-direction: column;
    align-items: center;
    gap: 4px;
    padding: 12px 10px 14px;
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 6px;
    cursor: pointer;
    font: inherit; color: inherit; text-align: center;
    transition: background .12s, border-color .12s, transform .08s, box-shadow .12s;
  }
  .size-card:hover {
    background: color-mix(in srgb, var(--gold-soft) 22%, var(--paper-2));
    border-color: var(--gold-soft);
    transform: translateY(-1px);
    box-shadow: 0 4px 14px rgba(100,72,30,0.10);
  }
  .size-card.is-picked {
    background: var(--gold-soft);
    border-color: var(--gold);
    box-shadow: inset 0 0 0 1px var(--gold);
  }
  .size-card-svg {
    display: block;
    width: 72px; height: 72px;
    color: var(--ink-2);
    margin-bottom: 4px;
  }
  .size-card-svg img {
    width: 100%; height: 100%;
    object-fit: contain;
    display: block;
  }
  /* For sizes without an SVG (fries S/M/L) — render a big letter mark
     instead. Keeps the cards visually balanced with the bucket cards. */
  .size-card-letter {
    display: flex; align-items: center; justify-content: center;
    width: 72px; height: 72px;
    margin-bottom: 4px;
    font-family: var(--serif); font-weight: 700;
    font-size: 38px;
    color: var(--gold-dark);
    background: var(--paper);
    border: 2px solid var(--paper-edge);
    border-radius: 50%;
  }
  .size-card.is-picked .size-card-letter {
    background: var(--paper-2);
    border-color: var(--gold);
    color: var(--ink);
  }
  .size-card-name {
    font-family: var(--serif-jp); font-weight: 600;
    font-size: 14px;
    color: var(--ink);
    line-height: 1.25;
  }
  .size-card-kana {
    font-family: var(--serif-jp);
    font-size: 11px;
    color: var(--ink-4);
    line-height: 1.2;
  }
  .size-card-en {
    font-family: var(--serif); font-style: italic;
    font-size: 11px;
    color: var(--ink-3);
    margin-top: 2px;
    line-height: 1.2;
  }
  .size-card-price {
    margin-top: 6px;
    font-family: var(--serif); font-weight: 700;
    font-size: 14px;
    font-variant-numeric: tabular-nums;
    color: var(--gold-dark);
  }
  .size-card.is-picked .size-card-price {
    color: var(--ink);
  }
  .size-card-mark {
    position: absolute;
    top: 6px; right: 8px;
    color: var(--gold-dark);
    font-weight: 700;
    font-size: 14px;
  }
  /* When inside a brand-themed scene (KFC, McDonald's), the size step
     keeps the paper-tone look but tints the picked state subtly toward
     the brand color so the connection back to the menu reads. */
  .sizes-step-brand .size-card.is-picked {
    background: color-mix(in srgb, var(--gold-soft) 70%, var(--paper-2) 30%);
  }

  /* ── Shelf step (konbini) ─────────────────────────────────────────
     Top: 5 tabs (sections). Body: 2 columns — left is a 3:4 section
     hero image (paper-toned konbini-shelf illustration), right is a
     vertical list of items in the active section. Each list row has
     a small thumbnail + kanji + kana + price. Click toggles into the
     basket. The card itself stretches to viewport height (see the
     .scene-frame:has(.scene-shelf) override below) so the full list
     is visible without scroll on normal-tall screens. */
  .scene-frame:has(.scene-shelf) {
    height: calc(100vh - 24px);
    min-height: 0;
  }
  .scene-shelf {
    display: flex; flex-direction: column;
    gap: 12px;
    flex: 1;
    min-height: 0;
  }
  .shelf-tabs {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    gap: 4px;
    padding: 4px;
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    border-radius: 6px;
    flex-shrink: 0;
  }
  .shelf-tab {
    display: flex; flex-direction: column; align-items: center; gap: 2px;
    padding: 8px 6px;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 4px;
    cursor: pointer;
    font-family: var(--serif-jp);
    color: var(--ink-3);
    transition: background .12s, color .12s, border-color .12s;
    min-width: 0;
  }
  .shelf-tab:hover {
    background: color-mix(in srgb, var(--gold-soft) 14%, transparent);
    color: var(--ink-2);
  }
  .shelf-tab.active {
    background: var(--paper-2);
    color: var(--ink);
    border-color: var(--gold-soft);
    box-shadow: inset 0 -2px 0 var(--gold);
  }
  .shelf-tab-glyph {
    font-size: 22px; font-weight: 600;
    color: var(--gold-dark);
    line-height: 1;
  }
  .shelf-tab.active .shelf-tab-glyph { color: var(--ink); }
  .shelf-tab-label {
    display: flex; flex-direction: column; align-items: center;
    line-height: 1.2;
  }
  .shelf-tab-label .ja {
    font-size: 13px; color: inherit;
  }
  .shelf-tab-label .en {
    font-family: var(--serif); font-style: italic;
    font-size: 10px; color: var(--ink-4);
    letter-spacing: 0.02em;
  }
  /* 2-column body — hero left, vertical item list right.
     auto / 1fr keeps the hero at its 3:4 natural size and lets the
     list take the remaining width. min-height: 0 on both grid items
     so the list can scroll if it overflows (rare given the new full-
     viewport card height, but a safety net for short screens). */
  .shelf-body {
    display: grid;
    grid-template-columns: minmax(220px, 38%) 1fr;
    gap: 18px;
    flex: 1;
    min-height: 0;
  }
  .shelf-hero {
    position: relative;
    aspect-ratio: 3 / 4;
    /* Cap the hero so a very tall card doesn't make it dominate; let it
       shrink with the column. The 3:4 ratio is preserved regardless. */
    max-height: 100%;
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 8px;
    overflow: hidden;
    box-shadow: 0 4px 16px rgba(60,40,10,0.08);
    align-self: start;
  }
  .shelf-hero-img {
    position: absolute; inset: 0;
    width: 100%; height: 100%;
    object-fit: cover;
    display: block;
  }
  .shelf-hero-img[data-placeholder] {
    opacity: 0.85;
    object-fit: contain;
  }
  /* Right column — vertical list of items. Stays scrollable as a
     safety net for very short viewports / very long sections. */
  .shelf-list {
    display: flex; flex-direction: column;
    gap: 6px;
    padding: 2px 4px 2px 0;
    min-height: 0;
    overflow-y: auto;
    scrollbar-width: thin;
    scrollbar-color: var(--paper-edge) transparent;
  }
  .shelf-row {
    position: relative;
    display: grid;
    grid-template-columns: 48px 1fr auto;
    align-items: center;
    gap: 12px;
    padding: 8px 14px 8px 8px;
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    border-radius: 5px;
    cursor: pointer;
    text-align: left;
    font-family: inherit;
    color: inherit;
    transition: background .12s, border-color .12s, transform .08s;
    min-width: 0;
  }
  .shelf-row:hover {
    background: color-mix(in srgb, var(--gold-soft) 18%, var(--paper));
    border-color: var(--gold-soft);
  }
  .shelf-row:active { transform: translateY(1px); }
  .shelf-row.is-picked {
    background: color-mix(in srgb, var(--gold-soft) 38%, var(--paper));
    border-color: var(--gold);
    box-shadow: inset 0 0 0 1px var(--gold);
  }
  .shelf-row-img {
    display: block;
    width: 48px; height: 36px;
    border-radius: 3px;
    overflow: hidden;
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    flex-shrink: 0;
  }
  .shelf-row-img img {
    width: 100%; height: 100%;
    object-fit: cover;
    display: block;
  }
  .shelf-row-img img[data-placeholder] {
    opacity: 0.65;
    object-fit: contain;
  }
  .shelf-row-text {
    display: flex; flex-direction: column;
    min-width: 0;
    gap: 1px;
  }
  .shelf-row-kanji {
    font-family: var(--serif-jp); font-size: 15px;
    color: var(--ink); font-weight: 500;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .shelf-row-kana {
    font-family: var(--serif-jp); font-size: 11px;
    color: var(--ink-4);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .shelf-row-price {
    font-family: var(--serif); font-size: 14px;
    color: var(--ink-2);
    font-variant-numeric: tabular-nums;
    padding-right: 4px;
  }
  .shelf-row-mark {
    position: absolute; top: 4px; right: 6px;
    color: var(--gold-dark);
    font-weight: 700; font-size: 13px;
  }
  @media (max-width: 760px) {
    .shelf-tabs { grid-template-columns: repeat(5, 1fr); gap: 2px; padding: 3px; }
    .shelf-tab { padding: 6px 2px; }
    .shelf-tab-glyph { font-size: 18px; }
    .shelf-tab-label .ja { font-size: 11px; }
    .shelf-tab-label .en { font-size: 9px; }
    /* On narrow screens, stack — hero on top, list below */
    .shelf-body { grid-template-columns: 1fr; gap: 12px; }
    .shelf-hero { aspect-ratio: 4 / 3; max-height: 36vh; }
    .shelf-row-kanji { font-size: 14px; }
  }

  .scene-menu-foot {
    margin-top: auto;
    padding: 14px 0 4px;
    /* Continue/Next button always sits on the right. Any companion
       content (selection summary, hint) gets pushed left via flex:1 /
       margin-right:auto so the button position stays constant across
       every step — no matter what's beside it (or whether anything
       is beside it). */
    display: flex; align-items: center; justify-content: flex-end;
    gap: 12px;
    flex-wrap: wrap;
    /* Sticks to the bottom of the scrollable body so the continue button
       stays in reach even if the body scrolls. Paper-tone background +
       subtle top border lifts it visually from the content above. */
    position: sticky;
    bottom: 0;
    background: linear-gradient(to bottom, transparent 0, var(--paper-2) 18px);
    z-index: 2;
  }
  .scene-menu-foot-receipt { justify-content: center; }
  .scene-selection {
    display: flex; align-items: baseline; gap: 10px; flex: 1;
    min-width: 0;
  }
  .scene-selection .eyebrow {
    font-family: var(--serif); font-style: italic; font-size: 10px;
    letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-4);
  }
  .scene-selection-text {
    font-family: var(--serif-jp); font-size: 14px; color: var(--ink);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .scene-selection-total {
    font-family: var(--serif); font-size: 14px; color: var(--ink-2);
    font-variant-numeric: tabular-nums;
    margin-left: auto;
  }
  .scene-hint {
    font-family: var(--serif); font-style: italic; font-size: 13px;
    color: var(--ink-3);
    /* Push the Continue button to the right when only the hint sits
       in the foot — without this the button would dock against the
       hint and not against the right edge. */
    margin-right: auto;
  }
  .scene-next {
    background: var(--ink);
    color: var(--paper);
    border: none;
    padding: 12px 26px;
    border-radius: 5px;
    font-family: var(--serif); font-size: 16px;
    cursor: pointer;
    letter-spacing: 0.04em;
    transition: background .15s, transform .1s;
  }
  .scene-next:hover { background: var(--gold-dark); }
  .scene-next:disabled { background: var(--paper-edge); color: var(--ink-4); cursor: not-allowed; }

  /* Wait — the Pause. Centered atmospheric narrative line. */
  .scene-wait {
    flex: 1;
    display: flex; align-items: center; justify-content: center;
    padding: 48px 24px;
    min-height: 220px;
  }
  .scene-wait-line {
    font-family: var(--serif-jp); font-size: 26px; color: var(--ink-2);
    text-align: center;
    line-height: 1.75;
    max-width: 560px;
  }

  /* Receive — two-row layout tuned to fit a single viewport without any
     scrolling beyond what's strictly needed (the vocab list, if it has
     more rows than fit). Row 1 = NPC speech + narrative (compact).
     Row 2 = food image on the left, vocab list on the right (each card
     stretches to fill remaining height). Foot row = player line + Next
     button on a single row, sticky at the bottom. */
  .scene-receive {
    display: flex; flex-direction: column;
    gap: 10px;
    flex: 1;
    min-height: 0;
  }
  /* Compact row 1 — tighter bubble padding + slightly smaller text so the
     dialogue doesn't dominate vertical real estate. */
  .scene-receive .receive-message {
    display: flex; flex-direction: column; gap: 2px;
    flex-shrink: 0;
  }
  .scene-receive .scene-npc { gap: 12px; }
  .scene-receive .scene-npc-avatar { width: 48px; height: 48px; font-size: 24px; }
  .scene-receive .scene-npc-bubble { padding: 12px 16px; }
  .scene-receive .scene-npc-line   { font-size: 18px; line-height: 1.45; }
  .scene-receive .scene-narrative  { padding: 0 4px; font-size: 13px; }
  .scene-receive .receive-main {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 24px;
    flex: 1;
    min-height: 0;
    align-items: stretch;
  }
  /* Food column — top-view art of what arrived, centered. Wrapper is
     position:relative so the steam SVG can sit absolute-positioned above
     the image. No fixed min-height — the food card stretches to match
     the vocab card height (align-items: stretch on the grid). */
  .scene-receive .receive-food {
    position: relative;
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    border-radius: 8px;
    padding: 16px;
    display: flex; align-items: center; justify-content: center;
    min-height: 0;
    overflow: visible;
  }
  .scene-receive .receive-food .food-img {
    max-width: 100%;
    /* Sized to fit the available column without pushing the body past
       the viewport cap. The clamp ceiling intentionally stays below
       what would cause the scene-body safety-net scroll to trigger. */
    max-height: clamp(200px, 38vh, 360px);
    width: auto; height: auto;
    color: var(--ink-2); /* SVGs use currentColor */
  }
  .scene-receive .receive-food .food-img.is-arriving {
    animation: foodArrive 640ms ease-out;
  }
  @keyframes foodArrive {
    0%   { opacity: 0.12; transform: translateY(-40px); }
    100% { opacity: 1;    transform: translateY(0); }
  }
  /* Tray scene image — a 16:9 illustration of the clerk handing the
     tray across the counter (mcd-tray.png, kfc-tray.png). Used by
     fast-food restaurants. When present, fills the entire food card
     edge-to-edge and replaces both the food SVG and the small drink
     SVG cluster — the tray composition already shows food + drink. */
  .scene-receive .receive-food.has-tray-image {
    padding: 0;
    overflow: hidden;
  }
  .scene-receive .receive-food .receive-tray-img {
    width: 100%;
    height: auto;
    max-height: clamp(220px, 42vh, 380px);
    object-fit: cover;
    display: block;
    border-radius: 7px;
  }
  .scene-receive .receive-food .receive-tray-img[data-placeholder] {
    opacity: 0.7;
    object-fit: contain;
  }

  /* Tray placeholder — used by fast-food restaurants where there's
     no matching food SVG. Keeps the receive layout balanced (food
     column has *something* in it) without lying about which dish
     arrived; the receive-vocab list on the right does the heavy
     lifting for the food identity. */
  .scene-receive .receive-food .receive-tray-placeholder {
    font-family: var(--serif-jp);
    font-size: 26px;
    color: var(--ink-3);
    text-align: center;
    padding: 24px 16px;
    border: 1px dashed rgba(141,102,48,0.28);
    border-radius: 6px;
    width: 100%;
    letter-spacing: 0.08em;
  }
  /* Drinks that arrived with the food — small SVG cluster tucked into
     the bottom-right of the food card. Each drink gets its own little
     square; they cap at ~64px so they read as accompaniments next to
     the larger dish image. */
  .scene-receive .receive-food .receive-drinks {
    position: absolute;
    bottom: 10px;
    right: 10px;
    display: flex;
    gap: 6px;
    align-items: flex-end;
  }
  .scene-receive .receive-food .receive-drink {
    width: 56px; height: 56px;
    display: flex; align-items: center; justify-content: center;
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 5px;
    padding: 4px;
    box-shadow: 0 1px 3px rgba(60,40,10,0.10);
    color: var(--ink-2);
  }
  .scene-receive .receive-food .receive-drink img {
    width: 100%; height: 100%;
    object-fit: contain;
    display: block;
  }
  @media (max-width: 700px) {
    .scene-receive .receive-food .receive-drink { width: 44px; height: 44px; }
    .scene-receive .receive-food .receive-drinks { gap: 4px; bottom: 6px; right: 6px; }
  }
  @media (prefers-reduced-motion: reduce) {
    .scene-receive .receive-food .food-img.is-arriving { animation: none; }
  }
  /* Steam — reuse the existing wisp animation, positioned above the food
     image inside the receive-food card. */
  .scene-receive .receive-food .scene-steam {
    position: absolute;
    top: -36px; left: 50%;
    transform: translateX(-50%);
    width: 140px; height: 96px;
    z-index: 2;
    pointer-events: none;
  }
  /* Vocab column — scrolls internally only if the list is taller than
     the available space. Tightened paddings + row gaps so 6–7 items
     usually fit without any scrollbar. */
  .scene-receive .receive-vocab {
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    border-radius: 8px;
    padding: 14px 18px;
    display: flex; flex-direction: column;
    min-height: 0;
  }
  .scene-receive .receive-vocab-head {
    font-family: var(--serif-jp); font-weight: 600;
    font-size: 18px; color: var(--ink);
    text-align: center;
    padding-bottom: 10px;
    border-bottom: 1px dashed rgba(141,102,48,0.20);
    margin-bottom: 10px;
    word-break: break-word;
    overflow-wrap: anywhere;
    line-height: 1.4;
    flex-shrink: 0;
  }
  .scene-receive .receive-vocab-list {
    list-style: none; padding: 0; margin: 0;
    flex: 1;
    overflow-y: auto;
    padding-right: 4px;
    scrollbar-width: thin;
    scrollbar-color: var(--paper-edge) transparent;
  }
  .scene-receive .receive-vocab-list .scene-vocab-row { padding: 6px 4px; }
  .scene-receive .receive-vocab-list::-webkit-scrollbar { width: 6px; }
  .scene-receive .receive-vocab-list::-webkit-scrollbar-track { background: transparent; }
  .scene-receive .receive-vocab-list::-webkit-scrollbar-thumb {
    background: var(--paper-edge); border-radius: 3px;
  }
  .scene-receive .receive-vocab-list::-webkit-scrollbar-thumb:hover { background: var(--gold-soft); }

  /* Foot — player line + Next button on the same row. Sticky to the
     bottom of the card so it's always reachable. The player line keeps
     its right-aligned bubble feel; the Next button sits to its right
     (after wrap on mobile, below it). */
  .scene-receive .receive-foot {
    display: flex; align-items: center; justify-content: flex-end;
    gap: 14px;
    flex-wrap: wrap;
    position: sticky; bottom: 0; z-index: 4;
    padding: 6px 0 2px;
    background: linear-gradient(to bottom, transparent 0, var(--paper-2) 14px);
    flex-shrink: 0;
  }
  .scene-receive .receive-foot .scene-player-line {
    align-self: auto;
    max-width: none;
    flex: 0 1 auto;
    padding: 8px 14px;
  }
  .scene-receive .receive-foot .scene-next { flex-shrink: 0; }

  /* Mobile: stack the two columns vertically. */
  @media (max-width: 760px) {
    .scene-receive .receive-main { grid-template-columns: 1fr; gap: 16px; }
    .scene-receive .receive-food .food-img { max-height: 200px; }
    .scene-receive .receive-vocab { max-height: 280px; }
    .scene-receive .receive-foot { justify-content: space-between; }
  }

  /* Steam wisps — three curling SVG paths above the bowl that rise and
     fade in a loop. Each wisp has its own delay so the motion feels alive.
     Respects prefers-reduced-motion. */
  .scene-steam {
    position: absolute;
    top: -52px; left: 50%;
    transform: translateX(-50%);
    width: 120px; height: 80px;
    pointer-events: none;
  }
  .scene-steam .steam-wisp {
    stroke: rgba(141,102,48,0.32);
    stroke-width: 2;
    stroke-linecap: round;
    fill: none;
    opacity: 0;
    transform-origin: center bottom;
    animation: steamRise 9s ease-in-out infinite;
  }
  .scene-steam .steam-wisp-1 { animation-delay: 0s;   stroke-dasharray: 90; }
  .scene-steam .steam-wisp-2 { animation-delay: 2.4s; stroke-dasharray: 90; }
  .scene-steam .steam-wisp-3 { animation-delay: 4.8s; stroke-dasharray: 90; }
  @keyframes steamRise {
    0%   { opacity: 0; stroke-dashoffset: 90; transform: translateY(10px) scaleY(0.7); }
    25%  { opacity: 0.6; }
    70%  { opacity: 0.4; }
    100% { opacity: 0; stroke-dashoffset: -20; transform: translateY(-20px) scaleY(1.4); }
  }
  @media (prefers-reduced-motion: reduce) {
    .scene-steam .steam-wisp { animation: none; opacity: 0.35; }
  }

  /* Manga shout — 集中線 (concentration lines) behind the NPC bubble on
     the very first greeting. Plays once on enter, then fades to nothing.
     The radial ink-lines burst out from the avatar center. */
  .scene-npc-shout { position: relative; }
  .scene-shout {
    position: absolute;
    top: 50%; left: 24px;
    transform: translate(-50%, -50%);
    width: 220px; height: 220px;
    pointer-events: none;
    z-index: 0;
    opacity: 0;
    animation: shoutBurst 2.8s ease-out forwards;
  }
  .scene-shout line {
    stroke: var(--ink);
    stroke-width: 1.2;
    stroke-linecap: round;
    opacity: 0.45;
  }
  @keyframes shoutBurst {
    0%   { opacity: 0; transform: translate(-50%, -50%) scale(0.5); }
    15%  { opacity: 0.85; transform: translate(-50%, -50%) scale(1.0); }
    100% { opacity: 0;    transform: translate(-50%, -50%) scale(1.25); }
  }
  .scene-npc-shout .scene-npc-avatar,
  .scene-npc-shout .scene-npc-bubble {
    position: relative;
    z-index: 1;
  }
  @media (prefers-reduced-motion: reduce) {
    .scene-shout { animation: none; opacity: 0.25; }
  }

  /* 擬音 (gion) — small katakana sound effect floating into the wait
     step. Drifts up from below + fades. Sits behind the wait line. */
  .scene-gion {
    position: absolute;
    bottom: 18%; right: 14%;
    font-family: var(--serif-jp); font-weight: 700;
    font-size: 28px;
    color: rgba(141,102,48,0.42);
    letter-spacing: 0.04em;
    pointer-events: none;
    animation: gionFloat 6.4s ease-out forwards;
  }
  @keyframes gionFloat {
    0%   { opacity: 0; transform: translate(8px, 12px) rotate(-2deg); }
    20%  { opacity: 0.7; }
    100% { opacity: 0; transform: translate(-4px, -22px) rotate(4deg); }
  }
  @media (prefers-reduced-motion: reduce) {
    .scene-gion { animation: none; opacity: 0.45; }
  }
  .scene-wait { position: relative; }

  /* ── Cover step (restaurant selector) ─────────────────────────────────
     First screen the player sees. The cover image fills a 16:9 frame
     (with a placeholder fallback for restaurants that don't have their
     image yet). Overlay elements: top-left breadcrumb eyebrow, top-right
     category pill, bottom-left name card with glyph + setting. */
  /* Cover step — the frame hugs the artwork\'s natural height. No
     viewport stretch and no flex:1 chain trying to fill remaining
     space; the artwork sizes itself by aspect ratio, and the actions
     row sits flush beneath. */
  .cover-step {
    display: flex; flex-direction: column;
    gap: 14px;
    min-height: 0;
  }
  .cover-frame {
    width: 100%;
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 10px;
    overflow: hidden;
    box-shadow: 0 8px 24px rgba(100,72,30,0.10);
  }
  .cover-img {
    /* Full-width, natural aspect ratio. Container shrinks to fit.
       Baked-in cover text (shop name, awning, category labels) stays
       fully visible — no cropping. max-height cap is a safety net so
       a portrait cover can\'t take over the whole page. */
    display: block;
    width: 100%;
    height: auto;
    max-height: 75vh;
    object-fit: contain;
  }
  .cover-eyebrow {
    position: absolute; top: 14px; left: 20px;
    display: flex; flex-direction: column; gap: 2px;
    font-family: var(--serif); font-size: 13px;
    color: var(--ink-3);
    z-index: 2;
  }
  .cover-eyebrow-sub {
    font-style: italic; font-size: 11px; color: var(--ink-4);
    letter-spacing: 0.02em;
  }
  .cover-cat-tag {
    position: absolute; top: 14px; right: 20px;
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    border-radius: 999px;
    padding: 6px 16px;
    font-family: var(--serif-jp); font-size: 14px;
    color: var(--ink);
    z-index: 2;
    box-shadow: 0 1px 4px rgba(60,40,10,0.08);
  }
  .cover-name-card {
    position: absolute; bottom: 18px; left: 22px;
    background: color-mix(in srgb, var(--paper) 92%, transparent 8%);
    border: 1px solid var(--paper-edge);
    border-left: 4px solid var(--gold);
    border-radius: 4px;
    padding: 14px 22px 16px;
    min-width: 220px;
    display: grid;
    grid-template-columns: 1fr auto;
    grid-template-rows: auto auto auto;
    column-gap: 14px;
    z-index: 2;
    box-shadow: 0 4px 16px rgba(60,40,10,0.10);
  }
  .cover-name-glyph {
    grid-column: 1; grid-row: 1 / span 3;
    font-family: var(--serif-jp); font-weight: 700;
    font-size: 48px; line-height: 1;
    color: var(--ink);
    align-self: center;
  }
  .cover-name-en {
    grid-column: 2; grid-row: 1;
    font-family: var(--serif); font-style: normal;
    font-size: 11px; letter-spacing: 0.18em;
    color: var(--ink-3);
    margin-bottom: 4px;
  }
  .cover-name-setting {
    grid-column: 2; grid-row: 2;
    font-family: var(--serif-jp); font-size: 13px;
    color: var(--ink-2);
    line-height: 1.4;
  }
  .cover-name-npc {
    grid-column: 2; grid-row: 3;
    margin-top: 4px;
  }
  .cover-npc-pill {
    display: inline-flex; align-items: center; justify-content: center;
    width: 32px; height: 32px;
    border-radius: 16px;
    background: var(--gold-soft);
    color: var(--gold-dark);
    border: 1px solid var(--gold);
    font-family: var(--serif-jp); font-weight: 700;
    font-size: 16px; line-height: 1;
  }
  .cover-actions {
    display: flex; gap: 12px; align-items: center;
    justify-content: flex-end;
    padding: 4px 0;
    flex-wrap: wrap;
    /* Pin to the bottom of the cover-step column. The frame above flexes
       to fill all remaining space, so these buttons hug the viewport
       bottom no matter the screen height. */
    margin-top: auto;
    flex-shrink: 0;
  }
  .cover-enter { padding: 12px 32px; }
  .scene-cover-reroll {
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    color: var(--ink-3);
    padding: 10px 18px;
    border-radius: 5px;
    font-family: var(--serif); font-size: 14px;
    cursor: pointer;
    letter-spacing: 0.02em;
    transition: background .15s, color .15s, border-color .15s;
  }
  .scene-cover-reroll:hover {
    background: var(--gold-soft);
    color: var(--gold-dark);
    border-color: var(--gold);
  }
  /* Cover placeholder treatment — when the img fails and falls back to
     placeholder.svg, soften it with a subtle vignette AND switch its
     fit from "cover" to "contain". Real cover photos are designed to
     fill edge-to-edge with intentional crops; the placeholder is a
     short paper card and should sit at its natural aspect inside the
     tall frame (top-aligned, with the rest of the frame holding paper-2
     background) so we can see the frame extending around it. */
  .cover-img[data-placeholder] {
    opacity: 0.7;
    object-position: center top;
  }

  /* ── Monologue step (inner thoughts) ─────────────────────────────────
     Centered text card framed like a thought-bubble. Italic eyebrow tag,
     larger Japanese line below, EN reveal via the standard jp-line tooltip.
     Sits before the greeting. */
  .monologue-step {
    display: flex; flex-direction: column;
    gap: 18px;
    flex: 1;
    min-height: 0;
    justify-content: center;
    align-items: center;
  }
  .monologue-card {
    background: var(--paper);
    border: 1px dashed rgba(141,102,48,0.35);
    border-radius: 14px;
    padding: 28px 36px;
    max-width: 640px;
    text-align: center;
    box-shadow: 0 4px 14px rgba(60,40,10,0.06);
    position: relative;
  }
  /* Small "thought-bubble" tail */
  .monologue-card::before {
    content: '';
    position: absolute;
    bottom: -18px; left: 50%;
    width: 14px; height: 14px;
    border-radius: 50%;
    background: var(--paper);
    border: 1px dashed rgba(141,102,48,0.35);
    transform: translateX(-50%);
  }
  .monologue-card::after {
    content: '';
    position: absolute;
    bottom: -32px; left: calc(50% + 14px);
    width: 8px; height: 8px;
    border-radius: 50%;
    background: var(--paper);
    border: 1px dashed rgba(141,102,48,0.35);
  }
  .monologue-eyebrow {
    font-family: var(--serif); font-style: italic;
    font-size: 12px; letter-spacing: 0.12em;
    color: var(--ink-4);
    margin-bottom: 14px;
  }
  .monologue-text {
    font-family: var(--serif-jp);
    font-size: 21px; line-height: 1.65;
    color: var(--ink);
  }
  .monologue-line {
    /* leverages the jp-line styling — tooltip already supported */
  }

  /* ── Look-around step (atmospheric pause) ──────────────────────────
     Inside-the-shop image with a brief narrative. Sits between order
     and food arrival. The image fills the frame; narrative + continue
     button below. */
  .lookaround-step {
    display: flex; flex-direction: column;
    gap: 14px;
    flex: 1;
    min-height: 0;
  }
  .lookaround-frame {
    position: relative;
    width: 100%;
    aspect-ratio: 12 / 7;
    max-height: 50vh;
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 10px;
    overflow: hidden;
    box-shadow: 0 4px 16px rgba(60,40,10,0.06);
  }
  .lookaround-img {
    position: absolute; inset: 0;
    width: 100%; height: 100%;
    object-fit: cover;
    display: block;
  }
  .lookaround-img[data-placeholder] { opacity: 0.7; }
  .lookaround-narrative {
    font-family: var(--serif-jp);
    font-size: 18px;
    color: var(--ink-2);
    text-align: center;
    padding: 8px 4px;
  }

  /* Mobile adjustments for the new steps */
  @media (max-width: 700px) {
    /* Cover frame still flexes to fill on mobile — no aspect-ratio cap. */
    .monologue-card { padding: 20px 22px; }
    .monologue-text { font-size: 18px; }
    .lookaround-frame { aspect-ratio: 4 / 3; max-height: 40vh; }
  }

  /* ── Cashier scene image (konbini order + change) ─────────────────
     Shared 16:9 frame for any step that opts into a cashierImage. Sits
     at the top of the step inner — replaces the usual SVG block. */
  .cashier-frame {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 9;
    max-height: 38vh;
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 8px;
    overflow: hidden;
    margin-bottom: 14px;
    box-shadow: 0 4px 16px rgba(60,40,10,0.08);
  }
  .cashier-img {
    position: absolute; inset: 0;
    width: 100%; height: 100%;
    object-fit: cover;
    display: block;
  }
  .cashier-img[data-placeholder] {
    opacity: 0.7;
    object-fit: contain;
  }

  /* ── Change step ─────────────────────────────────────────────────
     Three-row receipt-style ledger (bill / received / change), then a
     visual breakdown of the change as individual bills + coins. */
  .change-ledger {
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    border-radius: 6px;
    padding: 14px 20px;
    margin: 14px 0 18px;
    font-variant-numeric: tabular-nums;
  }
  .change-ledger-row {
    display: flex; align-items: baseline; justify-content: space-between;
    padding: 6px 0;
    gap: 16px;
  }
  .change-ledger-row + .change-ledger-row {
    border-top: 1px dotted rgba(141,102,48,0.22);
  }
  .change-ledger-label {
    font-family: var(--serif-jp);
    font-size: 14px;
    color: var(--ink-3);
  }
  .change-ledger-value {
    font-family: var(--serif);
    font-size: 18px;
    color: var(--ink);
  }
  .change-ledger-result .change-ledger-label,
  .change-ledger-result .change-ledger-value {
    color: var(--gold-dark);
    font-weight: 600;
  }
  .change-ledger-result .change-ledger-value {
    font-size: 22px;
  }
  .change-breakdown {
    display: flex; flex-direction: column;
    gap: 10px;
    margin-bottom: 8px;
  }
  .change-empty {
    font-family: var(--serif); font-style: italic;
    color: var(--ink-3);
    text-align: center;
    padding: 18px 0;
  }
  .change-row {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: center;
    gap: 16px;
    padding: 6px 4px;
    border-bottom: 1px dashed rgba(141,102,48,0.16);
  }
  .change-row:last-child { border-bottom: none; }
  .change-row-pieces {
    display: flex; flex-wrap: wrap;
    gap: 6px;
    align-items: center;
  }
  .change-row-label {
    display: flex; flex-direction: column; align-items: flex-end;
    line-height: 1.2;
    min-width: 80px;
  }
  .change-denom {
    font-family: var(--serif);
    font-size: 14px;
    color: var(--ink-2);
    font-variant-numeric: tabular-nums;
  }
  .change-times {
    font-family: var(--serif); font-style: italic;
    font-size: 12px;
    color: var(--ink-4);
  }

  /* ── Bills + coins ──────────────────────────────────────────────
     Approximated colors for real Japanese currency. Bills are
     rectangular; coins are circular; 50/5-yen coins get a center
     hole rendered as an absolutely-positioned paper-tone dot. */
  .money-bill {
    display: inline-flex; align-items: center; justify-content: center;
    width: 60px; height: 36px;
    border-radius: 3px;
    border: 1px solid rgba(0,0,0,0.18);
    box-shadow: 0 1px 3px rgba(60,40,10,0.18), inset 0 0 0 1px rgba(255,255,255,0.18);
    font-family: var(--serif-jp); font-weight: 700;
    font-size: 11px; line-height: 1;
    letter-spacing: 0.02em;
    color: rgba(20,15,5,0.85);
    gap: 2px;
  }
  .money-bill .money-num { font-size: 13px; }
  .money-bill .money-mon { font-size: 9px; opacity: 0.75; }
  .money-bill-10000 { background: linear-gradient(135deg, #d6b88a, #b69466); color: rgba(40,28,10,0.92); }
  .money-bill-5000  { background: linear-gradient(135deg, #b29bc4, #8e7099); color: white; }
  .money-bill-2000  { background: linear-gradient(135deg, #a7c096, #7fa365); color: white; }
  .money-bill-1000  { background: linear-gradient(135deg, #8eabc2, #5e7c97); color: white; }
  .money-coin {
    position: relative;
    display: inline-flex; align-items: center; justify-content: center;
    width: 34px; height: 34px;
    border-radius: 50%;
    border: 1px solid rgba(0,0,0,0.18);
    box-shadow: 0 1px 2px rgba(60,40,10,0.18), inset 0 0 0 1px rgba(255,255,255,0.15);
    font-family: var(--serif); font-weight: 700;
    font-size: 12px; line-height: 1;
    color: rgba(20,15,5,0.85);
    font-variant-numeric: tabular-nums;
  }
  .money-coin-500 { background: radial-gradient(circle at 30% 30%, #e8c97a, #b58c3e); }
  .money-coin-100 { background: radial-gradient(circle at 30% 30%, #cfcfcf, #969696); }
  .money-coin-50  { background: radial-gradient(circle at 30% 30%, #d8d8d8, #a0a0a0); }
  .money-coin-10  { background: radial-gradient(circle at 30% 30%, #c8855a, #8a5530); color: rgba(255,250,240,0.95); }
  .money-coin-5   { background: radial-gradient(circle at 30% 30%, #e2c576, #b08a3a); }
  .money-coin-1   { background: radial-gradient(circle at 30% 30%, #ececec, #b8b8b8); }
  .money-hole {
    position: absolute;
    top: 50%; left: 50%; transform: translate(-50%, -50%);
    width: 9px; height: 9px;
    border-radius: 50%;
    background: var(--paper-2);
    border: 1px solid rgba(0,0,0,0.16);
    box-shadow: inset 0 1px 1px rgba(0,0,0,0.18);
  }
  /* 5/50-yen coins put the number off-center because the hole is in
     the middle; offset the digit a touch. */
  .money-coin-50 .money-num,
  .money-coin-5  .money-num {
    transform: translateY(11px);
    font-size: 9px;
    font-weight: 700;
  }

  @media (max-width: 700px) {
    .cashier-frame { aspect-ratio: 16 / 9; max-height: 32vh; }
    .money-bill { width: 52px; height: 32px; }
    .money-coin { width: 30px; height: 30px; }
    .change-ledger-value { font-size: 16px; }
    .change-ledger-result .change-ledger-value { font-size: 19px; }
  }

  /* Per-step silhouette — a small manga-ink illustration that anchors the
     step visually. Sits above the dialogue. Color flows from currentColor
     on the .scene-svg wrapper, so all SVGs adopt the parent ink hue and
     stay consistent with the paper aesthetic. */
  .scene-svg {
    display: flex;
    justify-content: center;
    margin: -4px 0 12px;
    color: var(--ink-2);
  }
  .scene-svg img {
    /* Responsive to viewport height — shrinks on short screens, caps on
       tall ones. Prevents the illustration from eating critical room
       needed for the dialogue + choices below it. */
    width: clamp(96px, 16vh, 152px);
    height: clamp(96px, 16vh, 152px);
    opacity: 0.85;
    /* Slight ink-print drop so the silhouette feels printed, not flat */
    filter: drop-shadow(0 1px 0 rgba(60,40,10,0.06));
  }
  /* On wider steps (menu, receive, receipt) the SVG sits smaller so it
     doesn't dominate. */
  .scene-body .scene-menu ~ .scene-menu-foot,
  .scene-body .scene-receipt ~ .scene-menu-foot { padding-top: 8px; }

  /* Experience-mode shop name in the scene head — replaces the generic
     setting label with the rolled restaurant's name + small EN gloss. */
  .exp-shop-name { font-family: var(--serif-jp); font-weight: 600; color: var(--ink); font-size: 14px; }
  .exp-shop-sep  { color: var(--ink-4); margin: 0 6px; }
  .exp-shop-en   { font-family: var(--serif); font-style: italic; color: var(--ink-3); font-size: 12px; }

  /* ── Shop picker dropdown ──────────────────────────────────────
     The place-name line in the scene header is also the trigger:
     same visuals as before (no border, no background) plus a tiny
     caret. Click opens a paper-toned popover that lists every
     restaurant grouped by category. Intentionally subtle so the
     current header rhythm reads unchanged. */
  .scene-head-place { position: relative; }
  .exp-shop-picker {
    background: transparent;
    border: none;
    padding: 2px 4px;
    margin: -2px -4px;
    font: inherit; color: inherit;
    cursor: pointer;
    border-radius: 3px;
    display: inline-flex; align-items: baseline;
    transition: background .12s;
  }
  .exp-shop-picker:hover { background: color-mix(in srgb, var(--gold-soft) 30%, transparent); }
  .exp-shop-picker:focus-visible { outline: 1px solid var(--gold); outline-offset: 1px; }
  .exp-shop-picker[aria-expanded="true"] { background: var(--gold-soft); }
  .exp-shop-caret {
    margin-left: 6px;
    font-size: 10px;
    color: var(--ink-4);
    line-height: 1;
    transition: transform .15s, color .12s;
  }
  .exp-shop-picker:hover .exp-shop-caret { color: var(--ink-3); }
  .exp-shop-picker[aria-expanded="true"] .exp-shop-caret {
    transform: translateY(1px) rotate(180deg);
    color: var(--ink-2);
  }
  .exp-shop-dropdown {
    position: absolute;
    top: calc(100% + 6px);
    /* Center under the picker; if the column is narrow the popover
       overflows symmetrically rather than clinging to one edge. */
    left: 50%;
    transform: translateX(-50%);
    min-width: 260px;
    max-width: min(420px, calc(100vw - 32px));
    max-height: 60vh;
    overflow-y: auto;
    z-index: 50;
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    border-radius: 6px;
    box-shadow: 0 12px 32px rgba(60,40,10,0.18);
    padding: 6px 4px;
    text-align: left;
    scrollbar-width: thin;
    scrollbar-color: var(--paper-edge) transparent;
  }
  .exp-shop-group { padding: 4px 0; }
  .exp-shop-group + .exp-shop-group {
    border-top: 1px dashed rgba(141,102,48,0.18);
    margin-top: 4px;
  }
  .exp-shop-group-head {
    padding: 4px 12px 6px;
    display: flex; align-items: baseline; gap: 8px;
  }
  .exp-shop-group-head .ja {
    font-family: var(--serif-jp); font-weight: 600;
    font-size: 11px;
    color: var(--ink-3);
    letter-spacing: 0.04em;
  }
  .exp-shop-group-head .en {
    font-family: var(--serif); font-style: italic;
    font-size: 10px;
    color: var(--ink-4);
    text-transform: lowercase;
    letter-spacing: 0.04em;
  }
  .exp-shop-list { list-style: none; padding: 0; margin: 0; }
  .exp-shop-option {
    width: 100%;
    display: flex; align-items: baseline; gap: 8px;
    padding: 5px 12px;
    background: transparent;
    border: none;
    cursor: pointer;
    font: inherit; color: inherit; text-align: left;
    border-radius: 3px;
    transition: background .12s;
    position: relative;
  }
  .exp-shop-option:hover {
    background: color-mix(in srgb, var(--gold-soft) 30%, transparent);
  }
  .exp-shop-option.is-active {
    background: var(--gold-soft);
  }
  .exp-shop-option .ja {
    font-family: var(--serif-jp); font-weight: 500;
    font-size: 13px;
    color: var(--ink);
  }
  .exp-shop-option .en {
    font-family: var(--serif); font-style: italic;
    font-size: 11px;
    color: var(--ink-3);
  }
  .exp-shop-option .mark {
    margin-left: auto;
    color: var(--gold-dark);
    font-weight: 700;
    font-size: 11px;
  }

  /* Furigana-toggle button — when on, the button itself shows the active
     gold state so the user knows the mode is engaged. */
  .scene-control.is-on {
    background: var(--gold-soft);
    color: var(--gold-dark);
    border-color: var(--gold);
  }
  .scene-bowl-head {
    font-family: var(--serif-jp); font-weight: 600;
    font-size: 21px; color: var(--ink);
    text-align: center;
    padding-bottom: 14px;
    border-bottom: 1px dashed rgba(141,102,48,0.20);
    margin-bottom: 16px;
    /* Long order names with ・ separators must wrap — without this they
       were forcing the body wider than the card. */
    word-break: break-word;
    overflow-wrap: anywhere;
    line-height: 1.5;
  }
  .scene-vocab-list { list-style: none; padding: 0; margin: 0; }
  .scene-vocab-row {
    display: grid; grid-template-columns: minmax(0, auto) minmax(0, auto) minmax(0, 1fr);
    gap: 14px;
    align-items: baseline;
    padding: 8px 6px;
    /* min-width:0 on the grid columns lets the 1fr column shrink/wrap
       instead of pushing horizontal overflow. */
    min-width: 0;
  }
  .scene-vocab-row > * { min-width: 0; word-break: break-word; }
  .v-kanji { font-family: var(--serif-jp); font-size: 20px; color: var(--ink); font-weight: 500; }
  .v-kana  { font-family: var(--serif-jp); font-size: 15px; color: var(--ink-3); }
  .v-en    { font-family: var(--serif); font-style: italic; font-size: 14px; color: var(--ink-4); text-align: right; }

  /* Pay — total card + payment choices. */
  .scene-total-card {
    display: flex; align-items: baseline; justify-content: space-between;
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    border-radius: 4px;
    padding: 14px 20px;
  }
  .scene-total-card .eyebrow {
    font-family: var(--serif); font-style: italic; font-size: 11px;
    letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-4);
  }
  .scene-total-yen {
    font-family: var(--serif-jp); font-size: 32px; font-weight: 600; color: var(--ink);
    font-variant-numeric: tabular-nums;
  }

  /* Receipt — typeset paper receipt as session outro. */
  .scene-receipt {
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    box-shadow: 0 8px 24px rgba(100,72,30,0.08);
    border-radius: 4px;
    padding: 28px 32px;
    max-width: 520px;
    margin: 0 auto;
    font-family: var(--serif-jp);
    /* The receipt itself scrolls internally when it gets tall — the
       "play again" button below stays anchored. */
    max-height: clamp(320px, calc(100vh - 240px), 720px);
    overflow-y: auto;
    scrollbar-width: thin;
    scrollbar-color: var(--paper-edge) transparent;
  }
  .scene-receipt::-webkit-scrollbar { width: 6px; }
  .scene-receipt::-webkit-scrollbar-track { background: transparent; }
  .scene-receipt::-webkit-scrollbar-thumb {
    background: var(--paper-edge);
    border-radius: 3px;
  }
  .scene-receipt::-webkit-scrollbar-thumb:hover { background: var(--gold-soft); }
  .receipt-shop {
    text-align: center;
    font-size: 17px; font-weight: 600; color: var(--ink);
    letter-spacing: 0.06em;
  }
  .receipt-rule {
    border-bottom: 1px dashed rgba(141,102,48,0.30);
    margin: 14px 0;
  }
  .receipt-section { margin: 16px 0; }
  .receipt-label {
    font-family: var(--serif); font-style: italic; font-size: 12px;
    color: var(--ink-4); letter-spacing: 0.1em;
    margin-bottom: 10px;
  }
  .receipt-lines, .receipt-vocab { list-style: none; padding: 0; margin: 0; }
  .receipt-lines li {
    display: flex; align-items: baseline; gap: 8px;
    padding: 4px 0;
    font-family: var(--serif-jp); font-size: 16px; color: var(--ink);
  }
  .receipt-lines .dot-line {
    flex: 1; border-bottom: 1px dotted var(--paper-edge);
    margin-bottom: 4px;
  }
  .receipt-total {
    display: flex; align-items: baseline; gap: 8px;
    padding: 14px 0 6px;
    border-top: 1px solid var(--paper-edge);
    border-bottom: 2px double var(--paper-edge);
    margin-top: 10px;
    font-family: var(--serif-jp); font-size: 18px; font-weight: 600; color: var(--ink);
  }
  .receipt-total .dot-line {
    flex: 1; border-bottom: 1px dotted var(--paper-edge);
    margin-bottom: 4px;
  }
  .receipt-vocab li {
    display: grid; grid-template-columns: auto auto 1fr; gap: 10px;
    padding: 3px 0;
  }
  .receipt-vocab .rv-kanji { font-family: var(--serif-jp); font-size: 16px; color: var(--ink); }
  .receipt-vocab .rv-kana  { font-family: var(--serif-jp); font-size: 13px; color: var(--ink-3); }
  .receipt-vocab .rv-en    { font-family: var(--serif); font-style: italic; font-size: 13px; color: var(--ink-4); text-align: right; }
  .receipt-foot {
    text-align: center;
    font-family: var(--serif); font-style: italic; font-size: 12px;
    color: var(--ink-3);
    margin-top: 14px;
  }

  @media (max-width: 700px) {
    .scene-frame { padding: 18px 18px 22px; font-size: 15px; }
    .scene-head { grid-template-columns: 1fr auto; }
    .scene-head-place { display: none; }
    .scene-npc-avatar { width: 44px; height: 44px; font-size: 22px; }
    .scene-npc-line   { font-size: 19px; }
    .scene-narrative  { font-size: 15px; }
    .scene-choice .ja { font-size: 18px; }
    .scene-choice .en { font-size: 13px; }
    .scene-menu       { padding: 14px 16px; }
    .m-kanji          { font-size: 18px; }
    .m-kana           { font-size: 13px; }
    .m-price          { font-size: 15px; }
    .scene-wait-line  { font-size: 22px; }
    .scene-bowl-head  { font-size: 19px; }
    .v-kanji          { font-size: 18px; }
    .scene-svg img    { width: 120px; height: 120px; }
    .scene-total-yen  { font-size: 26px; }
  }

  /* ─── card modal ─────────────────────────────────────────────────────
     Opens when a Writing → Colors tile is clicked. Shows the deck
     flashcard for that color so the learner can quickly see examples
     without losing their place in the reference grid. Centered modal
     on desktop, fullscreen on mobile (≤700px). */
  .card-modal-backdrop {
    display: none;
    position: fixed; inset: 0;
    background: rgba(20,12,4,0.45);
    z-index: 70;
    opacity: 0;
    transition: opacity .2s ease;
  }
  .card-modal-backdrop.is-open { display: block; opacity: 1; }
  .card-modal {
    position: fixed;
    z-index: 80;
    background: var(--paper);
    box-shadow: 0 16px 40px rgba(60,40,10,0.25);
    border: 1px solid var(--paper-edge);
    opacity: 0;
    pointer-events: none;
    /* Desktop default: centered card. */
    top: 50%; left: 50%;
    transform: translate(-50%, -50%) scale(0.96);
    width: min(560px, 92vw);
    max-height: 86vh;
    border-radius: 10px;
    overflow: hidden;
    display: flex; flex-direction: column;
    transition: opacity .2s ease, transform .25s cubic-bezier(.4,.0,.2,1);
  }
  .card-modal.is-open {
    opacity: 1;
    pointer-events: auto;
    transform: translate(-50%, -50%) scale(1);
  }
  .card-modal-close {
    position: absolute;
    top: 8px; right: 10px;
    background: none; border: none;
    font-family: var(--serif); font-size: 26px;
    color: var(--ink-3);
    cursor: pointer;
    width: 32px; height: 32px;
    line-height: 1;
    border-radius: 16px;
    z-index: 1;
    transition: background .12s, color .12s;
  }
  .card-modal-close:hover {
    background: var(--paper-2);
    color: var(--gold-dark);
  }
  .card-modal-body {
    padding: 28px 32px 24px;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
  }
  /* The .flash-card inside the modal: drop the outer shadow rings since
     the modal already has its own framing — the card-within-card would
     look noisy. */
  .card-modal-body .flash-card {
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    box-shadow: none;
    padding: 24px 24px 28px;
    border-radius: 6px;
    display: flex; flex-direction: column;
  }
  /* Mobile: fill the screen. */
  @media (max-width: 700px) {
    .card-modal {
      top: 0; left: 0;
      transform: translate(0, 0) scale(1);
      width: 100vw; height: 100vh; max-height: 100vh;
      border-radius: 0;
      border: none;
    }
    .card-modal.is-open { transform: translate(0, 0) scale(1); }
    .card-modal-close { top: 12px; right: 14px; font-size: 28px; }
    .card-modal-body { padding: 56px 20px 24px; }
  }

  /* ─── radicals page ────────────────────────────────────────────────────
     A paper-on-paper reference. Stroke counts act as quiet chapter markers,
     not headers — they live to the left of each row as a small italic numeral
     + 画 (stroke count). Radical chips are dense but breathing: small font,
     just enough padding to be a target. Selected chips fill with gold-soft.
     Chips that can't appear in the current intersection get dimmed (still
     clickable — mirrors jisho.org). Results panel is its own block below,
     sorted by deck order so the user lands on the kanji they've been
     studying first. */
  .rad-selected {
    display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
    margin: 18px 0 14px;
    padding: 10px 14px;
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 8px;     /* match .rad-grid + .dict-list */
    min-height: 44px;
    transition: background .15s;
  }
  .rad-selected.is-empty {
    background: transparent;
    border-style: dashed;
    border-color: rgba(141,102,48,0.18);
  }
  .rad-selected-label {
    font-family: var(--serif); font-style: italic; font-size: 11px;
    letter-spacing: 0.12em; text-transform: uppercase;
    color: var(--ink-4);
  }
  .rad-selected-chips {
    display: flex; gap: 6px; flex-wrap: wrap; flex: 1;
  }
  .rad-selected-placeholder {
    font-family: var(--serif); font-style: italic; font-size: 12px;
    color: var(--ink-4);
  }
  .rad-clear {
    font-family: var(--serif); font-style: italic; font-size: 12px;
    color: var(--ink-3);
    background: transparent; border: none; cursor: pointer;
    padding: 4px 8px;
    transition: color .15s;
  }
  .rad-clear:hover { color: var(--gold-dark); }

  .rad-grid {
    display: flex; flex-direction: column;
    gap: 12px;
    margin-bottom: 28px;
    /* Wrap the radical pad in a paper card so the chips read cleanly
       against any section background — otherwise the bg illustration
       crowds the dense grid. paper-2 with a soft border + radius gives
       the same "panel" feel as the dictionary list and the bottom
       results card. */
    padding: 14px 18px;
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 8px;
  }
  /* Each stroke row: a small fixed-width label on the left, then the chips
     wrapping freely on the right. The number anchors the row visually so
     the eye can scan stroke counts without reading. */
  .rad-stroke-row {
    display: grid;
    grid-template-columns: 56px 1fr;
    gap: 12px;
    align-items: start;
    padding: 8px 0;
    border-bottom: 1px solid rgba(141,102,48,0.10);
  }
  .rad-stroke-row:last-child { border-bottom: none; }
  .rad-stroke-label {
    display: flex; align-items: baseline; gap: 3px;
    padding-top: 4px;
    font-family: var(--serif-jp);
    color: var(--ink-3);
  }
  .rad-stroke-num {
    font-family: var(--serif); font-style: italic;
    font-size: 18px; font-weight: 500;
    color: var(--ink-2);
  }
  .rad-stroke-unit {
    font-size: 12px; color: var(--ink-4);
  }
  .rad-stroke-chips {
    display: flex; flex-wrap: wrap;
    gap: 4px;
  }
  .rad-chip {
    font-family: var(--serif-jp); font-size: 17px;
    color: var(--ink);
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 3px;
    padding: 4px 8px;
    line-height: 1.1;
    min-width: 30px;
    cursor: pointer;
    transition: background .12s, border-color .12s, color .12s, opacity .12s;
  }
  .rad-chip:hover {
    background: var(--paper);
    border-color: var(--gold);
  }
  .rad-chip.is-active {
    background: var(--gold-soft);
    border-color: var(--gold);
    color: var(--gold-dark);
    font-weight: 600;
  }
  .rad-chip.is-dim {
    opacity: 0.32;
  }
  /* Tray chips are slightly larger and have a clearer 'remove me' feel
     — the close cue (×-tail) comes from hover behavior. */
  .rad-chip.is-tray {
    background: var(--gold-soft);
    border-color: var(--gold);
    color: var(--gold-dark);
    font-weight: 600;
    font-size: 18px;
    padding: 5px 10px;
  }
  .rad-chip.is-tray:hover {
    background: var(--paper-2);
    color: var(--ink-2);
    border-style: dashed;
  }

  /* Results panel — block below the radical grid. The kanji glyph reads
     large; reading + meaning sit alongside as a compact line. Click → jump
     to that kanji's flashcard. */
  .rad-results {
    margin-top: 18px;
    padding-top: 18px;
    border-top: 1px solid var(--paper-edge);
  }
  .rad-results-head {
    display: flex; align-items: baseline; gap: 10px;
    margin-bottom: 14px;
  }
  .rad-results-label {
    font-family: var(--serif); font-style: italic;
    font-size: 13px; color: var(--ink-3);
    letter-spacing: 0.04em;
  }
  .rad-results-count {
    font-family: var(--serif); font-size: 12px;
    color: var(--ink-4);
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 12px;
    padding: 2px 9px;
  }
  .rad-empty {
    font-family: var(--serif); font-style: italic;
    font-size: 13px; color: var(--ink-4);
    padding: 18px 0;
  }
  .rad-result-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 10px;
  }
  .rad-result {
    display: flex; align-items: center; gap: 12px;
    padding: 10px 12px;
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 4px;
    cursor: pointer;
    text-align: left;
    transition: border-color .15s, background .15s, transform .12s;
  }
  .rad-result:hover {
    background: var(--paper);
    border-color: var(--gold);
    transform: translateY(-1px);
  }
  .rad-result .rr-kanji {
    font-family: var(--serif-jp); font-weight: 700;
    font-size: 28px; line-height: 1; color: var(--ink);
    flex-shrink: 0;
  }
  .rad-result .rr-body {
    display: flex; flex-direction: column; gap: 1px;
    min-width: 0;
  }
  .rad-result .rr-reading {
    font-family: var(--serif-jp); font-size: 13px;
    color: var(--ink-2);
  }
  .rad-result .rr-on {
    font-family: var(--serif-jp); font-size: 11px;
    color: var(--ink-4); letter-spacing: 0.05em;
  }
  .rad-result .rr-en {
    font-family: var(--serif); font-style: italic;
    font-size: 12px; color: var(--ink-3);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }

  @media (max-width: 520px) {
    .rad-stroke-row { grid-template-columns: 44px 1fr; gap: 8px; }
    .rad-stroke-num { font-size: 16px; }
    .rad-chip { font-size: 16px; padding: 3px 7px; }
    .rad-result .rr-kanji { font-size: 24px; }
  }

  /* Page-head subtitle paragraph — small italic explainer. */
  .page-sub {
    font-family: var(--serif); font-style: italic;
    font-size: 13px; color: var(--ink-3);
    line-height: 1.5;
    margin: 8px 0 0;
    max-width: 540px;
  }

  /* ─── colors reference grid ────────────────────────────────────────── */
  /* Inline toggle that lives in the writing/colors page header — paints the
     kanji prefix in its swatch hue. Shares state with the flashcards Colors
     class toggle (jp:flashColorize). */
  .writing-colors-controls {
    display: flex; align-items: center; gap: 8px;
    margin-top: 14px;
  }
  .writing-colors-controls .small-label {
    font-family: var(--serif); font-style: italic; font-size: 12px;
    color: var(--ink-3); letter-spacing: 0.04em;
  }
  .color-ref-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 16px;
    margin-top: 24px;
  }
  .color-ref-card {
    display: flex; align-items: center; gap: 14px;
    padding: 18px 16px;
    overflow: hidden;
    background: var(--paper-2);
    border: 1px solid rgba(141,102,48,0.12);
    border-radius: 8px;
    transition: box-shadow .15s, border-color .15s, transform .12s;
    cursor: pointer;
  }
  .color-ref-card:hover {
    box-shadow: 0 4px 12px rgba(100,72,30,0.14);
    border-color: var(--gold-soft);
    transform: translateY(-1px);
  }
  .color-ref-card:focus-visible {
    outline: 2px solid var(--gold);
    outline-offset: 2px;
  }
  .color-ref-swatch {
    flex: 0 0 auto;
    width: 36px; height: 50px;
    opacity: 0.75;
  }
  .color-ref-swatch[data-light] {
    opacity: 0.85;
  }
  .color-ref-body {
    display: flex; flex-direction: column; gap: 2px;
    min-width: 0;
  }
  .color-ref-top {
    display: flex; align-items: baseline; gap: 8px;
    flex-wrap: wrap;
  }
  .color-ref-kanji {
    font-family: var(--serif-jp); font-weight: 700;
    font-size: 28px; line-height: 1.1; color: var(--ink);
    white-space: nowrap;
  }
  .color-ref-furi {
    font-family: var(--serif-jp); font-size: 17px;
    color: var(--ink-2); white-space: nowrap;
  }
  .color-ref-alt {
    font-family: var(--serif-jp); font-size: 10px;
    color: var(--ink-4); line-height: 1.2;
  }
  .color-ref-en {
    font-family: var(--serif); font-style: italic;
    font-size: 12px; color: var(--ink-3);
  }

  /* ─── class strip (above book picker) ───────────────────────────────── */
  .class-strip {
    display: flex; gap: 6px; flex-wrap: wrap;
    margin-bottom: 10px;
    padding-bottom: 10px;
    border-bottom: 1px dashed rgba(141,102,48,0.20);
  }
  .class-tab {
    display: inline-flex; align-items: center; gap: 10px;
    padding: 6px 14px 6px 10px;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 999px;
    color: var(--ink-3);
    cursor: pointer;
    transition: background .15s, color .15s, border-color .15s;
  }
  .class-tab:hover { background: rgba(180,138,74,0.08); color: var(--ink-2); }
  .class-tab.active {
    background: var(--paper-2);
    border-color: rgba(141,102,48,0.30);
    color: var(--ink);
  }
  .class-tab .glyph {
    font-family: var(--font-menu-ja); font-weight: 700; font-size: 20px;
    color: var(--gold-dark);
    line-height: 1; width: 22px; text-align: center;
  }
  .class-tab.active .glyph { color: var(--ink); }
  .class-tab .label {
    display: flex; flex-direction: column; gap: 1px; line-height: 1.1;
  }
  .class-tab .label .ja {
    font-family: var(--font-menu-ja); font-size: 13px;
  }
  .class-tab .label .en {
    font-family: var(--font-menu-en); font-style: italic; font-size: 11px;
    color: var(--ink-3);
  }
  .class-tab.active .label .en { color: var(--ink-2); }

  /* ─── book picker ───────────────────────────────────────────────────── */
  .book-strip {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
    gap: 14px;
    margin-bottom: 24px;
  }
  .book-strip.book-strip-scroll {
    display: flex;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    gap: 12px;
    padding-bottom: 4px;
  }
  .book-strip.book-strip-scroll::-webkit-scrollbar { display: none; }
  .book-strip.book-strip-scroll .book-card {
    flex: 0 0 auto;
    min-width: 120px;
    max-width: 160px;
    scroll-snap-align: start;
  }
  .book-card {
    background: var(--paper-2);
    border: 1px solid rgba(141,102,48,0.22);
    border-radius: 6px;
    padding: 16px 14px;
    cursor: pointer;
    transition: all .15s;
    display: flex; flex-direction: column; gap: 8px;
    position: relative;
    overflow: hidden;
  }
  .book-card::before {
    content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 4px;
    background: var(--sage-soft);
    transition: background .15s;
  }
  .book-card:hover { transform: translateY(-2px); border-color: var(--gold); }
  .book-card:hover::before { background: var(--gold); }
  .book-card.active { border-color: var(--gold-dark); background: var(--paper); }
  .book-card.active::before { background: var(--gold); }
  .book-card .glyph {
    font-family: var(--serif-jp); font-weight: 700; font-size: 26px;
    color: var(--gold-dark);
  }
  .book-card .ja {
    font-family: var(--serif-jp); font-size: 15px; color: var(--ink);
  }
  .book-card .en {
    font-family: var(--serif); font-style: italic; font-size: 12px;
    color: var(--ink-3);
  }

  /* ─── misc ──────────────────────────────────────────────────────────── */
  .floating-controls {
    display: flex; align-items: center; gap: 10px;
    margin-bottom: 22px; flex-wrap: wrap;
  }
  .floating-controls .sep {
    width: 1px; height: 22px; background: var(--paper-edge);
    margin: 0 4px;
  }
  .small-label {
    font-family: var(--serif); font-style: italic; font-size: 12px;
    color: var(--ink-3); margin-right: 4px;
  }

  /* ─── word popover ──────────────────────────────────────────────────── */
  .vocab-row.is-open { background: rgba(180,138,74,0.15); }
  .vocab-row.is-open .num { color: var(--gold-dark); }

  .word-pop-backdrop {
    position: fixed; inset: 0; z-index: 80;
  }
  .word-pop {
    position: fixed; z-index: 81;
    width: 304px;
    background: var(--paper-2);
    border: 1px solid var(--gold-soft);
    border-radius: 6px;
    box-shadow:
      0 0 0 4px var(--paper-2),
      0 0 0 5px var(--gold-soft),
      0 14px 38px rgba(60,40,20,0.22);
    padding: 22px 22px 18px;
  }
  .word-pop.is-hidden { visibility: hidden; }

  .word-pop-close {
    position: absolute; top: 10px; right: 10px;
    width: 22px; height: 22px; border-radius: 50%;
    border: none; background: transparent;
    color: var(--ink-3); cursor: pointer;
    font-family: var(--serif); font-size: 18px; line-height: 1;
    transition: color .12s, background .12s;
  }
  .word-pop-close:hover { color: var(--ink); background: rgba(141,102,48,0.10); }

  .word-pop-glyph {
    font-family: var(--serif-jp); font-weight: 700;
    font-size: 52px; line-height: 1;
    color: var(--ink); letter-spacing: 0.01em;
    margin: 0;
  }
  .word-pop-kana {
    font-family: var(--serif-jp); font-size: 14px;
    color: var(--ink-2);
    margin-top: 6px;
    letter-spacing: 0.04em;
  }
  .word-pop-en {
    font-family: var(--serif); font-style: italic; font-size: 13px;
    color: var(--ink-3);
    margin-top: 2px;
  }

  .word-pop-breakdown {
    display: flex; flex-wrap: wrap; align-items: center;
    gap: 6px 10px;
    margin-top: 12px;
    padding: 10px 10px 8px;
    background: rgba(180,138,74,0.08);
    border-left: 2px solid var(--gold-soft);
    border-radius: 0 4px 4px 0;
    font-family: var(--serif);
    font-size: 12px;
  }
  .word-pop-breakdown .bk-pair {
    display: inline-flex; align-items: center; gap: 5px;
    white-space: nowrap;
  }
  .word-pop-breakdown .bk-stack {
    display: inline-flex; flex-direction: column; align-items: center;
    line-height: 1; gap: 1px;
  }
  .word-pop-breakdown .bk-reading {
    font-family: var(--serif-jp); font-size: 9px;
    color: var(--ink-3); letter-spacing: 0.02em;
    min-height: 11px;
  }
  .word-pop-breakdown .bk-kanji {
    font-family: var(--serif-jp); font-size: 16px;
    color: var(--ink); font-weight: 600;
  }
  .word-pop-breakdown .bk-eq {
    color: var(--ink-3); font-size: 11px;
  }
  .word-pop-breakdown .bk-meaning {
    color: var(--ink-2); font-style: italic;
  }
  .word-pop-breakdown .bk-sep {
    color: var(--ink-4);
  }

  /* Furigana on the big popover glyph. ruby spacing kept tight so the
     stacked kanji+reading still reads as one block. */
  .word-pop-glyph ruby {
    ruby-position: over;
    ruby-align: center;
  }
  .word-pop-glyph rt {
    font-family: var(--serif-jp);
    font-size: 0.32em;
    font-weight: 500;
    color: var(--ink-3);
    line-height: 1;
    letter-spacing: 0.02em;
  }

  body.no-particles .ja-particle {
    color: inherit !important;
    margin: 0 !important;
    font-weight: inherit !important;
  }

  /* ─── word chunks ───────────────────────────────────────────────────── */
  .word-chunk {
    display: inline-block;
    padding: 1px 4px;
    margin: 0 2px;
    border-radius: 4px;
    cursor: pointer;
    transition: background .12s, box-shadow .12s;
  }
  .word-chunk:hover {
    background: rgba(180,138,74,0.18);
    box-shadow: 0 0 0 1px rgba(141,102,48,0.15);
  }
  .word-chunk:active { background: rgba(180,138,74,0.28); }
  .word-chunk .wc-stem { }

  .word-pop-glyph, .word-pop-kana {
    cursor: pointer;
    user-select: none;
  }
  .word-pop-glyph:hover { color: var(--gold-dark); }
  .word-pop-kana:hover { color: var(--ink); }

  .word-pop-divider {
    height: 0; border-top: 1px dashed var(--gold-soft);
    margin: 16px 0 12px;
  }

  .word-pop-section-label {
    font-family: var(--serif); font-style: italic; font-size: 11px;
    color: var(--ink-3); letter-spacing: 0.06em;
    text-transform: lowercase;
    margin-bottom: 6px;
  }

  .word-pop-quote {
    font-family: var(--serif-jp); font-size: 15px;
    color: var(--ink); line-height: 1.55;
  }
  .word-pop-quote-en {
    font-family: var(--serif); font-style: italic; font-size: 12px;
    color: var(--ink-3); line-height: 1.4;
    margin-top: 4px;
  }
  .word-pop-quote .mark {
    background: linear-gradient(transparent 60%, var(--gold-soft) 60%);
    padding: 0 1px;
  }
  .word-pop-empty {
    font-family: var(--serif); font-style: italic; font-size: 12px;
    color: var(--ink-3); line-height: 1.5;
  }

  .word-pop-link {
    display: inline-flex; align-items: center; gap: 4px;
    margin-top: 14px;
    background: none; border: none; padding: 0; cursor: pointer;
    font-family: var(--serif); font-style: italic; font-size: 13px;
    color: var(--gold-dark);
    text-decoration: underline;
    text-decoration-color: var(--gold-soft);
    text-underline-offset: 3px;
    transition: color .12s;
  }
  .word-pop-link:hover { color: var(--ink); }

  .fade-enter { animation: fadeIn .28s ease both; }
  @keyframes fadeIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }

  .sidebar::-webkit-scrollbar { width: 6px; }
  .sidebar::-webkit-scrollbar-thumb { background: var(--gold-soft); border-radius: 3px; }

  /* ─── writing / particles page ──────────────────────────────────────── */
  /* Lesson card carries a --pc custom property (particle color) so the
     component themes itself per particle without per-instance CSS. The
     picker that used to live above the lesson now lives in the 3rd
     sidebar (#particles-sidebar). This is a v1 — design-expert:build
     will iterate the layout next. */

  /* lesson card (book frame) */
  .particle-lesson {
    --pc: var(--ink);
    position: relative;
    background: var(--paper-2);
    border: 2px solid var(--gold-soft);
    box-shadow:
      0 0 0 6px var(--paper-2),
      0 0 0 7px var(--gold-soft),
      0 8px 24px rgba(100,72,30,0.18);
    border-radius: 4px;
    padding: 32px clamp(20px, 4vw, 48px) 36px;
    margin-bottom: 48px;
  }
  .particle-lesson::before, .particle-lesson::after,
  .particle-lesson > .corner-tl, .particle-lesson > .corner-tr {
    content: ""; position: absolute; width: 14px; height: 14px;
    border: 2px solid var(--gold);
    background: var(--paper-2);
    border-radius: 50%;
  }
  .particle-lesson::before { top: -8px; left: -8px; }
  .particle-lesson::after  { bottom: -8px; right: -8px; }
  .particle-lesson > .corner-tl { top: -8px; right: -8px; }
  .particle-lesson > .corner-tr { bottom: -8px; left: -8px; }
  .particle-lesson .lesson-hd {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 28px;
    align-items: center;
    padding-bottom: 20px;
    border-bottom: 1px solid var(--gold-soft);
    margin-bottom: 26px;
  }
  @media (max-width: 600px) {
    .particle-lesson .lesson-hd { grid-template-columns: auto 1fr; gap: 16px; }
  }
  .particle-lesson .glyph-big {
    font-family: var(--serif-jp); font-weight: 700;
    font-size: clamp(72px, 10vw, 108px);
    color: var(--pc);
    line-height: 1; letter-spacing: 0;
  }
  .particle-lesson .lesson-titles .romaji {
    font-family: var(--serif); font-style: italic;
    font-size: 18px; color: var(--ink-3);
    letter-spacing: 0.04em;
  }
  .particle-lesson .lesson-titles .role {
    font-family: var(--serif-jp); font-weight: 600;
    font-size: clamp(20px, 2.4vw, 26px);
    color: var(--ink);
    margin: 2px 0 6px;
    letter-spacing: 0.02em;
  }
  .particle-lesson .lesson-titles .tagline {
    font-family: var(--serif); font-style: italic;
    font-size: 15px; color: var(--ink-2);
    line-height: 1.45;
  }
  .particle-lesson .pronounce-note {
    font-family: var(--serif); font-size: 12px;
    color: var(--ink-3); font-style: italic;
    background: rgba(180,138,74,0.10);
    border-radius: 4px;
    padding: 6px 10px;
    border-left: 2px solid var(--pc);
    margin-top: 10px;
    line-height: 1.45;
  }
  .particle-lesson .pronounce-note b { color: var(--pc); font-style: normal; font-weight: 600; }
  .particle-lesson .uses { display: flex; flex-direction: column; gap: 22px; }
  .particle-lesson .use {
    display: grid;
    grid-template-columns: 28px 1fr;
    gap: 10px 14px;
    align-items: start;
  }
  .particle-lesson .use-num {
    font-family: var(--serif); font-weight: 600;
    font-size: 14px;
    width: 26px; height: 26px;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: 50%;
    background: var(--pc);
    color: white;
    margin-top: 2px;
    flex: 0 0 auto;
  }
  .particle-lesson .use-rule {
    font-family: var(--serif); font-style: italic;
    font-size: 12px; color: var(--ink-3);
    letter-spacing: 0.06em;
    text-transform: lowercase;
    margin-bottom: 2px;
  }
  .particle-lesson .use-title {
    font-family: var(--serif-jp); font-weight: 600;
    font-size: 18px; color: var(--ink);
    letter-spacing: 0.02em;
  }
  .particle-lesson .use-pattern {
    margin-top: 8px;
    font-family: var(--sans-jp);
    font-size: 14px; color: var(--ink-2);
    background: var(--paper);
    border: 1px dashed var(--paper-edge);
    border-radius: 6px;
    padding: 6px 10px;
    display: inline-flex; align-items: center; gap: 4px; flex-wrap: wrap;
  }
  .particle-lesson .use-pattern .slot {
    background: var(--paper-2);
    padding: 1px 8px;
    border-radius: 4px;
    border: 1px solid rgba(141,102,48,0.22);
    font-family: var(--serif); font-style: italic;
    color: var(--ink-2);
    font-size: 13px;
  }
  .particle-lesson .use-pattern .pcc {
    color: var(--pc);
    font-weight: 600;
    font-family: var(--serif-jp);
    padding: 0 2px;
  }
  .particle-lesson .use-pattern .arrow { color: var(--ink-3); margin: 0 4px; }
  .particle-lesson .examples {
    margin-top: 12px;
    display: flex; flex-direction: column;
    gap: 10px;
  }
  .particle-lesson .example {
    padding: 10px 14px;
    border-left: 2px solid var(--pc);
    background: rgba(255,255,255,0.30);
    border-radius: 0 6px 6px 0;
  }
  .particle-lesson .example .ja {
    font-family: var(--serif-jp); font-size: 18px; color: var(--ink);
    line-height: 1.55;
    letter-spacing: 0.01em;
  }
  .particle-lesson .example .ja .pc {
    color: var(--pc);
    font-weight: 600;
    margin: 0 0.05em;
  }
  .particle-lesson .example .kana {
    font-family: var(--serif-jp); font-size: 12px; color: var(--ink-3);
    margin-top: 2px;
    letter-spacing: 0.02em;
  }
  .particle-lesson .example .en {
    font-family: var(--serif); font-style: italic; font-size: 13px;
    color: var(--ink-2);
    margin-top: 6px;
    line-height: 1.4;
  }
  .particle-lesson .compare {
    margin-top: 22px;
    padding: 14px 18px;
    background: rgba(180,138,74,0.10);
    border-radius: 6px;
    border-left: 3px solid var(--gold);
  }
  .particle-lesson .compare-hd {
    font-family: var(--serif); font-style: italic;
    color: var(--gold-dark); font-size: 13px;
    letter-spacing: 0.06em;
    margin-bottom: 6px;
  }
  .particle-lesson .compare-body {
    font-family: var(--serif); font-size: 14px;
    color: var(--ink-2); line-height: 1.55;
  }
  .particle-lesson .compare-body .ja {
    font-family: var(--serif-jp); color: var(--ink);
  }
  .particle-lesson .compare-body .pc { font-weight: 600; }

  /* ─── particle lessons ───────────────────────────────────────────────
     Catalog (cards grouped by block) + detail (editorial single-column
     reading layout). The shared --pc custom property carries a particle\'s
     color through chips and inline highlights. */

  /* Shared particle chip — colored kana pill. */
  .lp-chip {
    --pc: var(--ink-3);
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 28px; height: 28px;
    padding: 0 8px;
    background: color-mix(in srgb, var(--pc) 8%, var(--paper-2) 92%);
    border: 1px solid color-mix(in srgb, var(--pc) 40%, var(--paper-edge) 60%);
    border-radius: 999px;
    font-family: var(--serif-jp); font-weight: 700;
    font-size: 14px;
    color: var(--pc);
    line-height: 1;
  }
  .lp-chips { display: inline-flex; flex-wrap: wrap; gap: 6px; align-items: center; }
  .lp-chips-lg { margin-top: 12px; }
  .lp-chips-lg .lp-chip { min-width: 34px; height: 34px; font-size: 16px; padding: 0 10px; }

  /* Big-particle title row — the visual anchor of every catalog card and
     every lesson / article detail header. Each particle is rendered in
     its assigned color at large size; a small neutral middle-dot acts as
     the connector so there\'s no confusion between "main" particle and
     the connector (which used to be a literal と).
     Three sizes: sm (compact), md (catalog cards), lg (detail headers). */
  .lp-bigparticles {
    display: flex; align-items: baseline;
    flex-wrap: wrap;
    gap: 4px 8px;
    font-family: var(--serif-jp); font-weight: 700;
    line-height: 1;
    letter-spacing: 0.02em;
  }
  .lp-bigp { line-height: 1; }
  .lp-bigp-dot {
    color: var(--ink-4);
    font-weight: 400;
    font-size: 0.55em;
    transform: translateY(-0.18em);
    margin: 0 2px;
  }
  .lp-bigp-all { color: var(--gold-dark); }
  .lp-bigparticles-sm { font-size: 20px; }
  .lp-bigparticles-md { font-size: 28px; margin-bottom: 10px; }
  .lp-bigparticles-lg { font-size: clamp(44px, 6vw, 60px); margin: 2px 0 6px; }

  /* Catalog */
  .lp-summary {
    margin: 20px 0 32px;
    font-family: var(--serif); font-style: italic;
    font-size: 14px;
    color: var(--ink-3);
  }
  .lp-summary b { color: var(--gold-dark); font-style: normal; font-weight: 600; }
  .lp-group { margin-bottom: 40px; }
  .lp-group-hd { margin-bottom: 16px; }
  .lp-group-hd h3 {
    margin: 0;
    font-family: var(--serif); font-style: italic; font-weight: 500;
    font-size: 17px;
    color: var(--ink);
    letter-spacing: 0.04em;
  }
  .lp-group-hd p {
    margin: 4px 0 0;
    font-family: var(--serif); font-style: italic;
    font-size: 13px;
    color: var(--ink-3);
  }
  .lp-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 14px;
  }
  /* Card layout: [num] [body] [time] in a 3-col grid. Body holds the big
     particle row + the inverted-hierarchy title pair (JA larger, EN
     smaller). */
  .lp-card {
    position: relative;
    display: grid;
    grid-template-columns: 40px 1fr auto;
    gap: 12px;
    padding: 18px 16px 18px 14px;
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 10px;
    text-align: left;
    cursor: pointer;
    transition: transform .15s, border-color .15s, box-shadow .15s;
    overflow: hidden;
    font-family: var(--serif);
  }
  .lp-card.is-ready:hover {
    transform: translateY(-2px);
    border-color: var(--gold);
    box-shadow: 0 8px 20px rgba(100,72,30,0.10);
  }
  .lp-card.is-soon {
    border-style: dashed;
    opacity: 0.55;
    cursor: not-allowed;
    background: var(--paper);
  }
  .lp-card-num {
    font-family: var(--serif); font-style: italic;
    font-size: 22px;
    color: var(--gold-dark);
    line-height: 1;
    padding-top: 4px;
  }
  .lp-card.is-soon .lp-card-num { color: var(--ink-4); }
  .lp-card-body { min-width: 0; }
  /* Inverted hierarchy: JA descriptive larger than EN. */
  .lp-card-ja {
    font-family: var(--serif-jp); font-weight: 600;
    font-size: 17px;
    color: var(--ink);
    line-height: 1.3;
    margin-bottom: 4px;
    letter-spacing: 0.01em;
  }
  .lp-card-en {
    font-family: var(--serif); font-style: italic;
    font-size: 12px;
    color: var(--ink-3);
    line-height: 1.4;
  }
  .lp-card-time {
    align-self: flex-start;
    font-family: var(--serif); font-style: italic;
    font-size: 11px;
    color: var(--ink-4);
    padding-top: 6px;
    white-space: nowrap;
  }
  .lp-card-soon {
    position: absolute; bottom: 10px; right: 12px;
    font-family: var(--serif); font-style: italic;
    font-size: 11px;
    color: var(--ink-4);
    padding: 2px 8px;
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 999px;
  }
  /* Article cards have a longer summary that sits below title. */
  .lp-card-article { grid-template-columns: 40px 1fr auto; }
  .lp-card-summary {
    margin-top: 10px;
    font-family: var(--serif);
    font-size: 13px;
    color: var(--ink-2);
    line-height: 1.55;
  }

  /* Lesson / article detail header — inverted hierarchy: big particles
     first, then JA descriptive (medium), then EN descriptive (small). */
  .lp-detail-ja {
    font-family: var(--serif-jp); font-weight: 600;
    font-size: clamp(20px, 2.4vw, 26px);
    color: var(--ink);
    line-height: 1.25;
    letter-spacing: 0.02em;
    margin: 2px 0 4px;
  }
  .lp-detail-en {
    font-family: var(--serif); font-style: italic;
    font-size: 14px;
    color: var(--ink-3);
    line-height: 1.45;
  }

  /* Article body — editorial prose. Reuses lesson typographic feel. */
  .lp-article-body {
    max-width: 64ch;
    margin: 8px 0 28px;
    font-family: var(--serif);
    font-size: 16px;
    line-height: 1.7;
    color: var(--ink-2);
  }
  .lp-article-body h3 {
    margin: 28px 0 10px;
    font-family: var(--serif-jp); font-weight: 600;
    font-size: 18px;
    color: var(--ink);
    letter-spacing: 0.02em;
  }
  .lp-article-body p {
    margin: 0 0 14px;
  }
  .lp-article-body b { color: var(--ink); font-weight: 600; }
  .lp-article-body i { color: var(--ink); }
  .lp-article-body .ja {
    font-family: var(--serif-jp); font-weight: 500;
    color: var(--ink);
  }
  .lp-article-body .pc {
    font-family: var(--serif-jp); font-weight: 700;
    padding: 0 1px;
  }
  .article-list {
    margin: 8px 0 16px;
    padding-left: 22px;
    line-height: 1.65;
  }
  .article-list li { margin-bottom: 6px; }

  /* Detail — editorial reading column */
  .lp-back-row {
    display: flex; align-items: center; justify-content: space-between;
    margin: -8px 0 16px;
    gap: 12px;
  }
  .lp-back {
    background: transparent; border: none;
    font-family: var(--serif); font-style: italic;
    font-size: 13px;
    color: var(--ink-3);
    cursor: pointer;
    padding: 4px 0;
    transition: color .15s;
  }
  .lp-back:hover { color: var(--gold-dark); }
  .lp-progress {
    font-family: var(--serif); font-style: italic;
    font-size: 12px;
    color: var(--ink-4);
  }
  .lp-progress-sub { color: var(--ink-4); opacity: 0.7; }
  .lp-head { max-width: 720px; }
  .lp-head-meta {
    font-family: var(--serif); font-style: italic; font-size: 14px;
    color: var(--ink-3); font-weight: 400; margin-left: 6px;
  }

  .lp-intro {
    max-width: 64ch;
    margin: 0 0 36px;
    font-family: var(--serif);
    font-size: 16px;
    line-height: 1.7;
    color: var(--ink-2);
  }
  .lp-intro b { color: var(--ink); font-weight: 600; }
  .lp-intro i { color: var(--ink); }

  .lp-steps {
    display: flex; flex-direction: column;
    gap: 20px;
    max-width: 720px;
  }
  .lp-step {
    display: grid;
    grid-template-columns: 36px 1fr;
    gap: 16px;
    padding: 22px 24px;
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 8px;
  }
  .lp-step-num {
    font-family: var(--serif); font-style: italic;
    font-size: 18px;
    color: var(--gold-dark);
    line-height: 1;
    padding-top: 2px;
  }
  .lp-step-body { min-width: 0; }
  .lp-step-title {
    font-family: var(--serif-jp); font-weight: 600;
    font-size: 16px;
    color: var(--ink);
    margin-bottom: 8px;
    letter-spacing: 0.02em;
  }
  .lp-step-prose {
    font-family: var(--serif);
    font-size: 15px;
    line-height: 1.65;
    color: var(--ink-2);
  }
  .lp-step-prose b { color: var(--ink); font-weight: 600; }
  .lp-step-prose i { color: var(--ink); }
  .lp-step-prose .ja {
    font-family: var(--serif-jp); font-weight: 500;
    color: var(--ink);
  }
  .lp-step-prose .pc { font-weight: 700; font-family: var(--serif-jp); }

  /* Pattern row */
  .lp-pattern {
    display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap;
    margin: 4px 0 14px;
    padding: 10px 14px;
    background: var(--paper);
    border: 1px dashed var(--paper-edge);
    border-radius: 6px;
    font-family: var(--sans-jp);
    font-size: 15px;
  }
  .lp-pat-slot {
    padding: 2px 10px;
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 4px;
    font-family: var(--serif); font-style: italic;
    color: var(--ink-2);
    font-size: 13px;
  }
  .lp-pat-particle {
    font-family: var(--serif-jp); font-weight: 700;
    font-size: 18px;
    padding: 0 4px;
  }

  /* Examples */
  .lp-examples { display: flex; flex-direction: column; gap: 12px; margin-top: 6px; }
  .lp-example {
    padding: 12px 16px;
    border-left: 2px solid var(--gold-soft);
    background: rgba(255,255,255,0.30);
    border-radius: 0 6px 6px 0;
  }
  .lp-ex-ja {
    font-family: var(--serif-jp); font-size: 18px;
    color: var(--ink); line-height: 1.55;
  }
  .lp-ex-ja .pc { font-weight: 700; }
  .lp-ex-kana {
    font-family: var(--serif-jp); font-size: 12px;
    color: var(--ink-3);
    margin-top: 2px;
  }
  .lp-ex-en {
    font-family: var(--serif); font-style: italic;
    font-size: 13px;
    color: var(--ink-2);
    margin-top: 6px;
    line-height: 1.45;
  }
  .lp-ex-note {
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px dashed var(--paper-edge);
    font-family: var(--serif); font-style: italic;
    font-size: 12px;
    color: var(--ink-3);
    line-height: 1.55;
  }

  /* Contrast */
  .lp-contrast {
    margin-top: 10px;
    padding: 16px 20px;
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    border-radius: 8px;
    display: flex; flex-direction: column;
    gap: 12px;
  }
  .lp-contrast-row { }
  .lp-contrast-ja {
    font-family: var(--serif-jp); font-size: 17px;
    color: var(--ink); line-height: 1.5;
  }
  .lp-contrast-ja .pc { font-weight: 700; }
  .lp-contrast-en {
    font-family: var(--serif); font-style: italic;
    font-size: 13px;
    color: var(--ink-2);
    margin-top: 4px;
  }
  .lp-contrast-vs {
    align-self: center;
    font-family: var(--serif); font-style: italic;
    font-size: 16px;
    color: var(--gold);
  }

  /* Mistake */
  .lp-step-mistake {
    background: rgba(192,138,138,0.08);
    border-color: rgba(192,138,138,0.35);
  }
  .lp-mistake-title {
    color: var(--rose);
  }

  /* Check */
  .lp-step-check { background: var(--paper); }
  .lp-check { }
  .lp-check-hd {
    font-family: var(--serif); font-style: italic;
    font-size: 12px;
    color: var(--ink-3);
    letter-spacing: 0.06em;
    text-transform: lowercase;
    margin-bottom: 8px;
  }
  .lp-check-q-ja {
    font-family: var(--serif-jp);
    font-size: 18px;
    color: var(--ink);
    line-height: 1.5;
  }
  .lp-check-q-en {
    font-family: var(--serif); font-style: italic;
    font-size: 13px;
    color: var(--ink-3);
    margin: 4px 0 14px;
  }
  .lp-check-options {
    display: flex; flex-wrap: wrap; gap: 8px;
  }
  .lp-check-opt {
    cursor: pointer;
    background: var(--paper-2);
    border: 1px solid rgba(141,102,48,0.25);
    border-radius: 8px;
    padding: 8px 16px;
    font-family: var(--serif-jp); font-weight: 600;
    font-size: 16px;
    color: var(--ink);
    transition: all .12s;
  }
  .lp-check-opt:hover:not(:disabled) {
    background: var(--paper);
    border-color: var(--gold);
    transform: translateY(-1px);
  }
  .lp-check-opt.correct {
    background: rgba(122,143,110,0.20);
    border-color: var(--sage);
    color: var(--sage);
  }
  .lp-check-opt.wrong {
    background: rgba(192,138,138,0.20);
    border-color: var(--rose);
    color: var(--rose);
  }
  .lp-check-opt.dim { opacity: 0.4; }
  .lp-check-feedback {
    margin-top: 14px;
    padding: 12px 14px;
    border-radius: 6px;
    border: 1px solid var(--paper-edge);
    background: var(--paper-2);
  }
  .lp-check.is-correct .lp-check-feedback { border-color: var(--sage); background: rgba(122,143,110,0.08); }
  .lp-check.is-wrong   .lp-check-feedback { border-color: var(--rose); background: rgba(192,138,138,0.08); }
  .lp-check-verdict {
    font-family: var(--serif); font-weight: 600;
    font-size: 13px;
    margin-bottom: 4px;
  }
  .lp-check.is-correct .lp-check-verdict { color: var(--sage); }
  .lp-check.is-wrong   .lp-check-verdict { color: var(--rose); }
  .lp-check-explain {
    font-family: var(--serif); font-style: italic;
    font-size: 13px; line-height: 1.55;
    color: var(--ink-2);
  }
  .lp-check-explain b { color: var(--ink); font-style: normal; font-weight: 600; }
  .lp-check-explain .ja { font-family: var(--serif-jp); color: var(--ink); }
  .lp-check-explain .pc { font-weight: 700; }

  /* Takeaways box */
  .lp-takeaways {
    margin: 36px 0 28px;
    max-width: 720px;
    padding: 22px 26px;
    background: rgba(180,138,74,0.08);
    border-left: 3px solid var(--gold);
    border-radius: 0 8px 8px 0;
  }
  .lp-takeaways-hd {
    font-family: var(--serif); font-style: italic;
    color: var(--gold-dark); font-size: 13px;
    letter-spacing: 0.06em;
    margin-bottom: 10px;
  }
  .lp-takeaways ul {
    margin: 0; padding-left: 18px;
    font-family: var(--serif);
    font-size: 14px;
    line-height: 1.7;
    color: var(--ink-2);
  }
  .lp-takeaways li { margin-bottom: 4px; }

  /* Footer prev/next nav */
  .lp-foot-nav {
    display: flex; align-items: stretch; justify-content: space-between;
    gap: 12px;
    margin: 28px 0 16px;
    max-width: 720px;
  }
  .lp-foot-prev, .lp-foot-next {
    display: flex; flex-direction: column; align-items: flex-start;
    gap: 4px;
    padding: 12px 18px;
    min-width: 200px;
  }
  .lp-foot-next { align-items: flex-end; text-align: right; }
  .lp-foot-dir {
    font-family: var(--serif); font-style: italic;
    font-size: 12px;
    opacity: 0.8;
  }
  .lp-foot-title {
    font-family: var(--serif-jp); font-weight: 600;
    font-size: 14px;
  }

  @media (max-width: 700px) {
    .lp-step { padding: 18px 16px; }
    .lp-foot-nav { flex-direction: column; }
    .lp-foot-prev, .lp-foot-next { min-width: 0; align-items: flex-start; text-align: left; }
  }

  /* ─── particle quiz config screen ───────────────────────────────────── */
  .qcfg-section {
    margin: 28px 0;
    padding-bottom: 24px;
    border-bottom: 1px solid var(--paper-edge);
  }
  .qcfg-section:last-of-type { border-bottom: none; padding-bottom: 0; }
  .qcfg-hd {
    display: flex; align-items: baseline; flex-wrap: wrap;
    gap: 8px 16px;
    margin-bottom: 14px;
  }
  .qcfg-hd h3 {
    margin: 0;
    font-family: var(--serif-jp); font-weight: 600;
    font-size: 16px;
    color: var(--ink);
    letter-spacing: 0.02em;
  }
  .qcfg-hd p {
    margin: 0;
    flex: 1; min-width: 200px;
    font-family: var(--serif); font-style: italic;
    font-size: 13px; color: var(--ink-3);
  }
  .qcfg-bulk { display: flex; gap: 6px; }
  .qcfg-bulk-btn {
    background: transparent; border: none;
    font-family: var(--serif); font-style: italic;
    font-size: 12px; color: var(--ink-3);
    cursor: pointer; padding: 2px 6px;
    border-radius: 4px;
    transition: color .15s, background .15s;
  }
  .qcfg-bulk-btn:hover { color: var(--gold-dark); background: rgba(180,138,74,0.10); }
  .qcfg-chips {
    display: flex; flex-wrap: wrap; gap: 8px;
  }
  .qcfg-chips-particle { gap: 10px; }
  .qcfg-chip {
    --pc: var(--ink-3);
    position: relative;
    display: inline-flex; align-items: center; gap: 6px;
    padding: 8px 14px;
    background: var(--paper-2);
    border: 1px solid rgba(141,102,48,0.22);
    border-radius: 8px;
    font-family: var(--serif); font-size: 13px;
    color: var(--ink-3);
    cursor: pointer;
    transition: all .12s;
  }
  .qcfg-chip:hover:not(:disabled) {
    border-color: var(--pc);
    transform: translateY(-1px);
    box-shadow: 0 4px 12px rgba(100,72,30,0.08);
  }
  .qcfg-chip.on {
    background: var(--paper);
    border-color: var(--pc);
    color: var(--ink);
    box-shadow: 0 0 0 1px var(--pc), 0 2px 8px rgba(100,72,30,0.10);
  }
  .qcfg-chip.empty {
    opacity: 0.4;
    cursor: not-allowed;
  }
  .qcfg-chip .qcfg-ja {
    font-family: var(--serif-jp); font-weight: 700;
    font-size: 18px;
    color: var(--pc);
  }
  .qcfg-chip.on .qcfg-ja { color: var(--pc); }
  .qcfg-chip .qcfg-romaji {
    font-family: var(--serif); font-style: italic;
    font-size: 12px;
    color: var(--ink-3);
  }
  .qcfg-chip .qcfg-count {
    margin-left: 4px;
    padding: 2px 8px;
    background: var(--paper-edge);
    border-radius: 999px;
    font-family: var(--serif); font-size: 11px;
    color: var(--ink-3);
    line-height: 1;
    min-width: 18px; text-align: center;
  }
  .qcfg-chip.on .qcfg-count {
    background: var(--pc);
    color: white;
  }
  .qcfg-chip.qcfg-level {
    --pc: var(--gold-dark);
  }
  .qcfg-chip.qcfg-size {
    --pc: var(--gold-dark);
    padding: 8px 18px;
    font-family: var(--serif-jp); font-weight: 600; font-size: 15px;
    color: var(--ink-3);
  }
  .qcfg-chip.qcfg-size.on { color: var(--ink); }
  .qcfg-start-row {
    margin-top: 32px;
    display: flex; align-items: center; justify-content: space-between;
    gap: 16px; flex-wrap: wrap;
    padding: 18px 22px;
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 10px;
  }
  .qcfg-summary {
    font-family: var(--serif); font-style: italic;
    font-size: 14px;
    color: var(--ink-2);
  }
  .qcfg-summary b {
    font-style: normal; font-weight: 600;
    color: var(--gold-dark);
  }
  .qcfg-start {
    font-family: var(--serif-jp); font-weight: 600;
    font-size: 15px;
    padding: 10px 22px;
  }

  /* particle quiz — v1, will be refined by /design-expert:build */
  .quiz-progress {
    height: 4px; background: var(--paper-edge);
    border-radius: 2px;
    overflow: hidden;
    margin-bottom: 20px;
  }
  .quiz-progress > div {
    height: 100%; background: var(--gold);
    transition: width .3s ease;
  }
  .quiz-card {
    --pc: var(--ink);
    background: var(--paper-2);
    border: 1px solid rgba(141,102,48,0.22);
    border-radius: 10px;
    padding: 28px 32px;
    box-shadow: 0 1px 0 rgba(255,255,255,0.4) inset, 0 4px 14px var(--paper-shadow);
  }
  .qz-prompt-eyebrow {
    font-family: var(--serif); font-style: italic;
    color: var(--ink-3); font-size: 12px;
    letter-spacing: 0.06em;
    margin-bottom: 12px;
  }
  .qz-sentence {
    font-family: var(--serif-jp); font-size: clamp(20px, 2.6vw, 26px);
    color: var(--ink); line-height: 1.6;
    margin-bottom: 8px;
    display: flex; align-items: baseline; flex-wrap: wrap;
    gap: 0;
  }
  .qz-blank {
    --pc: var(--gold);
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 1.6em; height: 1.4em;
    border-bottom: 2px dashed var(--gold);
    margin: 0 0.18em;
    color: var(--gold-dark);
    font-weight: 600;
    transition: border-color .15s, color .15s;
  }
  .qz-blank.filled {
    border-bottom-style: solid;
    border-bottom-color: var(--pc);
    color: var(--pc);
  }
  .qz-en {
    font-family: var(--serif); font-style: italic; font-size: 14px;
    color: var(--ink-3); line-height: 1.4;
    margin-top: 4px;
  }
  .qz-choices {
    margin-top: 22px;
    display: flex; flex-wrap: wrap; gap: 8px;
  }
  .qz-choice {
    --pc: var(--ink);
    cursor: pointer;
    background: var(--paper);
    border: 1px solid rgba(141,102,48,0.22);
    border-radius: 8px;
    padding: 10px 18px;
    font-family: var(--serif-jp); font-weight: 600;
    font-size: 20px;
    color: var(--pc);
    line-height: 1;
    transition: all .12s;
    display: inline-flex; align-items: center; gap: 8px;
  }
  .qz-choice:hover {
    background: var(--paper-2);
    border-color: var(--pc);
    transform: translateY(-1px);
    box-shadow: 0 4px 12px rgba(100,72,30,0.10);
  }
  .qz-choice.correct {
    background: rgba(122,143,110,0.20);
    border-color: var(--sage);
    color: var(--sage);
  }
  .qz-choice.wrong {
    background: rgba(192,138,138,0.20);
    border-color: var(--rose);
    color: var(--rose);
  }
  .qz-choice.dim { opacity: 0.4; }
  .qz-choice:disabled { cursor: default; }
  .qz-feedback {
    margin-top: 18px;
    padding: 14px 16px;
    border-radius: 8px;
    font-family: var(--serif);
    font-size: 14px;
    color: var(--ink-2);
    line-height: 1.5;
    background: var(--paper);
    border: 1px solid var(--paper-edge);
  }
  .qz-feedback.ok { border-color: var(--sage); background: rgba(122,143,110,0.10); }
  .qz-feedback.no { border-color: var(--rose); background: rgba(192,138,138,0.10); }
  .qz-feedback .verdict {
    font-family: var(--serif); font-weight: 600;
    font-size: 14px;
    margin-bottom: 6px;
    letter-spacing: 0.04em;
  }
  .qz-feedback.ok .verdict { color: var(--sage); }
  .qz-feedback.no .verdict { color: var(--rose); }
  .qz-feedback .explain { font-style: italic; color: var(--ink-2); }
  .qz-feedback .explain b { color: var(--ink); font-style: normal; font-weight: 600; }
  .qz-feedback .en-line {
    margin-top: 8px;
    font-family: var(--serif); font-style: italic;
    color: var(--ink-3); font-size: 13px;
  }
  .qz-actions {
    display: flex; align-items: center; justify-content: flex-end;
    gap: 10px;
    margin-top: 18px;
  }
  .qz-done { text-align: center; padding: 30px 20px; }
  .qz-done .num {
    font-family: var(--serif-jp); font-weight: 700;
    font-size: 56px; color: var(--gold-dark);
    line-height: 1;
    margin-bottom: 10px;
  }
  .qz-done .num small {
    font-family: var(--serif); font-style: italic;
    font-size: 22px; color: var(--ink-3);
    font-weight: 400;
  }
  .qz-done .msg {
    font-family: var(--serif); font-style: italic;
    font-size: 16px; color: var(--ink-2);
    margin-bottom: 18px;
  }
  .btn {
    display: inline-flex; align-items: center; gap: 8px;
    padding: 8px 18px; border-radius: 8px;
    background: var(--paper-2); color: var(--ink);
    border: 1px solid rgba(141,102,48,0.25);
    font-family: var(--serif); font-size: 14px;
    cursor: pointer; transition: all .15s;
  }
  .btn:hover { background: var(--gold-soft); border-color: var(--gold); }
  .btn.primary { background: var(--gold); color: white; border-color: var(--gold-dark); }
  .btn.primary:hover { background: var(--gold-dark); }

  /* ─── writing / kana page ───────────────────────────────────────────── */
  .kana-controls {
    display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
    margin-bottom: 14px;
  }

  /* ─── Font dropdown — reusable across kana page, flashcard, settings ─
     A pill trigger that shows the current font name + a tiny preview,
     opening a menu where each option previews itself in its own font.
     The active option gets a small check, the hovered one warms gold. */
  .font-dropdown {
    position: relative;
    display: inline-block;
    font-family: var(--serif);
  }
  .font-dropdown-trigger {
    display: inline-flex; align-items: center; gap: 8px;
    padding: 5px 10px 5px 12px;
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    border-radius: 6px;
    cursor: pointer;
    font-family: var(--serif);
    font-size: 13px;
    color: var(--ink-2);
    line-height: 1;
    transition: border-color .12s, background .12s;
  }
  .font-dropdown-trigger:hover {
    border-color: var(--gold-soft);
    background: var(--paper-2);
  }
  .font-dropdown-trigger[aria-expanded="true"] {
    border-color: var(--gold-dark);
    background: var(--paper-2);
  }
  .font-dropdown-trigger-label {
    line-height: 1;
    color: var(--ink);
    /* font-family is set inline per-render so the trigger previews
       the current font through the label\'s own typography. */
  }
  .font-dropdown-caret {
    font-size: 10px;
    color: var(--ink-3);
    line-height: 1;
    transition: transform .15s;
  }
  .font-dropdown-trigger[aria-expanded="true"] .font-dropdown-caret {
    transform: rotate(180deg);
  }
  .font-dropdown-menu {
    position: absolute;
    top: calc(100% + 4px); left: 0;
    z-index: 60;
    margin: 0; padding: 4px;
    list-style: none;
    min-width: 100%;
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(100,72,30,0.18);
    display: flex; flex-direction: column;
    gap: 2px;
  }
  /* Native [hidden] { display: none } gets overridden by the
     `.font-dropdown-menu { display: flex }` rule above because the
     class selector wins specificity. Restore the close behavior with
     an attribute selector at equal-or-higher specificity. */
  .font-dropdown-menu[hidden] { display: none; }
  .font-dropdown-option {
    display: grid;
    grid-template-columns: 1fr 14px;
    align-items: center;
    gap: 10px;
    padding: 6px 12px;
    background: transparent;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    text-align: left;
    width: 100%;
    transition: background .12s, color .12s;
    color: var(--ink-2);
  }
  .font-dropdown-option:hover {
    background: var(--paper-2);
    color: var(--ink);
  }
  .font-dropdown-option.is-active {
    background: rgba(180,138,74,0.12);
    color: var(--ink);
  }
  .font-dropdown-opt-label {
    font-size: 14px;
    line-height: 1.2;
    color: var(--ink);
    white-space: nowrap;
    /* font-family is set inline per-option so each row previews itself
       through its own label rendering. */
  }
  .font-dropdown-opt-check {
    font-size: 12px;
    color: var(--gold-dark);
    text-align: center;
    line-height: 1;
  }
  /* Variant — when used inside a tight inline row (kana page controls,
     glyph picker on the card). Slightly more compact trigger. */
  .font-dropdown.is-compact .font-dropdown-trigger {
    padding: 4px 8px 4px 10px;
    font-size: 12px;
  }

  /* ─── Inline size inputs (live inside .kana-controls) ────────────────
     Each script gets its own direct size input. The label is the kana
     glyph itself (あ for hiragana, ア for katakana) styled in the
     current font, so the control reads as "set this script\'s size."
     A small ↺ reset glyph rotates on hover. */
  .kana-ctl-sep {
    display: inline-block;
    width: 1px; height: 18px;
    background: var(--paper-edge);
    margin: 0 4px;
    align-self: center;
  }
  .kana-size-mini {
    display: inline-flex; align-items: center;
    gap: 4px;
    padding: 3px 7px 3px 8px;
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    border-radius: 6px;
    transition: border-color .12s;
  }
  .kana-size-mini:focus-within { border-color: var(--gold); }
  .kana-size-mini-glyph {
    font-size: 14px;
    color: var(--ink-3);
    line-height: 1;
    min-width: 0.9em; text-align: center;
  }
  .kana-size-mini input {
    width: 32px;
    background: transparent;
    border: none;
    font-family: var(--serif);
    font-size: 13px;
    color: var(--ink);
    text-align: center;
    outline: none;
    -moz-appearance: textfield;
  }
  .kana-size-mini input::-webkit-outer-spin-button,
  .kana-size-mini input::-webkit-inner-spin-button {
    -webkit-appearance: none; margin: 0;
  }
  /* Custom stepper — two stacked chevron buttons styled to match the
     paper / gold palette. Default browser number-input arrows are clunky
     and platform-dependent; ours stay tidy and on-brand. */
  .kana-size-stepper {
    display: inline-flex; flex-direction: column;
    gap: 1px;
    margin-left: 2px;
    line-height: 1;
  }
  .kana-size-step {
    background: transparent;
    border: none;
    padding: 0;
    width: 14px; height: 9px;
    font-size: 8px;
    color: var(--ink-4);
    cursor: pointer;
    line-height: 1;
    border-radius: 2px;
    transition: color .12s, background .12s;
    display: inline-flex; align-items: center; justify-content: center;
    user-select: none;
  }
  .kana-size-step:hover {
    color: var(--gold-dark);
    background: rgba(180,138,74,0.12);
  }
  .kana-size-step:active {
    color: var(--ink);
    background: var(--gold-soft);
  }
  .kana-size-reset-mini {
    background: transparent;
    border: none;
    font-family: var(--serif-jp);
    font-size: 15px;
    color: var(--ink-3);
    cursor: pointer;
    padding: 2px 6px;
    line-height: 1;
    border-radius: 4px;
    transition: color .12s, transform .25s;
  }
  .kana-size-reset-mini:hover {
    color: var(--gold-dark);
    transform: rotate(-180deg);
  }
  .kana-card {
    background: var(--paper-2);
    border: 1px solid rgba(141,102,48,0.22);
    border-radius: 12px;
    padding: 20px 24px;
    margin-bottom: 28px;
    box-shadow: 0 1px 0 rgba(255,255,255,0.4) inset, 0 4px 14px var(--paper-shadow);
  }
  .kana-section-h {
    font-family: var(--serif-jp); font-size: 18px; font-weight: 600;
    color: var(--ink-2);
    margin: 0 0 12px;
    letter-spacing: 0.04em;
  }
  .kana-grid { display: flex; flex-direction: column; gap: 16px; }
  .kana-row {
    display: grid;
    grid-template-columns: 28px repeat(5, 1fr);
    gap: 6px;
    align-items: stretch;
    padding-bottom: 12px;
    border-bottom: 1px dashed rgba(141,102,48,0.18);
  }
  .kana-row:last-child { border-bottom: none; padding-bottom: 0; }
  .kana-row.kana-header { padding-bottom: 6px; border-bottom: 1px dashed var(--paper-edge); margin-bottom: 4px; }
  .kana-col-label {
    font-family: var(--serif); font-style: italic; font-size: 13px;
    color: var(--ink-3); text-align: center;
  }
  .kana-cell-label {
    font-family: var(--serif); font-style: italic; font-size: 13px;
    color: var(--ink-3); text-align: center; align-self: center;
  }
  .kana-cell {
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-template-rows: auto auto;
    column-gap: 6px; row-gap: 2px;
    align-items: center; justify-items: center;
    padding: 10px 6px;
    border-radius: 6px;
    border-right: 1px dashed rgba(141,102,48,0.18);
    min-height: 64px;
    transition: background .12s;
    line-height: 1.05;
  }
  .kana-row > .kana-cell:last-child,
  .kana-row > .kana-cell:nth-child(6) { border-right: none; }
  .kana-row.kana-header > .kana-cell { border-right: none; }
  .kana-cell.kana-only-one { grid-template-columns: 1fr; }
  .kana-cell .kana-r { grid-column: 1 / -1; }
  .kana-cell:hover:not(.kana-empty) { background: rgba(180,138,74,0.12); }
  .kana-empty {
    background: repeating-linear-gradient(135deg, transparent 0 4px, var(--paper-edge) 4px 5px);
    opacity: 0.4; min-height: 64px;
  }
  .kana-h { color: var(--ink); line-height: 1; transition: font-size .15s; }
  .kana-k { color: var(--ink-2); line-height: 1; transition: font-size .15s; }
  .kana-r {
    font-family: var(--serif); font-style: italic; font-size: 11px;
    color: var(--ink-3); margin-top: 2px; letter-spacing: 0.04em;
  }

  /* numbers reference */
  .num-twocol {
    display: grid; grid-template-columns: 1fr 1fr; gap: 18px; margin-bottom: 18px;
  }
  @media (max-width: 820px) { .num-twocol { grid-template-columns: 1fr; } }

  .num-card {
    background: var(--paper-2);
    border: 1px solid rgba(141,102,48,0.22);
    border-radius: 10px;
    padding: 16px 20px;
    box-shadow: 0 1px 0 rgba(255,255,255,0.4) inset, 0 4px 14px var(--paper-shadow);
    margin-bottom: 18px;
  }
  .num-card-hd {
    display: flex; align-items: center; gap: 10px;
    padding-bottom: 10px; border-bottom: 1px dashed var(--paper-edge); margin-bottom: 12px;
  }
  .num-badge {
    display: inline-flex; align-items: center; justify-content: center;
    width: 24px; height: 24px; border-radius: 50%;
    background: var(--gold-dark); color: white;
    font-family: var(--serif); font-weight: 600; font-size: 13px;
    flex: 0 0 auto;
  }
  .num-card-title {
    font-size: 18px; font-weight: 600; color: var(--ink); letter-spacing: 0.04em;
  }
  .num-card-body { display: grid; grid-template-columns: 1fr; gap: 18px; }
  .num-card.num-card-wide .num-card-body {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr);
  }
  @media (max-width: 820px) {
    .num-card .num-card-body { grid-template-columns: 1fr !important; }
  }
  .num-card-aside { border-left: 1px dashed var(--paper-edge); padding-left: 18px; }
  @media (max-width: 820px) {
    .num-card-aside { border-left: none; padding-left: 0; border-top: 1px dashed var(--paper-edge); padding-top: 14px; }
  }

  .num-table { width: 100%; border-collapse: collapse; }
  .num-table td {
    padding: 6px 4px; vertical-align: middle;
    border-bottom: 1px dashed rgba(141,102,48,0.15);
  }
  .num-table tr:last-child td { border-bottom: none; }
  .num-table-v {
    font-family: var(--serif); font-variant-numeric: tabular-nums;
    color: var(--ink-3); width: 56px; font-size: 14px;
    text-align: right; padding-right: 14px;
  }
  .num-table-k {
    font-family: var(--serif-jp); font-size: 22px; color: var(--ink);
    font-weight: 500; letter-spacing: 0.04em; width: 78px;
  }
  .num-table-r {
    font-family: var(--serif-jp); font-size: 15px; color: var(--ink-2);
  }
  .num-table-r.is-irregular-r { color: var(--rose, #c43a4a); font-weight: 600; }

  .num-callout {
    border: 1.5px solid var(--gold); border-radius: 12px;
    padding: 12px 14px; text-align: center; background: var(--paper);
  }
  .num-callout-hd { font-size: 18px; margin-bottom: 4px; }
  .num-callout-body {
    display: flex; flex-direction: column; gap: 4px;
    font-family: var(--serif); font-size: 14px; color: var(--ink-2);
  }
  .num-callout-jp { font-size: 20px; color: var(--gold-dark); font-weight: 600; }

  .num-formula-area { display: flex; flex-direction: column; gap: 12px; }
  .num-formula {
    text-align: center; padding: 10px 12px;
    background: var(--paper); border: 1px solid var(--paper-edge); border-radius: 6px;
    font-family: var(--serif); color: var(--ink-2); font-size: 14px;
  }
  .num-pill {
    display: inline-block; padding: 2px 10px; border-radius: 4px;
    margin: 0 4px; font-size: 13px; font-weight: 500;
  }
  .num-pill.ten { background: #d9e3f0; color: #2a5b94; }
  .num-plus { color: var(--ink-3); margin: 0 4px; font-size: 14px; }
  .num-construct {
    display: flex; align-items: center; justify-content: center;
    gap: 6px; flex-wrap: wrap;
  }
  .num-construct-pill {
    padding: 6px 12px; border-radius: 6px; text-align: center;
    border: 1px solid currentColor;
    font-family: var(--serif); font-size: 13px; line-height: 1.2;
  }
  .num-construct-pill.tens   { color: #2a5b94; background: rgba(42,91,148,0.06); }
  .num-construct-pill.ones   { color: #c43a4a; background: rgba(196,58,74,0.06); }
  .num-construct-pill.result { color: var(--ink-2); background: var(--paper); border-style: dashed; }
  .num-construct-pill .hint { font-family: var(--serif-jp); font-size: 10px; color: var(--ink-3); }
  .num-arrow { color: var(--ink-3); font-size: 16px; }

  .num-divider {
    text-align: center; border-bottom: 1px dashed var(--paper-edge);
    line-height: 0; margin: 4px 0;
  }
  .num-divider span {
    background: var(--paper-2); padding: 0 10px;
    font-family: var(--serif); font-style: italic; font-size: 12px;
    color: var(--ink-3); letter-spacing: 0.04em;
  }

  .num-examples {
    display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px;
  }
  @media (max-width: 540px) { .num-examples { grid-template-columns: repeat(2, 1fr); } }
  .num-example {
    text-align: center; padding: 8px 4px; border-radius: 6px;
    background: var(--paper); border: 1px solid var(--paper-edge);
    display: flex; flex-direction: column; align-items: center; gap: 2px;
  }
  .num-example-v {
    font-family: var(--serif); font-weight: 600; font-size: 17px;
    color: var(--ink); font-variant-numeric: tabular-nums;
  }
  .num-example-k {
    font-family: var(--serif-jp); font-size: 16px; color: var(--ink);
    letter-spacing: 0.04em;
  }
  .num-example-k .as-tens { color: #2a5b94; }
  .num-example-k .as-ones { color: #c43a4a; }
  .num-example-arrow { color: var(--ink-3); font-size: 11px; }
  .num-example-r {
    font-family: var(--serif-jp); font-size: 12px; color: var(--ink-2);
  }

  .num-irregular-callout {
    border: 1.5px solid var(--gold-soft);
    background: rgba(217,193,145,0.18);
    border-radius: 8px; padding: 12px 14px; margin-bottom: 14px;
  }
  .num-irregular-hd {
    font-family: var(--serif); font-style: italic; font-size: 13px;
    color: var(--gold-dark); margin-bottom: 6px; font-weight: 600;
  }
  .num-irregular-body {
    color: var(--rose, #c43a4a);
    font-size: 15px; font-weight: 600; line-height: 1.6;
  }

  .num-bigex {
    display: flex; flex-direction: column; gap: 4px;
    padding: 12px 14px; border: 1px dashed var(--paper-edge); border-radius: 8px;
  }
  .num-bigex-line {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap; justify-content: center;
  }
  .num-bigex-v {
    font-family: var(--serif); font-weight: 600; font-size: 18px;
    color: var(--ink); font-variant-numeric: tabular-nums;
  }
  .num-bigex-k { font-family: var(--serif-jp); font-size: 20px; color: var(--ink); }
  .num-bigex-k .thousands { color: #6a3a92; }
  .num-bigex-k .hundreds  { color: var(--gold-dark); }
  .num-bigex-k .tens      { color: #2a5b94; }
  .num-bigex-k .ones      { color: #c43a4a; }
  .num-bigex-r { font-family: var(--serif-jp); font-size: 14px; color: var(--ink-2); }
  .dash { color: var(--ink-3); }
  .num-bigex-parts {
    text-align: center;
    font-family: var(--serif); font-style: italic; font-size: 11px;
    color: var(--ink-3); margin-top: -2px;
  }
  .num-bigex-parts .thousands { color: #6a3a92; }
  .num-bigex-parts .hundreds  { color: var(--gold-dark); }
  .num-bigex-parts .tens      { color: #2a5b94; }
  .num-bigex-parts .ones      { color: #c43a4a; }

  .kana-note {
    padding: 14px 18px;
    border-left: 2px solid var(--gold-soft);
    background: rgba(180,138,74,0.06);
    border-radius: 0 6px 6px 0;
    display: flex; flex-direction: column; gap: 4px;
  }
  .kana-note-jp { font-size: 18px; color: var(--ink); letter-spacing: 0.04em; }
  .kana-note-en {
    font-family: var(--serif); font-style: italic; font-size: 13px;
    color: var(--ink-3); line-height: 1.5;
  }

  /* ─── settings button ───────────────────────────────────────────────── */
  .settings-btn {
    position: fixed; bottom: 16px; right: 16px; z-index: 90;
    width: 38px; height: 38px; border-radius: 50%;
    background: var(--paper-2); border: 1px solid var(--paper-edge);
    color: var(--ink-2); font-size: 17px; cursor: pointer;
    box-shadow: 0 2px 8px rgba(100,72,30,0.12);
    transition: background .15s, transform .15s;
    display: flex; align-items: center; justify-content: center;
  }
  .settings-btn:hover { background: var(--gold-soft); transform: rotate(30deg); }

  .settings-panel {
    position: fixed; bottom: 62px; right: 16px; z-index: 91;
    width: 240px; background: var(--paper-2);
    border: 1px solid var(--paper-edge); border-radius: 10px;
    padding: 14px 16px; box-shadow: 0 4px 18px rgba(100,72,30,0.14);
  }
  .settings-panel[hidden] { display: none; }
  .settings-row {
    display: flex; align-items: center; justify-content: space-between;
    padding: 7px 0; font-family: var(--serif); font-size: 13px; color: var(--ink-2);
    border-bottom: 1px dashed rgba(141,102,48,0.15);
  }
  .settings-row:last-child { border-bottom: none; }
  .settings-toggle {
    position: relative; width: 32px; height: 18px;
    background: rgba(0,0,0,.15); border: none; border-radius: 999px;
    cursor: pointer; padding: 0; transition: background .15s;
  }
  .settings-toggle[aria-checked="true"] { background: var(--gold); }
  .settings-toggle i {
    position: absolute; top: 2px; left: 2px; width: 14px; height: 14px;
    border-radius: 50%; background: #fff; box-shadow: 0 1px 2px rgba(0,0,0,.25);
    transition: transform .15s; pointer-events: none;
  }
  .settings-toggle[aria-checked="true"] i { transform: translateX(14px); }
  .settings-density { display: flex; gap: 4px; }
  .settings-density button {
    padding: 3px 8px; border-radius: 5px; border: 1px solid var(--paper-edge);
    background: var(--paper); font-family: var(--serif); font-size: 12px;
    color: var(--ink-3); cursor: pointer; transition: all .12s;
  }
  .settings-density button.active {
    background: var(--gold); color: white; border-color: var(--gold-dark);
  }

  /* ── 設定 link in the sidebar bottom ───────────────────────────── */
  .settings-link {
    position: relative;
    margin-top: auto;
    padding: 14px 14px 18px;
    border-top: 1px dashed rgba(141,102,48,0.3);
    display: flex; align-items: center;
    gap: 10px;
  }
  .settings-link button#settings-open {
    background: none;
    border: none;
    cursor: pointer;
    padding: 4px 6px;
    color: var(--ink-3);
    font-family: var(--serif-jp);
    font-size: 15px;
    letter-spacing: 0.04em;
    transition: color .15s, background .15s;
    border-radius: 4px;
  }
  .settings-link button#settings-open:hover {
    color: var(--ink);
    background: var(--gold-soft);
  }
  /* Audio-settings trigger — small speaker icon to the right of 設定.
     Same hover treatment as the text button, but icon-shaped so the
     two affordances read as distinct without competing for label space. */
  .audio-settings-btn {
    appearance: none;
    background: none;
    border: 1px solid transparent;
    border-radius: 50%;
    padding: 0;
    width: 32px; height: 32px;
    display: inline-flex; align-items: center; justify-content: center;
    color: var(--ink-3);
    cursor: pointer;
    transition: color .15s, background .15s, border-color .15s;
  }
  .audio-settings-btn:hover {
    color: var(--ink);
    background: var(--gold-soft);
    border-color: color-mix(in srgb, var(--ink) 14%, transparent);
  }
  .audio-settings-btn:focus-visible {
    outline: 2px solid var(--gold);
    outline-offset: 2px;
  }
  .audio-settings-btn svg { width: 18px; height: 18px; }

  /* Audio-settings popover — paper-tinted floating panel anchored via
     JS to the audio-settings button's viewport position (computed each
     time the popover opens). Uses position: fixed so it escapes any
     overflow / transform / position-context constraints from ancestor
     elements — the popover lands wherever the JS sets top/left,
     regardless of where in the DOM it lives. Opens upward (above the
     button) because the sidebar's settings row lives at the bottom of
     the viewport; opening downward would push the panel off-screen. */
  .audio-settings-popover {
    position: fixed;
    width: 340px;
    max-width: calc(100vw - 24px);
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    border-radius: 12px;
    box-shadow: 0 16px 40px -12px color-mix(in srgb, var(--ink) 36%, transparent);
    padding: 14px 16px 16px;
    z-index: 1100;
    color: var(--ink);
  }
  .audio-settings-popover[hidden] { display: none; }
  .audio-settings-pop-head {
    display: flex; align-items: baseline;
    gap: 10px;
    padding-bottom: 10px;
    margin-bottom: 10px;
    border-bottom: 1px solid color-mix(in srgb, var(--ink) 10%, transparent);
  }
  .audio-settings-pop-title {
    font-family: var(--font-title);
    font-size: 18px;
    color: var(--ink);
  }
  .audio-settings-pop-title rt {
    font-size: 0.42em;
    font-family: var(--font-body);
    color: var(--ink-3);
    letter-spacing: 0.02em;
  }
  .audio-settings-pop-en {
    font-family: var(--font-menu-en); font-style: italic;
    font-size: 11px;
    color: var(--ink-3);
    letter-spacing: 0.04em;
  }
  .audio-settings-row {
    display: grid;
    grid-template-columns: 70px 1fr auto;
    align-items: center;
    gap: 10px;
    padding: 8px 0;
  }
  .audio-settings-row-voice {
    grid-template-columns: 70px 1fr;
  }
  .audio-settings-label {
    display: inline-flex; flex-direction: column;
    line-height: 1.1;
    min-width: 0;
  }
  .audio-settings-label ruby {
    font-family: var(--font-title);
    font-size: 14px;
    color: var(--ink);
  }
  .audio-settings-label rt {
    font-size: 0.42em;
    font-family: var(--font-body);
    color: var(--ink-3);
    letter-spacing: 0.02em;
  }
  .audio-settings-label-en {
    font-family: var(--font-menu-en); font-style: italic;
    font-size: 10px;
    color: var(--ink-3);
    margin-top: 2px;
  }
  .audio-settings-slider {
    -webkit-appearance: none; appearance: none;
    width: 100%;
    height: 4px;
    background: color-mix(in srgb, var(--ink) 14%, transparent);
    border-radius: 999px;
    outline: none;
    cursor: pointer;
  }
  .audio-settings-slider::-webkit-slider-thumb {
    -webkit-appearance: none; appearance: none;
    width: 18px; height: 18px;
    background: var(--gold);
    border: 2px solid var(--paper);
    border-radius: 50%;
    box-shadow: 0 1px 3px color-mix(in srgb, var(--ink) 30%, transparent);
    cursor: pointer;
  }
  .audio-settings-slider::-moz-range-thumb {
    width: 18px; height: 18px;
    background: var(--gold);
    border: 2px solid var(--paper);
    border-radius: 50%;
    box-shadow: 0 1px 3px color-mix(in srgb, var(--ink) 30%, transparent);
    cursor: pointer;
  }
  .audio-settings-value {
    font-family: var(--font-menu-en); font-style: italic;
    font-size: 12px;
    color: var(--ink-2);
    min-width: 38px;
    text-align: right;
    letter-spacing: 0.02em;
  }
  .audio-settings-select {
    width: 100%;
    /* extra right padding keeps the long voice names clear of the native
       dropdown arrow (it was overlapping the text). */
    padding: 6px 28px 6px 8px;
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 6px;
    font-family: var(--font-body);
    font-size: 13px;
    color: var(--ink);
    cursor: pointer;
    text-overflow: ellipsis;
  }
  .audio-settings-select:focus-visible {
    outline: 2px solid var(--gold);
    outline-offset: 1px;
  }
  .audio-settings-test-row {
    display: flex; align-items: center;
    gap: 10px;
    padding-top: 12px;
    margin-top: 6px;
    border-top: 1px solid color-mix(in srgb, var(--ink) 10%, transparent);
  }
  .audio-settings-test {
    appearance: none;
    display: inline-flex; align-items: center;
    gap: 6px;
    padding: 6px 12px 6px 10px;
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 999px;
    color: var(--ink);
    font-family: var(--font-title);
    font-size: 13px;
    cursor: pointer;
    transition: background .15s, border-color .15s, transform .15s;
  }
  .audio-settings-test:hover {
    background: var(--paper);
    border-color: color-mix(in srgb, var(--ink) 24%, var(--paper-edge));
    transform: translateY(-1px);
  }
  .audio-settings-test svg { width: 14px; height: 14px; color: var(--ink-2); }
  .audio-settings-test-hint {
    font-family: var(--font-body);
    font-size: 11px;
    color: var(--ink-3);
    font-style: italic;
  }
  @media (prefers-reduced-motion: reduce) {
    .audio-settings-test { transition: none; }
    .audio-settings-test:hover { transform: none; }
  }

  /* ── Settings modal ────────────────────────────────────────────── */
  .settings-modal-backdrop {
    position: fixed; inset: 0;
    background: rgba(40,28,12,0.42);
    backdrop-filter: blur(2px);
    display: flex; align-items: center; justify-content: center;
    padding: 24px;
    z-index: 1000;
    animation: settings-modal-fade .15s ease-out;
  }
  @keyframes settings-modal-fade { from { opacity: 0 } to { opacity: 1 } }
  .settings-modal {
    position: relative;
    background: var(--paper-2);
    border: 1px solid var(--gold-soft);
    border-radius: 8px;
    box-shadow:
      0 0 0 6px var(--paper-2),
      0 0 0 7px var(--gold-soft),
      0 24px 56px rgba(60,40,10,0.32);
    width: 100%;
    max-width: 520px;
    max-height: calc(100vh - 48px);
    overflow-y: auto;
    padding: 24px 28px 26px;
    scrollbar-width: thin;
    scrollbar-color: var(--paper-edge) transparent;
  }
  .settings-modal-close {
    position: absolute; top: 6px; right: 10px;
    background: transparent; border: none;
    font-family: var(--serif); font-size: 26px; line-height: 1;
    color: var(--ink-3); cursor: pointer;
    padding: 6px 10px; border-radius: 4px;
    transition: color .15s, background .15s;
  }
  .settings-modal-close:hover { color: var(--ink); background: var(--gold-soft); }
  .settings-modal h2 {
    margin: 0 0 4px;
    font-family: var(--serif-jp); font-weight: 700;
    font-size: 22px; color: var(--ink);
  }
  .settings-modal .settings-eyebrow {
    font-family: var(--serif); font-style: italic;
    font-size: 12px; color: var(--ink-4);
    letter-spacing: 0.04em;
    margin-bottom: 18px;
  }
  .settings-section {
    margin: 18px 0 0;
    padding-top: 14px;
    border-top: 1px dashed rgba(141,102,48,0.22);
  }
  .settings-section:first-of-type { border-top: none; padding-top: 4px; margin-top: 8px; }
  .settings-section-head {
    display: flex; align-items: baseline; gap: 10px;
    margin-bottom: 10px;
  }
  .settings-section-head .ja {
    font-family: var(--serif-jp); font-weight: 600;
    font-size: 14px; color: var(--ink);
  }
  .settings-section-head .en {
    font-family: var(--serif); font-style: italic;
    font-size: 11px; color: var(--ink-3);
  }
  .settings-field {
    display: flex; align-items: center; justify-content: space-between;
    gap: 16px;
    padding: 6px 0;
  }
  .settings-field-label {
    font-family: var(--serif); font-size: 13px; color: var(--ink-2);
  }
  .settings-field-label .hint {
    display: block;
    font-style: italic; font-size: 11px; color: var(--ink-4);
    margin-top: 2px;
  }
  .settings-field select,
  .settings-field input[type="number"] {
    font-family: var(--serif); font-size: 13px;
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    border-radius: 4px;
    padding: 5px 8px;
    color: var(--ink-2);
    min-width: 140px;
  }
  .settings-field input[type="number"] { width: 80px; min-width: 0; }
  .settings-toggle-row {
    display: flex; align-items: center; justify-content: space-between;
    gap: 16px;
    padding: 6px 0;
  }
  /* Settings — font role tabs. Tabs let the user switch between the four
     typography slots (title / menu-ja / menu-en / body). Active tab gets
     a gold underline; inactive tabs keep their type small and quiet. */
  .settings-font-tabs {
    display: flex; gap: 2px;
    margin-bottom: 14px;
    border-bottom: 1px solid var(--paper-edge);
    flex-wrap: wrap;
  }
  .settings-font-tab {
    flex: 1 1 auto;
    background: transparent;
    border: none;
    padding: 8px 10px 10px;
    cursor: pointer;
    text-align: left;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;            /* sit on the section border-bottom */
    transition: border-color .12s, color .12s;
    display: flex; flex-direction: column; gap: 1px;
    min-width: 0;
  }
  .settings-font-tab-ja {
    font-family: var(--font-menu-ja);
    font-size: 13px;
    color: var(--ink-3);
    line-height: 1.2;
  }
  .settings-font-tab-en {
    font-family: var(--serif); font-style: italic;
    font-size: 11px;
    color: var(--ink-4);
    line-height: 1.2;
  }
  .settings-font-tab:hover .settings-font-tab-ja { color: var(--ink-2); }
  .settings-font-tab.is-active {
    border-bottom-color: var(--gold-dark);
  }
  .settings-font-tab.is-active .settings-font-tab-ja { color: var(--ink); }
  .settings-font-tab.is-active .settings-font-tab-en { color: var(--gold-dark); }

  /* Live sample — shows what the currently-selected font looks like in
     context for the active role. Updates instantly when you pick a new
     font card below. */
  .settings-font-sample {
    margin-bottom: 14px;
    padding: 14px 16px;
    background: var(--paper);
    border: 1px dashed var(--paper-edge);
    border-radius: 6px;
    color: var(--ink);
    line-height: 1.4;
    min-height: 38px;
  }

  .settings-font-grid {
    display: grid; grid-template-columns: repeat(2, 1fr);
    gap: 8px;
  }
  /* Wrapper for the font-role dropdown inside each settings tab —
     replaces the old 2x2 card grid with a single inline dropdown. */
  .settings-font-dropdown-wrap {
    margin-top: 4px;
  }
  .settings-font-dropdown-wrap .font-dropdown { width: 100%; }
  .settings-font-dropdown-wrap .font-dropdown-trigger {
    width: 100%;
    justify-content: space-between;
  }
  .settings-font-card {
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    border-radius: 5px;
    padding: 10px 12px;
    cursor: pointer;
    text-align: left;
    transition: background .12s, border-color .12s;
  }
  .settings-font-card:hover {
    border-color: var(--gold-soft);
    background: color-mix(in srgb, var(--gold-soft) 18%, var(--paper));
  }
  .settings-font-card.is-active {
    background: var(--gold-soft);
    border-color: var(--gold-dark);
    box-shadow: inset 0 0 0 1px var(--gold-dark);
  }
  .settings-font-card .preview {
    font-weight: 700; font-size: 18px; color: var(--ink);
    line-height: 1.1;
  }
  .settings-font-card .label {
    display: flex; gap: 6px; align-items: baseline;
    margin-top: 4px;
    font-family: var(--serif); font-style: italic;
    font-size: 11px; color: var(--ink-3);
  }
  .settings-font-card .label .ja {
    font-family: var(--serif-jp); font-style: normal;
    font-size: 12px; color: var(--ink-2);
  }
  .settings-test-btn {
    margin-top: 8px;
    padding: 6px 14px;
    background: var(--ink);
    color: var(--paper);
    border: none; border-radius: 4px;
    font-family: var(--serif-jp); font-size: 13px;
    cursor: pointer;
    transition: background .12s;
  }
  .settings-test-btn:hover {
    background: color-mix(in srgb, var(--ink) 80%, var(--gold-dark) 20%);
  }
  /* Inline failure message under the Test button (e.g. a 403 from Cloud TTS). */
  .settings-test-status {
    margin: 8px 0 0;
    padding: 8px 10px;
    border-radius: 5px;
    background: color-mix(in srgb, var(--accent-vermilion, #c34b30) 9%, var(--paper));
    border: 1px solid color-mix(in srgb, var(--accent-vermilion, #c34b30) 35%, transparent);
    font-family: var(--serif); font-size: 12px; line-height: 1.45;
    color: color-mix(in srgb, var(--accent-vermilion, #c34b30) 55%, var(--ink));
  }
  .settings-test-status[hidden] { display: none; }
  .settings-no-voice {
    font-family: var(--serif); font-style: italic;
    font-size: 12px; color: var(--ink-3);
    background: var(--paper);
    border: 1px dashed rgba(141,102,48,0.3);
    padding: 10px 12px; border-radius: 5px;
    margin: 8px 0;
  }

  /* ── Speaker button (universal TTS trigger) ────────────────────── */
  /* Used inline next to JP content (popover, flashcard, NPC bubble).
     Tiny by default so it doesn't compete with text — gold-soft on
     hover, gold while speaking. */
  .tts-btn {
    display: inline-flex; align-items: center; justify-content: center;
    width: 28px; height: 28px;
    border-radius: 50%;
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    color: var(--ink-3);
    cursor: pointer;
    padding: 0;
    transition: background .12s, color .12s, border-color .12s, transform .08s;
  }
  .tts-btn svg { width: 14px; height: 14px; }
  .tts-btn:hover {
    background: var(--gold-soft);
    color: var(--ink);
    border-color: var(--gold);
  }
  .tts-btn:active { transform: scale(0.92); }
  .tts-btn:focus-visible {
    outline: 2px solid var(--gold);
    outline-offset: 2px;
  }
  /* When used inside the word popover — sit at top-right next to the close button. */
  .word-pop .tts-btn {
    position: absolute;
    top: 8px; right: 44px;
  }
  /* Larger variant — used on flashcards. */
  .tts-btn-lg { width: 38px; height: 38px; }
  .tts-btn-lg svg { width: 18px; height: 18px; }
  /* Inside NPC bubble — float at the top-right corner. */
  .scene-npc-bubble { position: relative; }
  .scene-npc-bubble .tts-btn {
    position: absolute;
    top: 6px; right: 6px;
  }

  /* ───────────────────────────────────────────────────────────────────
     Sentence Structure page — ported from sentence-structure.html
     All class names are prefixed `ss-` to keep the page sandboxed from
     the rest of the app. The page uses the lego-block visualization
     (noun/verb/particle/stop) and particle color highlights to teach
     SOV order, particles-as-labels, the verb-as-anchor, and word
     reordering for emphasis.
     ──────────────────────────────────────────────────────────────── */

  /* page-local tokens — lego-block palette + particle hues
     (the particle hues mirror the palette assigned to each particle in
     window.PARTICLES so colors stay consistent across the app). */
  .ss-page {
    /* lego-block palette (semantic word types) */
    --block-noun:    #d4884a;
    --block-noun-dk: #a85f24;
    --block-verb:    #6f9c5b;
    --block-verb-dk: #4a7138;
    --block-part:    #4f9aa8;
    --block-part-dk: #2f6e7a;
    --block-stop:    #8d6c9c;
    --block-stop-dk: #5e4470;

    /* particle hues — match particles-data.js exactly */
    --pc-wa:   #8a2538;
    --pc-ga:   #c97a2c;
    --pc-o:    #5a2e8a;
    --pc-ni:   #3a7a4a;
    --pc-de:   #2e7a3f;
    --pc-e:    #2a5b94;
    --pc-no:   #c43a4a;
    --pc-to:   #2a5b94;
    --pc-mo:   #6a3a92;
    --pc-kara: #a04a8a;
    --pc-made: #3a7a8a;
    --pc-ya:   #8d6630;

    /* Shared chevron depth — used by both the arrow blocks (notch + point)
       and the diamond's half-width, so adjacent pieces share their vertices. */
    --chev: 28px;
  }

  /* ─── section chrome ────────────────────────────────────────────────── */
  .ss-section { margin-bottom: 64px; }
  .ss-section-head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 4px 14px;
    margin-bottom: 22px;
  }
  .ss-section-num {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: clamp(72px, 7vw, 96px);
    height: clamp(72px, 7vw, 96px);
    margin: 0 18px 0 -8px;
    flex: 0 0 auto;
    overflow: visible;
    position: relative;
    perspective: 600px;
  }
  .ss-section-num .ss-kanji-num {
    display: inline-block;
    font-family: 'Yuji Mai', 'Shippori Mincho', serif;
    font-weight: 400;
    font-size: clamp(70px, 8vw, 96px);
    line-height: 1;
    color: #1a120a;
    /* Drop shadows removed — the kanji number reads cleaner flat,
       letting the calligraphic stroke do the work without bevelled
       text-shadow + filter:drop-shadow layers competing for attention. */
    --rot: -3deg;
    transform: rotate(var(--rot));
    transform-origin: 50% 60%;
    transition:
      clip-path 1300ms cubic-bezier(.22, .9, .12, 1.0),
      transform 1300ms cubic-bezier(.22, .9, .12, 1.0);
    will-change: clip-path, transform;
  }
  .ss-section-num[data-axis="h"] .ss-kanji-num {
    clip-path: inset(0% 100% 0% 0%);
    transform: rotate(var(--rot)) translateX(-6%) scaleX(0.92);
  }
  .ss-section-num[data-axis="v"] .ss-kanji-num {
    clip-path: inset(0% 0% 100% 0%);
    transform: rotate(var(--rot)) translateY(-4%) scaleY(0.92);
  }
  .ss-section-num.ss-drawn[data-axis="h"] .ss-kanji-num,
  .ss-section-num.ss-drawn[data-axis="v"] .ss-kanji-num,
  .ss-section-num.ss-drawn .ss-kanji-num {
    clip-path: inset(0% 0.01% 0% 0.01%);
    transform: rotate(var(--rot));
  }
  .ss-section-title {
    font-family: var(--serif-jp); font-weight: 600;
    font-size: clamp(22px, 2.6vw, 28px);
    color: var(--ink);
    letter-spacing: 0.02em;
    line-height: 1.2;
    flex: 1 1 auto;
    min-width: 0;
  }
  .ss-section-title em {
    font-style: italic;
    font-family: var(--serif);
    color: var(--ink-3);
    font-size: 0.7em;
    margin-left: 8px;
    font-weight: 400;
  }
  .ss-section-lede {
    flex: 1 0 100%;
    margin-left: calc(clamp(72px, 7vw, 96px) + 18px);
    font-family: var(--serif); font-size: 17px;
    color: var(--ink-2);
    line-height: 1.6;
    max-width: 64ch;
    margin-top: 8px;
  }
  .ss-section-lede b { color: var(--ink); font-weight: 600; }
  @media (max-width: 760px) {
    .ss-section-lede { margin-left: 0; }
  }

  /* ─── card / panel ──────────────────────────────────────────────────── */
  .ss-card {
    background: var(--paper-2);
    border: 1px solid rgba(141,102,48,0.22);
    border-radius: 10px;
    padding: 28px clamp(20px, 3vw, 36px);
    box-shadow: 0 1px 0 rgba(255,255,255,0.4) inset, 0 4px 14px var(--paper-shadow);
    margin-top: 18px;
    position: relative;
  }
  .ss-card.ss-feature {
    border: 2px solid var(--gold-soft);
    box-shadow:
      0 0 0 6px var(--paper-2),
      0 0 0 7px var(--gold-soft),
      0 8px 24px rgba(100,72,30,0.18);
    border-radius: 4px;
    padding: 32px clamp(20px, 4vw, 48px);
  }
  .ss-card.ss-feature::before, .ss-card.ss-feature::after,
  .ss-card.ss-feature > .ss-corner-a, .ss-card.ss-feature > .ss-corner-b {
    content: ""; position: absolute; width: 14px; height: 14px;
    border: 2px solid var(--gold);
    background: var(--paper-2);
    border-radius: 50%;
  }
  .ss-card.ss-feature::before { top: -8px; left: -8px; }
  .ss-card.ss-feature::after  { bottom: -8px; right: -8px; }
  .ss-card.ss-feature > .ss-corner-a { top: -8px; right: -8px; }
  .ss-card.ss-feature > .ss-corner-b { bottom: -8px; left: -8px; }

  /* ─── english vs japanese contrast ─────────────────────────────────── */
  .ss-contrast {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    gap: 24px;
    align-items: center;
  }
  @media (max-width: 760px) {
    .ss-contrast { grid-template-columns: 1fr; }
    .ss-contrast .ss-vs { order: 2; transform: rotate(90deg); }
  }
  .ss-lang-panel { text-align: center; }
  .ss-lang-panel h3 {
    font-family: var(--serif); font-style: italic; font-weight: 500;
    color: var(--ink-3); font-size: 14px;
    letter-spacing: 0.1em; text-transform: uppercase;
    margin: 0 0 12px;
  }
  .ss-lang-sentence {
    font-family: var(--serif-jp); font-size: clamp(18px, 2vw, 22px);
    color: var(--ink); line-height: 1.6;
    margin-bottom: 16px;
  }
  .ss-lang-sentence .ss-en-word { font-family: var(--serif); font-style: italic; }
  .ss-lang-sentence .ss-verb-pop {
    display: inline-block;
    padding: 2px 10px;
    background: var(--block-verb);
    color: #fff;
    border-radius: 4px;
    font-family: var(--sans-jp); font-weight: 700;
    box-shadow: 0 2px 0 var(--block-verb-dk);
  }
  .ss-vs {
    width: 44px; height: 44px;
    border-radius: 50%;
    background: var(--paper);
    border: 1.5px dashed var(--gold);
    display: flex; align-items: center; justify-content: center;
    font-family: var(--serif); font-style: italic;
    color: var(--gold-dark); font-size: 14px;
  }
  .ss-lang-rule {
    font-family: var(--serif); font-style: italic; font-size: 14px;
    color: var(--ink-3); line-height: 1.5;
  }
  .ss-lang-rule b { color: var(--ink-2); font-style: normal; font-weight: 600; }

  /* ─── particle-verb anatomy diagram ────────────────────────────────
     Replaces the previous symmetric star (verb-at-center, particles
     in circles, dotted radial lines). The old layout fought the lesson
     it was trying to teach — the verb sits at the END in Japanese,
     not the center.

     New shape:
       • horizontal stamp track running left → right
       • 5 particle stamps showing a real sentence chunk-by-chunk
       • a continuous gold hairline baseline runs UNDER every stamp,
         joining them as a single writing line (replaces the dotted
         radial lines)
       • verb terminus card at the far right, larger, with a 終わり
         (the end) eyebrow — the actual destination of the sentence
       • below the anatomy, an 8-card catalogue of every particle by
         role for reference (this is the "menu" of all possibilities)
       • 2-line italic note explains optionality + word-order rule

     No SVG, no JS — semantic HTML + CSS only. Hover/focus on a
     catalogue card optionally cross-highlights the matching stamp
     in the anatomy above; see [data-pc] attribute on both. */
  .pv-wrap {
    padding: 32px 24px 28px;
  }
  .pv-eyebrow {
    font-family: var(--serif); font-style: italic;
    font-size: 13px;
    color: var(--ink-4);
    letter-spacing: 0.06em;
    text-align: center;
    margin-bottom: 18px;
  }
  .pv-eyebrow .pv-eyebrow-ja {
    font-family: var(--serif-jp); font-style: normal;
    color: var(--ink-3);
    font-size: 14px;
    margin-right: 8px;
  }

  /* The track — single horizontal row of stamps. Wraps to a new line
     only when the viewport can't fit them at minimum-comfortable
     legibility. The flex layout uses align-items: flex-end so the
     stamps sit on a shared baseline regardless of their internal
     content height — that baseline IS the calligrapher's writing line. */
  .pv-track {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: center;
    gap: 14px 12px;
    position: relative;
    padding-bottom: 10px;
    margin: 0 auto 14px;
    max-width: 920px;
  }

  /* Particle stamp — a small letterpress-feel rectangle, three-line
     stack: real word on top, particle in its accent color underneath,
     italic role caption below the stamp body. The accent color comes
     in via --pc set inline so each stamp paints its own particle's hue
     without us having to enumerate 8 .pv-stamp-* variants. */
  .pv-stamp {
    --pc: var(--ink-3);
    display: flex; flex-direction: column;
    align-items: center;
    gap: 0;
    position: relative;
    flex: 0 0 auto;
    text-align: center;
    /* The gold baseline lives as the bottom border of the stamp's
       inner well — every stamp shares the same border, so they read
       as one continuous line when laid side-by-side. */
  }
  .pv-stamp-body {
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 4px;
    padding: 10px 14px 8px;
    box-shadow:
      0 1px 0 rgba(255, 255, 255, 0.55) inset,
      0 1px 0 var(--paper-edge),
      0 6px 14px rgba(60, 40, 10, 0.06);
    /* Letterpressed-into-paper feel: subtle inner highlight on top
       (1px white) + 1px shadow under, so the stamp reads like it was
       pressed into the page. */
    display: flex; flex-direction: column;
    align-items: center;
    min-width: 64px;
  }
  .pv-stamp-word {
    font-family: var(--serif-jp);
    font-weight: 600;
    font-size: 18px;
    color: var(--ink);
    line-height: 1.15;
    letter-spacing: 0.01em;
  }
  .pv-stamp-particle {
    font-family: var(--serif-jp);
    font-weight: 700;
    font-size: 22px;
    color: var(--pc);
    line-height: 1;
    margin-top: 4px;
    /* The particle is the "rubber-stamp" pressed mark — a touch
       larger than the word, in its accent color, against the
       paper-2 stamp body. Reads as the ink-block of a wax-seal. */
  }
  .pv-stamp-role {
    margin-top: 8px;
    font-family: var(--serif);
    font-style: italic;
    font-size: 11px;
    color: var(--ink-4);
    line-height: 1.3;
    letter-spacing: 0.04em;
  }
  .pv-stamp-role em {
    font-style: normal;
    font-family: var(--serif-jp);
    color: var(--ink-3);
    margin-left: 4px;
    font-size: 11px;
  }

  /* Verb terminus — bigger than the other stamps. Gold-soft halo +
     paper-2 fill so it matches the editorial-flashcard chrome family.
     "終わり · the end" eyebrow above tells the learner explicitly
     "this is where the sentence resolves." */
  .pv-verb {
    flex: 0 0 auto;
    display: flex; flex-direction: column;
    align-items: center;
    margin-left: 6px;
    text-align: center;
  }
  .pv-verb-eyebrow {
    font-family: var(--serif); font-style: italic;
    font-size: 11px;
    color: var(--gold-dark);
    letter-spacing: 0.08em;
    margin-bottom: 6px;
    text-transform: lowercase;
  }
  .pv-verb-eyebrow .pv-verb-eyebrow-ja {
    font-family: var(--serif-jp); font-style: normal;
    color: var(--gold-dark);
    margin-right: 6px;
  }
  .pv-verb-body {
    background: var(--paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 6px;
    padding: 14px 22px 12px;
    box-shadow:
      0 0 0 4px var(--paper-2),
      0 0 0 5px var(--gold-soft),
      0 10px 24px rgba(60, 40, 10, 0.14);
    display: flex; flex-direction: column;
    align-items: center;
    min-width: 110px;
  }
  .pv-verb-word {
    font-family: var(--serif-jp);
    font-weight: 700;
    font-size: 26px;
    color: var(--ink);
    line-height: 1.1;
    letter-spacing: 0.02em;
  }
  .pv-verb-label {
    margin-top: 8px;
    font-family: var(--serif);
    font-style: italic;
    font-size: 11px;
    color: var(--ink-3);
    letter-spacing: 0.04em;
  }
  .pv-verb-label em {
    font-style: normal;
    font-family: var(--serif-jp);
    color: var(--ink-2);
    margin-left: 4px;
    font-size: 11px;
  }

  /* The translation — hairline italic, sits under the anatomy track
     centered. Confirms the meaning without competing with the JP. */
  .pv-translation {
    text-align: center;
    margin: 4px auto 26px;
    max-width: 720px;
    font-family: var(--serif); font-style: italic;
    font-size: 14px;
    color: var(--ink-3);
    line-height: 1.55;
  }

  /* The catalogue — 8 small cards listing every particle by role.
     The grid is responsive; on wide viewports it's 4 cols × 2 rows
     so the eye reads top-to-bottom by particle position (は first,
     と last). On narrow viewports it collapses to 2 columns. */
  .pv-catalog {
    margin: 0 auto;
    max-width: 920px;
  }
  .pv-catalog-title {
    text-align: center;
    font-family: var(--serif-jp);
    font-size: 13px;
    color: var(--ink-3);
    letter-spacing: 0.06em;
    margin-bottom: 12px;
  }
  .pv-catalog-title em {
    font-family: var(--serif);
    font-style: italic;
    color: var(--ink-4);
    margin-left: 8px;
    font-size: 12px;
  }
  .pv-catalog-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 10px;
  }
  .pv-cat-card {
    --pc: var(--ink-3);
    background: var(--paper);
    border: 1px solid var(--paper-edge);
    border-radius: 6px;
    padding: 12px 12px 11px;
    display: grid;
    grid-template-columns: auto 1fr;
    grid-template-rows: auto auto auto;
    gap: 1px 12px;
    align-items: center;
    transition: border-color .15s, transform .12s, background .15s;
  }
  .pv-cat-card:hover {
    border-color: var(--pc);
    background: var(--paper-2);
    transform: translateY(-1px);
  }
  .pv-cat-glyph {
    grid-row: 1 / span 3;
    grid-column: 1;
    font-family: var(--serif-jp);
    font-weight: 700;
    font-size: 28px;
    color: var(--pc);
    line-height: 1;
    padding-right: 4px;
    border-right: 1px solid var(--paper-edge);
  }
  .pv-cat-role-en {
    grid-row: 1; grid-column: 2;
    font-family: var(--serif); font-weight: 600;
    font-size: 13px;
    color: var(--ink);
    line-height: 1.1;
  }
  .pv-cat-role-ja {
    grid-row: 2; grid-column: 2;
    font-family: var(--serif-jp);
    font-size: 11px;
    color: var(--ink-3);
    line-height: 1.1;
  }
  .pv-cat-ex {
    grid-row: 3; grid-column: 2;
    font-family: var(--serif-jp);
    font-size: 12px;
    color: var(--ink-2);
    line-height: 1.25;
    margin-top: 4px;
    letter-spacing: 0.01em;
  }
  .pv-cat-ex span {
    color: var(--pc);
    font-weight: 700;
  }
  /* "Same particle, two roles" — に and で each appear twice. The
     .pv-cat-card-dual modifier dims the second occurrence's left
     glyph border into a dashed mark so the eye notices "I've seen
     this character before, but with a different job." */
  .pv-cat-card-dual .pv-cat-glyph {
    border-right-style: dashed;
  }

  /* Bottom note — keeps the original message but rewritten cleaner. */
  .pv-note {
    text-align: center;
    margin: 22px auto 0;
    max-width: 720px;
    font-family: var(--serif); font-style: italic;
    color: var(--ink-3); font-size: 14px;
    line-height: 1.55;
  }
  .pv-note .pv-pc {
    --pc: var(--ink);
    color: var(--pc);
    font-family: var(--serif-jp);
    font-weight: 600;
    font-style: normal;
  }
  .pv-note em {
    color: var(--ink-2);
    font-style: italic;
  }

  /* Responsive — collapse the stamp track to a wrappable flow on
     tablet, two-column catalogue on tablet, one-column on phone. */
  @media (max-width: 760px) {
    .pv-catalog-grid { grid-template-columns: repeat(2, 1fr); }
    .pv-stamp-word { font-size: 16px; }
    .pv-stamp-particle { font-size: 19px; }
    .pv-verb-word { font-size: 22px; }
  }
  @media (max-width: 460px) {
    .pv-catalog-grid { grid-template-columns: 1fr; }
    .pv-wrap { padding: 24px 14px 22px; }
  }

  /* ─── lego legend ───────────────────────────────────────────────────── */
  .ss-lego-legend {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 20px;
    margin-top: 20px;
  }
  .ss-lego-card {
    background: var(--paper-2);
    border: 1px solid rgba(141,102,48,0.22);
    border-radius: 10px;
    padding: 18px 18px 22px;
    display: flex; flex-direction: column; gap: 12px;
  }
  .ss-lego-card .ss-shape-row { display: flex; justify-content: center; padding: 6px 0; }
  .ss-lego-card h4 {
    font-family: var(--serif-jp); font-weight: 700;
    font-size: 17px; color: var(--ink);
    margin: 0;
  }
  .ss-lego-card .ss-what {
    font-family: var(--serif); font-style: italic;
    color: var(--ink-2); font-size: 14px;
    line-height: 1.45;
    margin: 0;
  }
  .ss-lego-card .ss-ex {
    font-family: var(--sans-jp); font-size: 13px;
    color: var(--ink-3);
    background: var(--paper);
    border-radius: 6px;
    padding: 6px 10px;
    border: 1px dashed rgba(141,102,48,0.3);
  }
  .ss-lego-card .ss-ex .ss-jp { font-family: var(--serif-jp); font-weight: 600; color: var(--ink-2); }

  /* ─── lego block shapes (clip-path) ─────────────────────────────────── */
  .ss-blk {
    position: relative;
    display: inline-flex; align-items: center; justify-content: center;
    height: 56px;
    padding: 0 22px;
    font-family: var(--sans-jp); font-weight: 700;
    font-size: 18px;
    color: #fff;
    letter-spacing: 0.02em;
    text-shadow: 0 1px 0 rgba(0,0,0,0.18);
    transition: transform .15s;
    cursor: default;
    flex: 0 0 auto;
  }
  .ss-blk small { font-family: var(--serif); font-style: italic; font-weight: 400; font-size: 12px; opacity: 0.85; margin-left: 6px; }
  .ss-blk.ss-noun {
    background: var(--block-noun);
    clip-path: polygon(0 0, 100% 0, calc(100% - var(--chev)) 50%, 100% 100%, 0 100%, var(--chev) 50%);
    padding: 0 calc(20px + var(--chev));
    filter: drop-shadow(0 3px 0 var(--block-noun-dk));
  }
  .ss-blk.ss-verb {
    background: var(--block-verb);
    clip-path: polygon(0 0, calc(100% - var(--chev)) 0, 100% 50%, calc(100% - var(--chev)) 100%, 0 100%, var(--chev) 50%);
    padding: 0 calc(20px + var(--chev));
    filter: drop-shadow(0 3px 0 var(--block-verb-dk));
  }
  .ss-blk.ss-part {
    background: var(--block-part);
    clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
    width: 56px;
    height: 56px;
    padding: 0;
    font-size: 18px;
    filter: drop-shadow(0 3px 0 var(--block-part-dk));
  }
  .ss-blk.ss-stop {
    background: var(--block-stop);
    clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%, var(--chev) 50%);
    padding: 0 12px 0 calc(8px + var(--chev));
    min-width: 52px;
    filter: drop-shadow(0 3px 0 var(--block-stop-dk));
  }
  /* particle diamond colored by hue (overrides the generic cyan) */
  .ss-blk.ss-part.ss-h-wa   { background: var(--pc-wa);   filter: drop-shadow(0 3px 0 #5a1923); }
  .ss-blk.ss-part.ss-h-ga   { background: var(--pc-ga);   filter: drop-shadow(0 3px 0 #8f521b); }
  .ss-blk.ss-part.ss-h-o    { background: var(--pc-o);    filter: drop-shadow(0 3px 0 #3a1d5c); }
  .ss-blk.ss-part.ss-h-ni   { background: var(--pc-ni);   filter: drop-shadow(0 3px 0 #245030); }
  .ss-blk.ss-part.ss-h-de   { background: var(--pc-de);   filter: drop-shadow(0 3px 0 #1f5229); }
  .ss-blk.ss-part.ss-h-e    { background: var(--pc-e);    filter: drop-shadow(0 3px 0 #1c3e66); }
  .ss-blk.ss-part.ss-h-no   { background: var(--pc-no);   filter: drop-shadow(0 3px 0 #872732); }
  .ss-blk.ss-part.ss-h-to   { background: var(--pc-to);   filter: drop-shadow(0 3px 0 #1c3e66); }
  .ss-blk.ss-part.ss-h-mo   { background: var(--pc-mo);   filter: drop-shadow(0 3px 0 #482664); }
  .ss-blk.ss-part.ss-h-kara { background: var(--pc-kara); filter: drop-shadow(0 3px 0 #6e305f); }
  .ss-blk.ss-part.ss-h-made { background: var(--pc-made); filter: drop-shadow(0 3px 0 #275660); }
  .ss-blk.ss-part.ss-h-ya   { background: var(--pc-ya);   filter: drop-shadow(0 3px 0 #5d4420); }

  /* ─── built sentence row ───────────────────────────────────────────── */
  .ss-sentence-build {
    background: linear-gradient(180deg, #2e261e, #1a140e);
    border-radius: 12px;
    padding: 28px clamp(16px, 3vw, 32px);
    position: relative;
    overflow: hidden;
    margin-top: 18px;
    box-shadow:
      inset 0 0 0 1px rgba(255,255,255,0.04),
      0 8px 30px rgba(58,40,24,0.30);
  }
  .ss-sentence-build::before {
    content: "";
    position: absolute; inset: 0;
    background-image:
      radial-gradient(circle at 12% 20%, rgba(212,136,74,0.06), transparent 40%),
      radial-gradient(circle at 88% 80%, rgba(143,108,156,0.05), transparent 40%);
    pointer-events: none;
  }
  .ss-sentence-build > * { position: relative; z-index: 1; }
  .ss-sentence-en {
    font-family: var(--serif); font-style: italic;
    color: rgba(255,255,255,0.65);
    font-size: 17px;
    text-align: center;
    margin: 0 0 16px;
    letter-spacing: 0.02em;
  }
  .ss-sentence-jp-text {
    font-family: var(--sans-jp); font-weight: 900;
    font-size: clamp(20px, 2.6vw, 30px);
    color: #fff;
    text-align: center;
    line-height: 1.2;
    margin: 0 0 16px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
  }
  .ss-sentence-jp-text .ss-w-noun   { color: var(--block-noun); }
  .ss-sentence-jp-text .ss-w-verb   { color: var(--block-verb); }
  .ss-sentence-jp-text .ss-w-part   { color: var(--block-part); }
  .ss-sentence-jp-text .ss-w-stop   { color: var(--block-stop); }
  .ss-sentence-jp-text .ss-w-h-wa   { color: var(--pc-wa); }
  .ss-sentence-jp-text .ss-w-h-ga   { color: var(--pc-ga); }
  .ss-sentence-jp-text .ss-w-h-o    { color: #b287d6; }
  .ss-sentence-jp-text .ss-w-h-ni   { color: #74b187; }
  .ss-sentence-jp-text .ss-w-h-no   { color: var(--pc-no); }
  .ss-sentence-jp-text .ss-w-h-to   { color: #6a9bce; }
  .ss-sentence-jp-text .ss-w-h-mo   { color: #a87fc4; }

  .ss-blocks-row {
    display: flex; align-items: center; justify-content: center;
    gap: 0;
    flex-wrap: wrap;
    row-gap: 14px;
    margin-top: 14px;
    padding: 0 var(--chev);
  }
  .ss-blocks-row .ss-blk { margin: 0; }
  .ss-blocks-row .ss-blk + .ss-blk { margin-left: calc(-1 * var(--chev)); }
  .ss-blocks-row .ss-blk.ss-part { z-index: 1; }
  /* First arrow block in a row should have a flat left edge instead of a notch */
  .ss-blocks-row > .ss-blk.ss-noun:first-child {
    clip-path: polygon(0 0, 100% 0, calc(100% - var(--chev)) 50%, 100% 100%, 0 100%);
    padding-left: 22px;
  }
  .ss-blocks-row > .ss-blk.ss-verb:first-child {
    clip-path: polygon(0 0, calc(100% - var(--chev)) 0, 100% 50%, calc(100% - var(--chev)) 100%, 0 100%);
    padding-left: 22px;
  }

  .ss-breakdown {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
    gap: 10px;
    margin-top: 20px;
    padding-top: 16px;
    border-top: 1px dashed rgba(255,255,255,0.12);
  }
  .ss-breakdown .ss-bd {
    display: flex; flex-direction: column; gap: 2px;
    font-family: var(--serif); font-size: 13px;
    color: rgba(255,255,255,0.6);
    line-height: 1.3;
  }
  .ss-breakdown .ss-bd .ss-jp {
    font-family: var(--sans-jp); font-weight: 700;
    font-size: 15px;
    color: rgba(255,255,255,0.92);
    letter-spacing: 0.02em;
  }
  .ss-breakdown .ss-bd .ss-role {
    font-family: var(--serif); font-style: italic;
    font-size: 12px;
    color: rgba(255,255,255,0.5);
  }
  .ss-breakdown .ss-bd.ss-w-noun .ss-jp { color: var(--block-noun); }
  .ss-breakdown .ss-bd.ss-w-verb .ss-jp { color: var(--block-verb); }
  .ss-breakdown .ss-bd.ss-w-part .ss-jp { color: var(--block-part); }
  .ss-breakdown .ss-bd.ss-w-stop .ss-jp { color: var(--block-stop); }

  /* ─── flexible order grid ──────────────────────────────────────────── */
  .ss-reorder-list {
    display: flex; flex-direction: column;
    gap: 14px;
    margin-top: 12px;
  }
  .ss-reorder-row {
    background: var(--paper-2);
    border: 1px solid rgba(141,102,48,0.22);
    border-radius: 10px;
    padding: 16px 18px;
    display: grid;
    grid-template-columns: 150px 1fr;
    gap: 18px;
    align-items: center;
  }
  @media (max-width: 600px) {
    .ss-reorder-row { grid-template-columns: 1fr; }
  }
  .ss-reorder-row .ss-nuance {
    font-family: var(--serif); font-style: italic;
    color: var(--ink-3); font-size: 13px;
    line-height: 1.4;
  }
  .ss-reorder-row .ss-nuance b {
    font-family: var(--serif); font-style: normal; font-weight: 600;
    color: var(--gold-dark); display: block;
    font-size: 12px; letter-spacing: 0.06em; text-transform: uppercase;
    margin-bottom: 3px;
    white-space: nowrap;
  }
  .ss-reorder-row .ss-jp-line {
    font-family: var(--serif-jp); font-weight: 500;
    font-size: 19px;
    color: var(--ink); line-height: 1.6;
    letter-spacing: 0.02em;
  }
  .ss-reorder-row .ss-jp-line .ss-pc { font-weight: 700; }
  .ss-reorder-row .ss-jp-line .ss-pc.ss-wa   { color: var(--pc-wa); }
  .ss-reorder-row .ss-jp-line .ss-pc.ss-ga   { color: var(--pc-ga); }
  .ss-reorder-row .ss-jp-line .ss-pc.ss-o    { color: var(--pc-o);  }
  .ss-reorder-row .ss-jp-line .ss-pc.ss-ni   { color: var(--pc-ni); }
  .ss-reorder-row .ss-jp-line .ss-pc.ss-de   { color: var(--pc-de); }
  .ss-reorder-row .ss-jp-line .ss-pc.ss-no   { color: var(--pc-no); }
  .ss-reorder-row .ss-jp-line .ss-pc.ss-to   { color: var(--pc-to); }
  .ss-reorder-row .ss-jp-line .ss-pc.ss-kara { color: var(--pc-kara); }
  .ss-reorder-row .ss-jp-line .ss-pc.ss-made { color: var(--pc-made); }
  .ss-reorder-row .ss-jp-line .ss-verb-u { border-bottom: 2px solid var(--block-verb); padding-bottom: 1px; }
  .ss-reorder-row .ss-jp-line .ss-trans {
    font-family: var(--serif); font-style: italic; font-size: 13px;
    color: var(--ink-3); margin-top: 4px;
  }

  /* ─── rule pills (the two rules) ───────────────────────────────────── */
  .ss-rules-two {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 18px;
    margin-top: 12px;
  }
  @media (max-width: 600px) { .ss-rules-two { grid-template-columns: 1fr; } }
  .ss-rule-pill {
    position: relative;
    background: var(--paper-2);
    border-radius: 10px;
    border: 1px solid rgba(141,102,48,0.22);
    padding: 22px 22px 22px 70px;
    overflow: hidden;
  }
  .ss-rule-pill .ss-num {
    position: absolute; left: 18px; top: 22px;
    width: 38px; height: 38px;
    border-radius: 50%;
    background: var(--gold);
    color: #fff;
    font-family: var(--serif-jp); font-weight: 700;
    font-size: 18px;
    display: flex; align-items: center; justify-content: center;
    box-shadow: 0 3px 0 var(--gold-dark);
  }
  .ss-rule-pill h4 {
    font-family: var(--serif-jp); font-weight: 700;
    font-size: 16px; color: var(--ink);
    margin: 0 0 6px;
  }
  .ss-rule-pill p {
    font-family: var(--serif); font-size: 14px;
    color: var(--ink-2); line-height: 1.55;
    margin: 0;
  }
  .ss-rule-pill p b { color: var(--ink); font-weight: 600; }

  /* ─── confusing pairs ───────────────────────────────────────────────── */
  .ss-pairs {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: 18px;
    margin-top: 18px;
  }
  .ss-pair {
    background: var(--paper-2);
    border: 1px solid rgba(141,102,48,0.22);
    border-radius: 10px;
    padding: 20px;
  }
  .ss-pair-head {
    display: flex; align-items: center; gap: 10px;
    margin-bottom: 14px;
    font-family: var(--serif); font-style: italic;
    color: var(--ink-3); font-size: 14px;
  }
  .ss-pair-chip {
    width: 52px; height: 52px;
    display: inline-flex; align-items: center; justify-content: center;
    font-family: var(--serif-jp); font-weight: 700;
    font-size: 22px;
    background: var(--ss-pc-bg, var(--block-part));
    color: #fff;
    border-radius: 50%;
    text-shadow: 0 1px 2px rgba(0,0,0,0.4), 0 0 6px rgba(0,0,0,0.25);
  }
  .ss-pair-chip.ss-wa { --ss-pc-bg: var(--pc-wa); }
  .ss-pair-chip.ss-ga { --ss-pc-bg: var(--pc-ga); }
  .ss-pair-chip.ss-o  { --ss-pc-bg: var(--pc-o);  }
  .ss-pair-chip.ss-ni { --ss-pc-bg: var(--pc-ni); }
  .ss-pair-chip.ss-de { --ss-pc-bg: var(--pc-de); }
  .ss-pair-chip.ss-e  { --ss-pc-bg: var(--pc-e);  }
  .ss-pair-vs {
    font-family: var(--serif); font-style: italic;
    color: var(--ink-3); font-size: 14px;
  }
  .ss-pair-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
    margin-bottom: 12px;
  }
  .ss-pair-col h5 {
    font-family: var(--serif); font-weight: 600;
    font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase;
    color: var(--ink-3); margin: 0 0 6px;
  }
  .ss-pair-col p {
    font-family: var(--serif); font-size: 14px;
    color: var(--ink-2); line-height: 1.5;
    margin: 0 0 8px;
  }
  .ss-pair-col p b { color: var(--ink); font-weight: 600; }
  .ss-pair-ex {
    font-family: var(--serif-jp); font-size: 15px;
    color: var(--ink);
    background: var(--paper);
    border-radius: 6px;
    padding: 6px 10px;
    border-left: 2px solid var(--gold-soft);
    margin-bottom: 4px;
  }
  .ss-pair-ex .ss-pc { font-weight: 700; }
  .ss-pair-ex .ss-pc.ss-wa { color: var(--pc-wa); }
  .ss-pair-ex .ss-pc.ss-ga { color: var(--pc-ga); }
  .ss-pair-ex .ss-pc.ss-ni { color: var(--pc-ni); }
  .ss-pair-ex .ss-pc.ss-de { color: var(--pc-de); }
  .ss-pair-ex .ss-pc.ss-e  { color: var(--pc-e); }
  .ss-pair-ex .ss-en {
    display: block;
    font-family: var(--serif); font-style: italic;
    color: var(--ink-3); font-size: 12px;
    margin-top: 2px;
  }

  /* ─── inline kana marker (for body text references) ──────────────── */
  .ss-pmark {
    display: inline-flex; align-items: center; justify-content: center;
    width: 1.8em; height: 1.8em;
    margin: 0 0.1em;
    vertical-align: middle;
    font-family: var(--serif-jp); font-weight: 700;
    background: var(--ss-pc-bg, var(--block-part));
    color: #fff;
    border-radius: 50%;
    line-height: 1;
    text-shadow: 0 1px 2px rgba(0,0,0,0.4), 0 0 4px rgba(0,0,0,0.25);
  }
  .ss-pmark.ss-wa   { --ss-pc-bg: var(--pc-wa); }
  .ss-pmark.ss-ga   { --ss-pc-bg: var(--pc-ga); }
  .ss-pmark.ss-o    { --ss-pc-bg: var(--pc-o);  }
  .ss-pmark.ss-ni   { --ss-pc-bg: var(--pc-ni); }
  .ss-pmark.ss-de   { --ss-pc-bg: var(--pc-de); }
  .ss-pmark.ss-e    { --ss-pc-bg: var(--pc-e);  }
  .ss-pmark.ss-no   { --ss-pc-bg: var(--pc-no); }
  .ss-pmark.ss-to   { --ss-pc-bg: var(--pc-to); }
  .ss-pmark.ss-mo   { --ss-pc-bg: var(--pc-mo); }
  .ss-pmark.ss-kara { --ss-pc-bg: var(--pc-kara); width: 2.4em; font-size: 0.85em; }
  .ss-pmark.ss-made { --ss-pc-bg: var(--pc-made); width: 2.4em; font-size: 0.85em; }
  .ss-pmark.ss-ya   { --ss-pc-bg: var(--pc-ya); }

  /* ─── footnote paragraphs ──────────────────────────────────────────── */
  .ss-footnote {
    font-family: var(--serif); font-style: italic; font-size: 14px;
    color: var(--ink-3);
    margin-top: 18px;
    max-width: 64ch;
    line-height: 1.5;
  }
  .ss-footnote i { color: var(--ink-2); }

  /* ─── final call-to-action ──────────────────────────────────────────── */
  .ss-end-cta {
    text-align: center;
    padding: 32px 24px;
    margin-top: 20px;
    background:
      radial-gradient(circle at 50% 0%, rgba(180,138,74,0.10), transparent 70%),
      var(--paper-2);
    border-radius: 10px;
    border: 1px solid rgba(141,102,48,0.22);
  }
  .ss-end-cta h3 {
    font-family: var(--serif-jp); font-weight: 600;
    font-size: 22px; margin: 0 0 8px;
    color: var(--ink);
  }
  .ss-end-cta p {
    font-family: var(--serif); font-style: italic;
    color: var(--ink-2); font-size: 15px;
    margin: 0 0 16px;
  }
  .ss-btn-row { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }
  .ss-btn {
    display: inline-flex; align-items: center; gap: 8px;
    padding: 10px 22px; border-radius: 8px;
    background: var(--gold); color: white;
    border: 1px solid var(--gold-dark);
    font-family: var(--serif); font-size: 14px;
    text-decoration: none;
    transition: all .15s;
    box-shadow: 0 3px 0 var(--gold-dark);
    cursor: pointer;
  }
  .ss-btn:hover { transform: translateY(-1px); box-shadow: 0 4px 0 var(--gold-dark); }
  .ss-btn.ss-ghost {
    background: transparent;
    color: var(--ink-2);
    border: 1px dashed rgba(141,102,48,0.4);
    box-shadow: none;
  }
  .ss-btn.ss-ghost:hover { background: rgba(180,138,74,0.10); box-shadow: none; transform: none; }

  /* ───────────────────────────────────────────────────────────────────
     Days & Time page — last basics page. Visual language mixes the
     numbered sections from sentence-structure (large kanji numerals
     anchoring each section head) with the badge-and-table chrome
     from numbers. Page-local tokens at the top so future tweaks land
     in one place.
     ──────────────────────────────────────────────────────────────── */
  .dt-page {
    --dt-paper:        var(--paper);
    --dt-paper-2:      var(--paper-2);
    --dt-ink:          var(--ink);
    --dt-ink-2:        var(--ink-2);
    --dt-ink-3:        var(--ink-3);
    --dt-gold:         var(--gold);
    --dt-gold-soft:    var(--gold-soft);
    --dt-gold-dark:    var(--gold-dark);
    /* Element accents — one hue per elemental kanji. Subtle, all in
       the same paper/ink family, so the grid reads as a series and
       not a rainbow. */
    --dt-elem-sun:     #c97a2c;
    --dt-elem-moon:    #6f7a94;
    --dt-elem-fire:    #b04a3a;
    --dt-elem-water:   #3a7a94;
    --dt-elem-wood:    #4a7a4a;
    --dt-elem-metal:   #a07a2a;
    --dt-elem-earth:   #8a6a4a;
  }

  /* ─── section chrome — no kanji-numeral chips, just title + lede ───
     Earlier versions had a circular kanji-numeral chip (一 / 二 / 三)
     leading each section. Dropped because three sections don't need
     ceremonial numbering — the title and lede do the work, and the
     left-margin was eating horizontal space the day grid needed for
     all 7 cards. */
  .dt-section { margin-bottom: 56px; }
  .dt-section-head {
    display: flex;
    align-items: flex-start;
    gap: 18px;
    margin-bottom: 22px;
    flex-wrap: wrap;
  }
  .dt-section-title {
    margin: 0;
    font-family: var(--font-title);
    font-size: clamp(22px, 2.4vw, 28px);
    font-weight: 600;
    color: var(--dt-ink);
    letter-spacing: -0.005em;
    line-height: 1.15;
    flex: 1;
    min-width: 240px;
  }
  .dt-section-title em {
    display: block;
    margin-top: 4px;
    font-family: var(--serif);
    font-style: italic;
    font-size: 14px;
    font-weight: 400;
    color: var(--dt-ink-3);
    letter-spacing: 0.01em;
  }
  .dt-section-lede {
    flex-basis: 100%;
    margin: 0;
    font-family: var(--serif);
    font-size: 15px;
    line-height: 1.6;
    color: var(--dt-ink-2);
  }
  .dt-section-lede b { color: var(--dt-ink); font-weight: 600; }
  .dt-section-footnote {
    margin: 14px 0 0;
    font-family: var(--serif);
    font-style: italic;
    font-size: 13px;
    color: var(--dt-ink-3);
    line-height: 1.55;
  }
  .dt-section-footnote .ja {
    font-style: normal;
    font-family: var(--serif-jp);
    color: var(--dt-ink-2);
  }
  .dt-section-footnote b { color: var(--dt-ink); font-weight: 600; }

  /* ─── block labels — replace what was .dt-card-eyebrow ─────────────
     The page used to wrap each content chunk in a paper card with an
     eyebrow label. Distillation pass dropped the cards; the label
     survives as a subhead sitting directly on the page background. */
  .dt-block-label {
    margin: 24px 0 12px;
    padding-bottom: 8px;
    border-bottom: 1px dashed rgba(141,102,48,0.25);
    font-family: var(--serif);
    font-style: italic;
    font-size: 13px;
    color: var(--dt-ink-3);
    letter-spacing: 0.04em;
  }
  .dt-block-label .ja {
    font-style: normal;
    font-family: var(--serif-jp);
    color: var(--dt-ink-2);
    font-weight: 600;
  }

  /* ─── 一. Days of the week — image-first, 7 across ──────────────────
     Image takes the top slot (1:1 frame); body below carries the
     compound kanji, kana, and a small elemental + EN meta line.
     The per-day element-accent stripe moves OFF the card (the image
     now does the visual identification work) — we keep an
     elemental color on the small "日" inline glyph so the variety
     still threads through the row. */
  .dt-day-grid {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 8px;
  }
  .dt-day-card {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 8px;
    background: var(--dt-paper);
    /* Explicit 1px border on ALL sides — earlier iterations of this
       page used a colored border-top stripe per row that fought the
       images for visual primacy. Forcing the border declaration here
       makes sure no leftover stripe survives a partial CSS cache. */
    border: 1px solid var(--paper-edge);
    border-top-width: 1px;
    border-radius: 8px;
    text-align: center;
    transition: border-color .15s, box-shadow .15s, transform .15s;
    min-width: 0;
  }
  .dt-day-card:hover {
    border-color: var(--dt-gold-soft);
    box-shadow: 0 4px 12px rgba(141,102,48,0.10);
    transform: translateY(-1px);
  }
  /* Color the inline elemental glyph per-row via nth-child. The image
     itself carries the day's primary visual identity; the elemental
     hue is a quiet thread under it, not a competing stripe. */
  .dt-day-card:nth-child(1) { --dt-elem: var(--dt-elem-sun); }
  .dt-day-card:nth-child(2) { --dt-elem: var(--dt-elem-moon); }
  .dt-day-card:nth-child(3) { --dt-elem: var(--dt-elem-fire); }
  .dt-day-card:nth-child(4) { --dt-elem: var(--dt-elem-water); }
  .dt-day-card:nth-child(5) { --dt-elem: var(--dt-elem-wood); }
  .dt-day-card:nth-child(6) { --dt-elem: var(--dt-elem-metal); }
  .dt-day-card:nth-child(7) { --dt-elem: var(--dt-elem-earth); }
  .dt-day-image {
    aspect-ratio: 1 / 1;
    width: 100%;
    background: var(--dt-paper-2);
    border-radius: 6px;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .dt-day-image image-slot {
    display: block;
    width: 100%;
    height: 100%;
    border-radius: 6px;
  }
  .dt-day-image image-slot::part(img),
  .dt-day-image image-slot::part(frame) {
    border-radius: 6px;
  }
  .dt-day-body {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    padding: 4px 0;
    min-width: 0;
  }
  .dt-day-ja {
    font-family: var(--serif-jp);
    font-weight: 600;
    font-size: 16px;
    color: var(--dt-ink);
    line-height: 1.05;
    letter-spacing: -0.01em;
  }
  .dt-day-kana {
    font-family: var(--serif-jp);
    font-size: 11px;
    color: var(--dt-ink-3);
    letter-spacing: 0.01em;
    line-height: 1.2;
  }
  .dt-day-meta {
    margin-top: 4px;
    display: inline-flex;
    align-items: baseline;
    gap: 5px;
    font-family: var(--font-title);
    font-size: 11px;
    color: var(--dt-ink-2);
    line-height: 1.2;
  }
  .dt-day-elem {
    font-family: var(--serif-jp);
    font-weight: 700;
    font-size: 13px;
    color: var(--dt-elem, var(--dt-ink));
    line-height: 1;
  }
  .dt-day-sep {
    color: var(--dt-ink-4, var(--dt-ink-3));
    font-weight: 400;
  }
  .dt-day-en {
    font-weight: 600;
  }
  /* Below the desktop-wide breakpoint, fall back to a wrapping grid
     since 7 narrow cards become unreadable. 4 then 2 at the smallest. */
  @media (max-width: 980px) {
    .dt-day-grid { grid-template-columns: repeat(4, 1fr); }
  }
  @media (max-width: 560px) {
    .dt-day-grid { grid-template-columns: repeat(2, 1fr); }
  }

  /* Subsection title — used inside Section 1 to introduce the
     months subsection (the formula card + irregulars below it).
     Mirrors the section-title family but at a lower hierarchy
     level so it reads as "section heading"-style, not as a peer
     of the H2. */
  .dt-subsection-title {
    margin: 36px 0 12px;
    font-family: var(--font-title);
    font-size: clamp(18px, 2vw, 22px);
    font-weight: 600;
    color: var(--dt-ink);
    letter-spacing: -0.005em;
    line-height: 1.2;
  }
  .dt-subsection-title em {
    display: block;
    margin-top: 3px;
    font-family: var(--serif);
    font-style: italic;
    font-size: 13px;
    font-weight: 400;
    color: var(--dt-ink-3);
    letter-spacing: 0.02em;
  }

  /* ─── 曜 construction diagram ──────────────────────────────────────
     Visual equation primes the day grid: [elemental kanji] + [曜
     image] + [日] = day name. The image of 曜 anchors the centerpiece
     so the unfamiliar character has a face attached to it; the seven
     elementals on the left visually communicate "any one of these."
     Squint test reads as four blocks connected by operators (+ + = )
     — clean four-beat rhythm. */
  .dt-yo-build {
    display: grid;
    grid-template-columns: 1.2fr auto 1.3fr auto 0.7fr auto 1.2fr;
    gap: 8px;
    align-items: center;
    padding: 22px;
    margin-bottom: 14px;
    background: var(--dt-paper);
    border: 1px solid var(--paper-edge);
    border-radius: 8px;
    box-shadow: 0 2px 10px rgba(141,102,48,0.06);
  }
  .dt-yo-build-plus,
  .dt-yo-build-eq {
    font-family: var(--serif);
    font-weight: 400;
    font-size: clamp(20px, 2.6vw, 28px);
    color: var(--dt-gold-dark);
    text-align: center;
    user-select: none;
  }
  /* Slot — the swap-position holding all 7 elementals stacked as a
     vertical chip column. Communicates "any of these slots in here." */
  .dt-yo-build-slot,
  .dt-yo-build-yo,
  .dt-yo-build-day,
  .dt-yo-build-result {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    min-width: 0;
  }
  .dt-yo-build-slot-eyebrow,
  .dt-yo-build-yo-eyebrow,
  .dt-yo-build-day-eyebrow,
  .dt-yo-build-result-eyebrow {
    font-family: var(--serif);
    font-style: italic;
    font-size: 11px;
    color: var(--dt-ink-3);
    letter-spacing: 0.05em;
  }
  .dt-yo-build-elements {
    display: grid;
    grid-template-columns: repeat(4, auto);
    gap: 3px;
    padding: 8px;
    background: var(--dt-paper-2);
    border: 1.5px dashed var(--dt-gold-soft);
    border-radius: 8px;
  }
  .dt-yo-build-elem {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 26px; height: 26px;
    font-family: var(--serif-jp);
    font-weight: 700;
    font-size: 16px;
    color: var(--dt-elem, var(--dt-ink));
    line-height: 1;
  }
  .dt-yo-build-slot-label,
  .dt-yo-build-yo-label,
  .dt-yo-build-day-label,
  .dt-yo-build-result-ex {
    font-family: var(--serif);
    font-style: italic;
    font-size: 11px;
    color: var(--dt-ink-3);
    text-align: center;
  }
  .dt-yo-build-yo-label .ja,
  .dt-yo-build-day-label .ja,
  .dt-yo-build-result-ex .ja {
    font-style: normal;
    font-family: var(--serif-jp);
    color: var(--dt-ink-2);
  }
  /* The 曜 image — the centerpiece of the equation. Larger than the
     surrounding tiles since this is the kanji the learner doesn't
     yet know by sight. */
  .dt-yo-build-yo-image {
    width: 96px; height: 96px;
    background: var(--dt-paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 8px;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .dt-yo-build-yo-image image-slot {
    width: 100%; height: 100%;
    display: block;
    border-radius: 8px;
  }
  .dt-yo-build-yo-image image-slot::part(img),
  .dt-yo-build-yo-image image-slot::part(frame) {
    border-radius: 8px;
  }
  /* Closing 日 — sits as a single kanji chip, smaller than the 曜
     centerpiece but heavier than the slot of elementals. */
  .dt-yo-build-day-glyph {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 54px; height: 54px;
    background: var(--dt-paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 8px;
    font-family: var(--serif-jp);
    font-weight: 700;
    font-size: 32px;
    color: var(--dt-ink);
    line-height: 1;
  }
  /* Result — the final compound. The "X" slot is dashed-gold like
     the formula card's N slot; the 曜日 tail is solid ink. */
  .dt-yo-build-result-formula {
    display: inline-flex;
    align-items: baseline;
    gap: 2px;
    padding: 8px 10px;
    background: var(--dt-paper-2);
    border-radius: 8px;
  }
  .dt-yo-build-result-slot {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 26px;
    padding: 2px 6px;
    background: var(--dt-paper);
    border: 1.5px dashed var(--dt-gold-soft);
    border-radius: 4px;
    font-family: var(--serif);
    font-style: italic;
    font-weight: 600;
    font-size: 18px;
    color: var(--dt-gold-dark);
    line-height: 1;
  }
  .dt-yo-build-result-tail {
    font-family: var(--serif-jp);
    font-weight: 700;
    font-size: 24px;
    color: var(--dt-ink);
    line-height: 1;
  }
  .dt-yo-build-explain {
    margin: 0 0 32px;
    font-family: var(--serif);
    font-size: 13.5px;
    line-height: 1.6;
    color: var(--dt-ink-3);
    font-style: italic;
  }
  .dt-yo-build-explain .ja {
    font-style: normal;
    font-family: var(--serif-jp);
    color: var(--dt-ink-2);
  }
  /* Mobile collapse — the 7-block diagram becomes too narrow on
     small screens. Stack the parts and use a thin dashed line
     between them instead of the +/= operators (which would float
     awkwardly mid-stack). */
  @media (max-width: 760px) {
    .dt-yo-build {
      grid-template-columns: 1fr;
      gap: 14px;
      padding: 18px 16px;
    }
    .dt-yo-build-plus,
    .dt-yo-build-eq {
      font-size: 18px;
    }
  }

  /* ─── 月 — formula card + three exceptions ───────────────────────
     Replaced the 12-cell grid (pattern-as-data default) with a
     two-part treatment: the rule as a visual equation on top,
     the three exceptions as larger highlighted cards below.
     The squint test should read "rule → exceptions" — two beats,
     not twelve. */
  .dt-month-formula-card {
    margin-bottom: 24px;
    padding: 24px 28px;
    background: var(--dt-paper);
    border: 1px solid var(--paper-edge);
    border-radius: 8px;
    box-shadow: 0 2px 8px rgba(141,102,48,0.06);
  }
  .dt-month-formula {
    display: flex;
    align-items: baseline;
    justify-content: center;
    flex-wrap: wrap;
    gap: 6px 12px;
    padding: 16px 0 18px;
    font-family: var(--serif-jp);
    color: var(--dt-ink);
    line-height: 1;
  }
  .dt-month-formula-n {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 38px;
    padding: 6px 10px;
    background: var(--dt-paper-2);
    border: 1.5px dashed var(--dt-gold-soft);
    border-radius: 6px;
    font-family: var(--serif);
    font-style: italic;
    font-weight: 600;
    font-size: 22px;
    color: var(--dt-gold-dark);
    line-height: 1;
  }
  .dt-month-formula-glyph {
    font-weight: 700;
    font-size: clamp(32px, 4vw, 44px);
    color: var(--dt-ink);
  }
  .dt-month-formula-eq {
    margin: 0 6px;
    font-family: var(--serif);
    font-weight: 400;
    font-size: clamp(28px, 3.5vw, 36px);
    color: var(--dt-gold-dark);
  }
  .dt-month-formula-suffix {
    font-weight: 600;
    font-size: clamp(22px, 2.8vw, 28px);
    color: var(--dt-ink);
  }
  .dt-month-formula-explain {
    margin: 0;
    padding-top: 14px;
    border-top: 1px dashed rgba(141,102,48,0.25);
    text-align: center;
    font-family: var(--serif);
    font-size: 14px;
    line-height: 1.7;
    color: var(--dt-ink-2);
  }
  .dt-month-formula-explain b { color: var(--dt-ink); font-weight: 600; }
  .dt-month-formula-explain .ja {
    font-family: var(--serif-jp);
    color: var(--dt-ink);
  }
  /* Examples sub-line — sits on its own row below the rule sentence.
     Smaller and slightly more spaced so it reads as "here's the
     pattern in action," not as a continuation of the rule. */
  .dt-month-formula-examples {
    display: inline-block;
    margin-top: 6px;
    font-size: 13px;
    color: var(--dt-ink-3);
    letter-spacing: 0.01em;
  }
  .dt-month-formula-examples .ja {
    color: var(--dt-ink-2);
  }

  .dt-month-irr-label {
    margin: 24px 0 12px;
    font-family: var(--serif);
    font-size: 13px;
    color: var(--dt-ink-2);
    letter-spacing: 0.04em;
    font-weight: 600;
  }
  .dt-month-irr-label em {
    font-family: var(--serif);
    font-style: italic;
    font-weight: 400;
    color: var(--dt-ink-3);
  }
  .dt-month-irr-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 12px;
  }
  .dt-month-irr-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    padding: 18px 12px 14px;
    background: linear-gradient(180deg, var(--dt-paper) 0%, rgba(180,138,74,0.08) 100%);
    border: 1.5px solid var(--dt-gold);
    border-radius: 8px;
    text-align: center;
  }
  .dt-month-irr-glyph {
    font-family: var(--serif-jp);
    font-weight: 700;
    font-size: 36px;
    color: var(--dt-gold-dark);
    line-height: 1;
  }
  .dt-month-irr-kana {
    font-family: var(--serif-jp);
    font-weight: 600;
    font-size: 18px;
    color: var(--dt-gold-dark);
    margin-top: 2px;
  }
  .dt-month-irr-en {
    font-family: var(--serif);
    font-style: italic;
    font-size: 13px;
    color: var(--dt-ink-3);
  }
  .dt-month-irr-wrong {
    margin-top: 6px;
    padding-top: 8px;
    border-top: 1px dashed rgba(141,102,48,0.3);
    width: 100%;
    display: flex;
    align-items: baseline;
    justify-content: center;
    gap: 6px;
    font-size: 12px;
  }
  .dt-month-irr-x {
    font-family: var(--serif);
    font-style: italic;
    color: var(--dt-ink-3);
    text-decoration: line-through;
    text-decoration-color: var(--dt-ink-4, var(--dt-ink-3));
  }
  .dt-month-irr-wrong .ja {
    font-family: var(--serif-jp);
    color: var(--dt-ink-3);
    text-decoration: line-through;
    text-decoration-color: var(--dt-ink-3);
  }
  .dt-month-footnote {
    margin: 12px 0 0;
    font-family: var(--serif);
    font-style: italic;
    font-size: 13px;
    color: var(--dt-ink-3);
    line-height: 1.55;
  }
  .dt-month-footnote .ja {
    font-style: normal;
    font-family: var(--serif-jp);
    color: var(--dt-ink-2);
  }
  @media (max-width: 720px) {
    .dt-month-formula-card { padding: 18px 16px; }
    .dt-month-irr-grid { grid-template-columns: 1fr; }
  }

  /* ─── 二. Reading dates — three neutral stamps + dl rules ────────────
     Stamps lead with the 年/月/日 marker (the LESSON), the numeral
     sits below as data. No colored top-stripes — the colors didn't
     mean anything and read as decorative KPI-card chrome. Rules are
     a definition list flowing on the page background, no boxes. */
  .dt-date-row {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 16px;
    margin-bottom: 16px;
  }
  .dt-date-stamp {
    background: var(--dt-paper-2);
    border-radius: 8px;
    padding: 20px 16px 16px;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
  }
  .dt-date-marker {
    font-family: var(--serif-jp);
    font-weight: 700;
    font-size: clamp(40px, 5vw, 56px);
    color: var(--dt-ink);
    line-height: 1;
  }
  .dt-date-num {
    font-family: var(--serif);
    font-size: clamp(22px, 3vw, 32px);
    font-weight: 400;
    color: var(--dt-ink-2);
    line-height: 1;
    font-feature-settings: 'tnum' 1;
  }
  .dt-date-label {
    margin-top: 6px;
    font-family: var(--serif);
    font-style: italic;
    font-size: 12px;
    color: var(--dt-ink-3);
    letter-spacing: 0.04em;
  }
  .dt-date-translation {
    display: flex;
    align-items: baseline;
    justify-content: center;
    flex-wrap: wrap;
    gap: 8px 24px;
    padding: 12px 0;
    margin-bottom: 24px;
    border-bottom: 1px dashed rgba(141,102,48,0.25);
  }
  .dt-date-trans-ja {
    font-family: var(--serif-jp);
    font-size: 18px;
    color: var(--dt-ink);
    letter-spacing: 0.02em;
  }
  .dt-date-trans-en {
    font-family: var(--serif);
    font-style: italic;
    font-size: 15px;
    color: var(--dt-ink-3);
  }
  /* Definition list — no boxes, no accent borders, just flowing text.
     dt holds the marker + kana; dd holds the explanation. */
  .dt-date-rules {
    margin: 0;
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 8px 24px;
    align-items: baseline;
  }
  .dt-date-rules dt {
    display: flex;
    align-items: baseline;
    gap: 10px;
    font-family: var(--serif-jp);
    color: var(--dt-ink);
    padding-top: 2px;
  }
  .dt-date-rule-marker {
    font-weight: 700;
    font-size: 22px;
    line-height: 1;
    color: var(--dt-ink);
  }
  .dt-date-rule-kana {
    font-size: 13px;
    color: var(--dt-ink-3);
    letter-spacing: 0.04em;
  }
  .dt-date-rules dd {
    margin: 0;
    font-family: var(--serif);
    font-size: 14px;
    line-height: 1.55;
    color: var(--dt-ink-2);
  }
  .dt-date-rules dd .ja {
    font-family: var(--serif-jp);
    color: var(--dt-ink);
  }

  /* ─── 三. Telling time ──────────────────────────────────────────── */
  .dt-hour-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    gap: 10px;
  }
  .dt-hour-cell {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 3px;
    padding: 10px 8px;
    background: var(--dt-paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 6px;
    position: relative;
  }
  .dt-hour-cell.is-irregular {
    border-color: var(--dt-gold);
    background: linear-gradient(180deg, var(--dt-paper-2) 0%, rgba(180,138,74,0.08) 100%);
  }
  .dt-hour-num {
    font-family: var(--serif);
    font-size: 11px;
    color: var(--dt-ink-3);
    font-feature-settings: 'tnum' 1;
  }
  .dt-hour-ja {
    font-family: var(--serif-jp);
    font-weight: 600;
    font-size: 22px;
    color: var(--dt-ink);
    line-height: 1;
  }
  .dt-hour-cell.is-irregular .dt-hour-ja { color: var(--dt-gold-dark); }
  .dt-hour-kana {
    font-family: var(--serif-jp);
    font-size: 13px;
    color: var(--dt-ink-2);
  }
  .dt-hour-cell.is-irregular .dt-hour-kana { color: var(--dt-gold-dark); font-weight: 600; }
  .dt-hour-note {
    font-family: var(--serif);
    font-style: italic;
    font-size: 10px;
    color: var(--dt-ink-3);
    margin-top: 2px;
  }
  /* Minutes */
  .dt-min-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
    gap: 10px;
    margin-bottom: 16px;
  }
  .dt-min-cell {
    display: grid;
    grid-template-columns: auto 1fr auto;
    grid-template-rows: auto auto;
    gap: 2px 8px;
    align-items: center;
    padding: 10px 12px;
    background: var(--dt-paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 6px;
  }
  .dt-min-num {
    grid-row: 1;
    grid-column: 1;
    font-family: var(--serif);
    font-size: 11px;
    color: var(--dt-ink-3);
    font-feature-settings: 'tnum' 1;
  }
  .dt-min-ja {
    grid-row: 1;
    grid-column: 2;
    font-family: var(--serif-jp);
    font-weight: 600;
    font-size: 16px;
    color: var(--dt-ink);
    line-height: 1;
  }
  .dt-min-stem {
    grid-row: 1;
    grid-column: 3;
    align-self: start;
    padding: 1px 6px;
    border-radius: 3px;
    font-family: var(--serif);
    font-style: italic;
    font-size: 10px;
    letter-spacing: 0.04em;
  }
  .dt-min-stem-fun {
    background: rgba(58,122,148,0.14);
    color: #2a5b94;
  }
  .dt-min-stem-pun {
    background: rgba(176,74,58,0.14);
    color: #8a2538;
  }
  .dt-min-kana {
    grid-row: 2;
    grid-column: 1 / 3;
    font-family: var(--serif-jp);
    font-size: 12px;
    color: var(--dt-ink-2);
  }
  .dt-min-alt {
    grid-row: 2;
    grid-column: 3;
    display: inline-flex;
    align-items: baseline;
    gap: 3px;
    padding: 1px 5px;
    background: rgba(180,138,74,0.14);
    border-radius: 3px;
  }
  .dt-min-alt-ja {
    font-family: var(--serif-jp);
    font-weight: 700;
    font-size: 13px;
    color: var(--dt-gold-dark);
  }
  .dt-min-alt-kana {
    font-family: var(--serif-jp);
    font-size: 10px;
    color: var(--dt-gold-dark);
  }
  /* Minutes explanation — flowing prose paragraph, no row-list or
     paper-2 frame. The fun/pun chips inline carry the visual cue
     they had before; everything else is read normally. */
  .dt-min-explain-prose {
    margin: 12px 0 0;
    font-family: var(--serif);
    font-size: 14px;
    line-height: 1.65;
    color: var(--dt-ink-2);
  }
  .dt-min-explain-prose .ja {
    font-family: var(--serif-jp);
    color: var(--dt-ink);
  }
  .dt-min-explain-prose .dt-min-stem {
    margin: 0 4px;
    /* The chips are the same inline padding+radius from .dt-min-stem
       already — they sit nicely in flowing text without extra glue. */
  }

  /* AM / PM block — two side-by-side blocks, no divider. */
  .dt-ampm-row {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 24px;
    align-items: start;
    padding: 8px 0 16px;
  }
  .dt-ampm-block {
    text-align: center;
  }
  .dt-ampm-ja {
    font-family: var(--serif-jp);
    font-weight: 700;
    font-size: 32px;
    color: var(--dt-ink);
    line-height: 1;
  }
  .dt-ampm-block.dt-ampm-am .dt-ampm-ja { color: var(--dt-elem-sun); }
  .dt-ampm-block.dt-ampm-pm .dt-ampm-ja { color: var(--dt-elem-moon); }
  .dt-ampm-kana {
    font-family: var(--serif-jp);
    font-size: 14px;
    color: var(--dt-ink-3);
    margin-top: 4px;
  }
  .dt-ampm-en {
    margin-top: 6px;
    font-family: var(--font-title);
    font-weight: 600;
    font-size: 14px;
    color: var(--dt-ink-2);
  }
  .dt-ampm-footnote {
    margin: 0;
    font-family: var(--serif);
    font-style: italic;
    font-size: 13px;
    color: var(--dt-ink-3);
    line-height: 1.55;
  }
  .dt-ampm-footnote .ja {
    font-style: normal;
    font-family: var(--serif-jp);
    color: var(--dt-ink-2);
  }

  /* ─── Interactive clock ────────────────────────────────────────────
     SVG analog face on the left, numeric inputs + Japanese readout on
     the right. Two-column on desktop; stacks on mobile. The clock
     hands are SVG <line> elements with cursor:grab — the JS wiring
     in renderWritingDatetime handles drag + numeric input syncing. */
  .dt-clock-card {
    display: grid;
    grid-template-columns: minmax(220px, 280px) 1fr;
    gap: 24px;
    align-items: start;
    padding: 20px 24px;
    background: var(--dt-paper);
    border: 1px solid var(--paper-edge);
    border-radius: 8px;
    box-shadow: 0 2px 10px rgba(141,102,48,0.06);
  }
  .dt-clock-left {
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .dt-clock-svg {
    width: 100%;
    max-width: 260px;
    height: auto;
    touch-action: none;
    user-select: none;
    -webkit-user-select: none;
  }
  .dt-clock-svg [data-clock-hand] {
    cursor: grab;
    transition: stroke .12s;
  }
  .dt-clock-svg [data-clock-hand]:hover { stroke: var(--dt-gold-dark); }
  .dt-clock-svg [data-clock-hand]:active { cursor: grabbing; }

  .dt-clock-right {
    display: flex;
    flex-direction: column;
    gap: 18px;
  }
  .dt-clock-inputs {
    display: flex;
    flex-wrap: wrap;
    gap: 10px 14px;
    align-items: stretch;
  }
  .dt-clock-input-group {
    flex: 1 1 110px;
    display: grid;
    grid-template-columns: auto 1fr;
    grid-template-rows: auto auto;
    grid-template-areas:
      "glyph label"
      "input input"
      "hint  hint";
    column-gap: 8px;
    align-items: center;
    padding: 10px 12px;
    background: var(--dt-paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 6px;
    cursor: text;
    transition: border-color .12s, box-shadow .12s;
  }
  .dt-clock-input-group:focus-within {
    border-color: var(--dt-gold);
    box-shadow: 0 0 0 2px rgba(180,138,74,0.18);
  }
  .dt-clock-input-glyph {
    grid-area: glyph;
    font-family: var(--serif-jp);
    font-weight: 700;
    font-size: 20px;
    color: var(--dt-ink);
    line-height: 1;
  }
  .dt-clock-input-label {
    grid-area: label;
    font-family: var(--serif);
    font-style: italic;
    font-size: 12px;
    color: var(--dt-ink-3);
    letter-spacing: 0.04em;
  }
  .dt-clock-input-group input {
    grid-area: input;
    width: 100%;
    margin-top: 4px;
    padding: 4px 0 0;
    border: none;
    background: transparent;
    border-bottom: 2px solid var(--paper-edge);
    font-family: var(--serif);
    font-size: 28px;
    font-weight: 600;
    color: var(--dt-ink);
    text-align: left;
    font-feature-settings: 'tnum' 1;
    outline: none;
    transition: border-color .12s;
    -moz-appearance: textfield;
  }
  .dt-clock-input-group input:focus { border-bottom-color: var(--dt-gold-dark); }
  .dt-clock-input-group input::-webkit-outer-spin-button,
  .dt-clock-input-group input::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
  }
  .dt-clock-input-hint {
    grid-area: hint;
    margin-top: 4px;
    font-family: var(--serif);
    font-style: italic;
    font-size: 10px;
    color: var(--dt-ink-4, var(--dt-ink-3));
    letter-spacing: 0.04em;
  }

  .dt-clock-ampm-btn {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 10px 14px;
    background: var(--dt-paper-2);
    border: 1px solid var(--paper-edge);
    border-radius: 6px;
    font-family: var(--font-title);
    font-weight: 600;
    font-size: 12px;
    color: var(--dt-ink-2);
    letter-spacing: 0.04em;
    cursor: pointer;
    transition: background .12s, border-color .12s, color .12s;
    align-self: center;
  }
  .dt-clock-ampm-btn:hover {
    background: var(--dt-paper);
    border-color: var(--dt-gold);
    color: var(--dt-ink);
  }
  .dt-clock-ampm-btn:active { transform: translateY(1px); }
  .dt-clock-ampm-icon {
    font-size: 16px;
    color: var(--dt-gold-dark);
  }

  .dt-clock-readout {
    padding: 16px 18px;
    background: linear-gradient(180deg, var(--dt-paper-2) 0%, rgba(180,138,74,0.06) 100%);
    border: 1px solid var(--dt-gold-soft);
    border-radius: 8px;
  }
  .dt-clock-readout-label {
    margin-bottom: 6px;
    font-family: var(--serif);
    font-style: italic;
    font-size: 12px;
    color: var(--dt-ink-3);
    letter-spacing: 0.04em;
  }
  .dt-clock-readout-label .ja {
    font-style: normal;
    font-family: var(--serif-jp);
    color: var(--dt-ink-2);
    font-weight: 600;
  }
  .dt-clock-ja {
    font-family: var(--serif-jp);
    font-weight: 700;
    font-size: clamp(20px, 2.6vw, 28px);
    color: var(--dt-ink);
    line-height: 1.2;
    letter-spacing: 0.02em;
  }
  .dt-clock-kana {
    margin-top: 4px;
    font-family: var(--serif-jp);
    font-size: 15px;
    color: var(--dt-ink-2);
    letter-spacing: 0.02em;
  }
  /* Gold marker tint — applied to 時 / 分 / 半 in the kanji readout
     and their kana equivalents (じ / ふん|ぷん / はん) in the kana
     readout. Visually splits the unit boundary so a long readout
     like 午後十二時五十五分 reads as PM + hour + minute instead of
     a single solid block. */
  .dt-clock-ja .dt-clock-mk,
  .dt-clock-kana .dt-clock-mk {
    color: var(--dt-gold-dark);
    margin-left: 0.04em;
  }
  .dt-clock-en {
    margin-top: 6px;
    font-family: var(--serif);
    font-style: italic;
    font-size: 14px;
    color: var(--dt-ink-3);
    font-feature-settings: 'tnum' 1;
  }
  .dt-clock-explain {
    margin: 14px 0 0;
    font-family: var(--serif);
    font-size: 14px;
    line-height: 1.6;
    color: var(--dt-ink-2);
  }
  .dt-clock-explain b { color: var(--dt-ink); font-weight: 600; }
  .dt-clock-explain .ja {
    font-family: var(--serif-jp);
    color: var(--dt-ink);
  }

  @media (max-width: 760px) {
    .dt-clock-card { grid-template-columns: 1fr; gap: 18px; padding: 18px 16px; }
    .dt-clock-svg { max-width: 220px; }
  }

  /* Responsive — collapse heavy multi-column grids */
  @media (max-width: 720px) {
    .dt-date-row         { grid-template-columns: 1fr; }
    .dt-ampm-row         { grid-template-columns: 1fr; gap: 16px; }
    .dt-date-rules       { grid-template-columns: 1fr; gap: 4px 0; }
    .dt-date-rules dt    { padding-top: 12px; }
  }

  /* ═════════════════════════════════════════════════════════════════
     SPEAKING SUB-SYSTEM
     Spec: docs/superpowers/specs/2026-05-28-speaking.{PRODUCT,DESIGN}.md
     Tokens declared inline so the rest of the project's CSS doesn't
     need to load them. Token names follow the speaking surface's
     world (--mic, --waveform-ink, --pitch-line).
     ═════════════════════════════════════════════════════════════════ */
  .speaking-studio {
    --mic-default:         #8c6a3a;
    --mic-default-hover:   #a0794a;
    --mic-recording:       #c34b30;
    --mic-recording-pulse: #e26544;
    --mic-disabled:        #cbbfa8;
    --waveform-original:   #8c6a3a;
    --waveform-user:       #7a9a5a;
    --score-chip:          #3a2e22;
    --score-dot-filled:    #8c6a3a;
    --score-dot-empty:     #d3c4ad;
    --pitch-line:          #8f3f2e;
    --pitch-line-user:     #5e7d3e;
    --pitch-pattern-heiban:    #a8843c;
    --pitch-pattern-atamadaka: #c34b30;
    --pitch-pattern-nakadaka:  #8c6a3a;
    --pitch-pattern-odaka:     #7a9a5a;
    display: grid;
    grid-template-columns: minmax(280px, 0.42fr) 1fr;
    grid-template-rows: auto 1fr;
    grid-template-areas:
      "portrait stage"
      "filmstrip stage";
    gap: clamp(24px, 2.6vw, 40px);
    padding: clamp(18px, 1.8vw, 30px) clamp(20px, 2vw, 36px) clamp(40px, 4vw, 72px);
    max-width: 1320px;
    margin: 0 auto;
    outline: none;
  }
  .speaking-studio:focus { outline: none; }

  .speaking-studio-head {
    grid-area: head;
    padding-bottom: clamp(8px, 1vw, 16px);
    border-bottom: 1px solid color-mix(in srgb, var(--ink) 8%, transparent);
  }
  .speaking-head-title {
    display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap;
    margin: 0;
    font: inherit;
  }
  .speaking-head-en {
    font-family: var(--font-menu-en);
    font-size: clamp(15px, 1.4vw, 22px);
    font-weight: 600;
    letter-spacing: 0.16em;
    color: var(--ink);
    text-transform: uppercase;
  }
  .speaking-head-dot { color: var(--ink-faint); }
  .speaking-head-ja {
    font-family: var(--font-title);
    font-size: clamp(16px, 1.5vw, 22px);
    color: var(--ink-soft);
  }
  .speaking-head-sub {
    margin: 6px 0 0;
    font-family: var(--font-en);
    font-style: italic;
    font-size: clamp(14px, 1.2vw, 16px);
    color: var(--ink-faint);
  }

  /* Watercolor portrait — left zone. Brushed-edge frame, vertical 3:4. */
  .speaking-portrait {
    grid-area: portrait;
    align-self: stretch;
  }
  .speaking-portrait-frame {
    position: relative;
    width: 100%;
    aspect-ratio: 3 / 4;
    background: var(--paper-soft, #ece3d2);
    border-radius: 4px;
    overflow: hidden;
    box-shadow: 0 1px 0 rgba(60,40,10,0.04), inset 0 0 0 1px rgba(141,102,48,0.10);
  }
  .speaking-portrait-frame::after {
    /* A subtle brush-edged inner border to evoke the brushed-edge surface
       pattern from the rest of the project without an SVG mask asset. */
    content: '';
    position: absolute; inset: 6px;
    border: 1px solid rgba(141,102,48,0.18);
    border-radius: 2px;
    pointer-events: none;
  }
  .speaking-portrait-frame image-slot,
  .speaking-portrait-frame image-slot::part(img),
  .speaking-portrait-frame image-slot::part(frame) {
    width: 100%; height: 100%; display: block;
    object-fit: cover;
  }
  .speaking-portrait-frame image-slot::part(frame) {
    background: transparent;
  }
  /* Fallback when portrait file 404s — paper-textured silhouette via
     gradient + the inherited paper palette. The image-slot's own
     :not([data-filled]) state shows ?  — override it for this surface. */
  .speaking-portrait-frame:has(image-slot:not([data-filled]))::before {
    content: '';
    position: absolute; inset: 0;
    background:
      radial-gradient(ellipse 60% 40% at 50% 38%,
        color-mix(in srgb, var(--ink) 22%, transparent),
        transparent 65%),
      radial-gradient(ellipse 40% 50% at 50% 78%,
        color-mix(in srgb, var(--ink) 18%, transparent),
        transparent 70%),
      var(--paper-soft, #ece3d2);
    opacity: 0.85;
    pointer-events: none;
  }

  /* Practice stage — right zone. */
  .speaking-stage {
    grid-area: stage;
    background: var(--paper-soft, #ece3d2);
    border-radius: 4px;
    padding: clamp(20px, 2.4vw, 36px);
    position: relative;
    box-shadow: inset 0 0 0 1px rgba(141,102,48,0.10);
    min-width: 0;
  }
  .speaking-stage::after {
    content: ''; position: absolute; inset: 6px;
    border: 1px solid rgba(141,102,48,0.10);
    border-radius: 2px; pointer-events: none;
  }
  /* Three zones: phrase counter (left), autoplay switch (centre), pitch
     chip (right). 1fr | auto | 1fr keeps the switch optically centred while
     the side items hug their edges. */
  .speaking-stage-eyebrow {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 10px;
    font-family: var(--font-menu-en);
    font-size: 12px; letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--ink-faint);
    margin-bottom: 18px;
  }
  .eyebrow-left { display: inline-flex; align-items: center; gap: 10px; min-width: 0; }
  .eyebrow-glyph {
    color: var(--accent-vermilion, #c34b30);
    font-size: 14px;
  }
  /* Autoplay — a real on/off switch (reuses .settings-toggle) with a label,
     centred in the row. */
  .speaking-autoplay { justify-self: center; display: inline-flex; align-items: center; gap: 7px; }
  .speaking-autoplay-label {
    font-family: var(--font-title); font-size: 12px; color: var(--ink-soft);
    letter-spacing: normal; text-transform: none; white-space: nowrap;
  }
  .speaking-stage-eyebrow .pitch-pattern-chip { justify-self: end; }

  /* Phrase + pitch contour */
  .speaking-phrase {
    display: flex; align-items: flex-start; justify-content: center;
    gap: clamp(12px, 1.4vw, 22px); flex-wrap: wrap;
    margin-bottom: 10px;
  }
  .phrase-mora-row {
    display: flex; flex-wrap: wrap;
    align-items: flex-end;
    gap: 0;
  }
  .pitch-mora-row {
    display: inline-flex;
    align-items: center;
    position: relative;
    /* Atomic word unit: the whole chunk wraps together or not at all, so a
       word/phrase never breaks across lines mid-mora. The .speaking-phrase
       flex container wraps BETWEEN chunks (at the chunk-gaps), so a long
       phrase like おれは ケンシンです breaks after the particle, keeping
       ケンシンです intact on the next line. */
    flex: 0 0 auto;
    white-space: nowrap;
    /* Room ABOVE for the overline (high mora) and BELOW for the
       underline (low mora). The glyph is centred between the two
       levels — the line's POSITION tells you the pitch, which reads
       far more clearly than packing every line into a band above. */
    padding-top: 0.46em;
    padding-bottom: 0.42em;
  }
  /* Contour SVG covers the FULL row box (top to bottom). Its proportional
     viewBox columns line up with the equal-width mora glyphs
     automatically, so kerning can change without the contour drifting.
     High lines land in the top padding (above the glyph), low lines in
     the bottom padding (below the glyph); vertical steps fall in the
     gaps between glyphs. */
  .pitch-contour-svg {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    z-index: 2;
    pointer-events: none;
    overflow: visible;
  }
  .pitch-mora {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* Wider columns than before (was 36px) — the kana at the large end
       of the clamp is ~48px wide, so 36px crammed them. ~0.95em + breathing
       room reads cleanly. The contour follows automatically. */
    width: clamp(44px, 5.4vw, 66px);
    font-family: var(--font-title);
    font-size: clamp(34px, 4.2vw, 56px);
    line-height: 1.05;
    color: var(--ink);
    text-align: center;
  }
  .phrase-chunk-gap {
    display: inline-block;
    width: clamp(10px, 1.2vw, 18px);
  }

  /* Inline pitch-markup variant — for `かな{LHLL}` rendered inside body
     prose (pitchifyText / window.pitchify). Everything sizes in EM so the
     contour scales with the surrounding text instead of the studio's
     fixed display px. Equal-width em columns keep the proportional SVG
     contour aligned to the mora. */
  .pitch-mora-row.is-inline {
    padding-top: 0.5em;
    padding-bottom: 0.45em;
    vertical-align: -0.35em;
    margin: 0 0.1em;
  }
  .pitch-mora-row.is-inline .pitch-mora {
    width: 1.04em;
    font-size: 1.35em;
    line-height: 1.1;
  }
  .pitch-mora-row.is-inline .pitch-contour-svg {
    height: 100%;
  }

  .pitch-pattern-chip {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 4px 10px;
    border-radius: 999px;
    background: color-mix(in srgb, var(--ink) 6%, transparent);
    font-family: var(--font-menu-en);
    font-size: 11px;
    letter-spacing: 0.06em;
    text-transform: none;          /* don't inherit the eyebrow's uppercase (keeps "heiban" lowercase) */
    color: var(--ink-soft);
    align-self: center;
    border: 1px solid transparent;
  }
  .pitch-pattern-chip[data-pattern="heiban"]    { border-color: var(--pitch-pattern-heiban);    color: color-mix(in srgb, var(--pitch-pattern-heiban) 80%, var(--ink)); }
  .pitch-pattern-chip[data-pattern="atamadaka"] { border-color: var(--pitch-pattern-atamadaka); color: color-mix(in srgb, var(--pitch-pattern-atamadaka) 80%, var(--ink)); }
  .pitch-pattern-chip[data-pattern="nakadaka"]  { border-color: var(--pitch-pattern-nakadaka);  color: color-mix(in srgb, var(--pitch-pattern-nakadaka) 80%, var(--ink)); }
  .pitch-pattern-chip[data-pattern="odaka"]     { border-color: var(--pitch-pattern-odaka);     color: color-mix(in srgb, var(--pitch-pattern-odaka) 80%, var(--ink)); }
  .pitch-pattern-ja { font-family: var(--font-title); font-size: 12px; color: inherit; }
  .pitch-pattern-en { font-style: italic; }
  .pitch-pattern-num { font-family: var(--font-en); font-weight: 600; opacity: 0.85; }

  /* Mic row */
  .speaking-mic-row {
    display: flex; flex-direction: column; align-items: center; gap: 8px;
    margin: 14px 0 24px;
  }
  /* Mic + STT-engine pill. The cluster shrink-wraps the mic so it stays
     centred; the pill is absolutely placed to the mic's right. */
  .speaking-mic-cluster { position: relative; display: inline-flex; }
  .speaking-stt-pick {
    position: absolute; left: 100%; top: 50%; transform: translateY(-50%);
    margin-left: 14px;
    display: inline-flex; align-items: center; gap: 5px;
    padding: 4px 10px 4px 8px;
    border-radius: 999px;
    border: 1px solid color-mix(in srgb, var(--ink) 14%, transparent);
    background: transparent;
    color: var(--ink-faint);
    cursor: pointer; white-space: nowrap;
    transition: background .15s, color .15s, border-color .15s;
  }
  .speaking-stt-pick svg { width: 14px; height: 14px; }
  .speaking-stt-pick .stt-pick-label { font-family: var(--font-en); font-size: 11px; letter-spacing: 0.02em; }
  .speaking-stt-pick:hover { color: var(--ink-soft); border-color: color-mix(in srgb, var(--ink) 24%, transparent); }
  /* Cloud engine active → filled gold; browser → muted. */
  .speaking-stt-pick[data-engine="cloud"] {
    background: color-mix(in srgb, var(--accent-gold, #a8843c) 16%, var(--paper));
    border-color: color-mix(in srgb, var(--accent-gold, #a8843c) 50%, transparent);
    color: color-mix(in srgb, var(--accent-gold, #a8843c) 70%, var(--ink));
  }
  @media (max-width: 560px) {
    /* Tight screens: icon-only pill so it doesn't collide with the card edge. */
    .speaking-stt-pick { margin-left: 8px; padding: 5px; }
    .speaking-stt-pick .stt-pick-label { display: none; }
  }
  .speaking-mic {
    width: 88px; height: 88px;
    border-radius: 999px;
    border: none;
    background: var(--mic-default);
    color: var(--paper);
    cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    transition: background .2s, transform .15s cubic-bezier(0.34, 1.4, 0.64, 1);
    position: relative;
    box-shadow: 0 4px 14px -8px color-mix(in srgb, var(--mic-default) 90%, transparent);
  }
  .speaking-mic svg { width: 32px; height: 32px; }
  .speaking-mic:hover { background: var(--mic-default-hover); transform: translateY(-1px); }
  .speaking-mic:active { transform: translateY(0) scale(0.96); }
  .speaking-mic:focus-visible {
    outline: 2px solid var(--accent-vermilion, #c34b30); outline-offset: 4px;
  }
  .speaking-mic[data-state="recording"] {
    background: var(--mic-recording);
    animation: speaking-mic-pulse 1400ms ease-in-out infinite;
  }
  .speaking-mic[data-state="recording"]::before {
    content: ''; position: absolute; inset: -10px;
    border-radius: 999px;
    border: 2px solid var(--mic-recording-pulse);
    opacity: 0.5;
    animation: speaking-mic-ring 1400ms ease-out infinite;
    pointer-events: none;
  }
  .speaking-mic[data-state="requesting"] { background: var(--mic-disabled); cursor: wait; }
  .speaking-mic[data-state="denied"]     { background: var(--mic-disabled); cursor: not-allowed; }
  @keyframes speaking-mic-pulse {
    0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--mic-recording-pulse) 50%, transparent); }
    50%      { box-shadow: 0 0 0 14px color-mix(in srgb, var(--mic-recording-pulse) 0%, transparent); }
  }
  @keyframes speaking-mic-ring {
    0%   { transform: scale(1); opacity: 0.55; }
    100% { transform: scale(1.6); opacity: 0; }
  }
  @media (prefers-reduced-motion: reduce) {
    .speaking-mic, .speaking-mic[data-state="recording"] { animation: none; }
    .speaking-mic[data-state="recording"]::before { animation: none; opacity: 0.7; }
  }
  .speaking-mic-labels {
    display: flex; flex-direction: column; align-items: center; gap: 0;
  }
  .mic-label-ja {
    font-family: var(--font-title);
    font-size: 14px;
    color: var(--ink-soft);
  }
  .mic-label-en {
    font-family: var(--font-en);
    font-size: 12px;
    color: var(--ink-faint);
  }
  .speaking-silent-hint {
    display: flex; flex-direction: column; align-items: center; gap: 2px;
    margin: 8px 0 0;
    padding: 8px 14px;
    border-radius: 6px;
    background: color-mix(in srgb, var(--accent-vermilion, #c34b30) 8%, transparent);
    text-align: center;
  }
  .speaking-silent-hint[hidden] { display: none; }
  .speaking-silent-hint .ja {
    font-family: var(--font-title);
    font-size: 14px;
    color: color-mix(in srgb, var(--accent-vermilion, #c34b30) 80%, var(--ink));
  }
  .speaking-silent-hint .en {
    font-family: var(--font-en);
    font-size: 12px;
    color: var(--ink-soft);
  }
  /* "Heard" line — the STT transcript (or an explanatory message) shown
     above the waveforms so the learner sees what the recognizer caught. */
  .speaking-heard {
    display: flex; flex-direction: column; align-items: center; gap: 5px;
    text-align: center;
    margin: 0 auto 16px;
    padding: 10px 16px;
    border-radius: 6px;
    background: color-mix(in srgb, var(--ink) 4%, transparent);
  }
  .speaking-heard[hidden] { display: none; }
  /* Stacked label — hiragana over English, matching the waveform/mic labels. */
  .speaking-heard-label {
    display: flex; flex-direction: column; line-height: 1.2;
  }
  .speaking-heard-label .ja {
    font-family: var(--font-title);
    font-size: 13px;
    color: var(--ink-soft);
  }
  .speaking-heard-label .en {
    font-family: var(--font-en);
    font-style: italic;
    font-size: 11px;
    color: var(--ink-faint);
  }
  .speaking-heard-text {
    font-family: var(--font-title);
    font-size: 20px;
    color: var(--ink);
    line-height: 1.4;
  }
  .speaking-heard-text ruby rt { font-size: 0.42em; color: var(--ink-faint); }
  /* Non-transcript states read as a quieter note, not a headword. */
  .speaking-heard[data-state="nowords"],
  .speaking-heard[data-state="unavailable"] {
    border-left-color: color-mix(in srgb, var(--ink) 18%, transparent);
  }
  .speaking-heard[data-state="nowords"] .speaking-heard-text,
  .speaking-heard[data-state="unavailable"] .speaking-heard-text {
    font-family: var(--font-en);
    font-style: italic;
    font-size: 13px;
    color: var(--ink-soft);
  }

  /* Waveform pair */
  .speaking-waveforms {
    display: flex; flex-direction: column; gap: 10px;
    margin: 0 0 22px;
  }
  .waveform-row {
    display: grid;
    grid-template-columns: 72px 32px 1fr 72px;
    align-items: center;
    gap: 12px;
    padding: 6px 0;
  }
  .waveform-label {
    display: flex; flex-direction: column;
    font-family: var(--font-en);
    font-size: 12px;
    color: var(--ink-soft);
    line-height: 1.2;
  }
  .waveform-label .ja {
    font-family: var(--font-title);
    font-size: 13px;
    color: var(--ink);
  }
  .waveform-label .en { font-size: 11px; color: var(--ink-faint); }
  .waveform-play {
    width: 30px; height: 30px;
    border-radius: 999px;
    border: 1px solid color-mix(in srgb, var(--ink) 14%, transparent);
    background: transparent;
    color: var(--ink-soft);
    cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    transition: background .15s, color .15s, border-color .15s;
  }
  .waveform-play svg { width: 12px; height: 12px; }
  .waveform-play:hover:not(:disabled) {
    background: color-mix(in srgb, var(--ink) 6%, transparent);
    color: var(--ink);
  }
  .waveform-play:disabled { opacity: 0.35; cursor: not-allowed; }
  .waveform-svg {
    width: 100%; height: 40px;
    display: block;
  }
  .waveform-duration {
    font-family: var(--font-en);
    font-size: 11px;
    color: var(--ink-faint);
    text-align: right;
    letter-spacing: 0.04em;
  }
  /* Voice picker — sits in the model row's right column (where the model
     clock used to be). Opens the shared voice/volume/speed popover. */
  .waveform-voice {
    justify-self: end;
    width: 30px; height: 30px;
    border-radius: 999px;
    border: 1px solid color-mix(in srgb, var(--ink) 14%, transparent);
    background: transparent;
    color: var(--ink-soft);
    cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    transition: background .15s, color .15s, border-color .15s;
  }
  .waveform-voice svg { width: 16px; height: 16px; }
  .waveform-voice:hover {
    background: color-mix(in srgb, var(--ink) 6%, transparent);
    color: var(--ink);
    border-color: color-mix(in srgb, var(--accent-gold, #a8843c) 50%, transparent);
  }

  /* Score quartet */
  .speaking-scores {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr 1fr 80px;
    gap: 14px;
    align-items: center;
    padding-top: 16px;
    border-top: 1px solid color-mix(in srgb, var(--ink) 8%, transparent);
  }
  .score-chip {
    position: relative;
    display: grid;
    grid-template-columns: 28px 1fr;
    grid-template-rows: auto auto;
    gap: 4px 10px;
    align-items: center;
    min-width: 0;
    cursor: help;
    outline: none;
  }
  .score-chip-glyph {
    grid-row: 1 / span 2;
    color: var(--ink-soft);
    width: 28px; height: 28px;
    display: flex; align-items: center; justify-content: center;
  }
  .score-chip-glyph svg { width: 20px; height: 20px; }
  .score-chip-ja {
    grid-column: 2; grid-row: 1;
    font-family: var(--font-title);
    font-size: 14px;
    color: var(--ink);
    line-height: 1.1;
    white-space: nowrap;            /* never break the label across lines */
  }
  .score-chip-dots {
    grid-column: 2; grid-row: 2;
    display: inline-flex; gap: 5px; align-items: center;
    margin-top: 2px;
  }
  /* Hover/focus tooltip — English name + hiragana + how the dimension is
     measured. Sits above the chip; appears on hover or keyboard focus. */
  .score-chip-tip {
    position: absolute;
    left: 50%; bottom: calc(100% + 10px);
    transform: translateX(-50%) translateY(4px);
    z-index: 30;
    width: max-content; max-width: 230px;
    display: flex; flex-direction: column; gap: 2px;
    padding: 10px 13px;
    text-align: left;
    background: var(--paper, #f4ecd8);
    border: 1px solid color-mix(in srgb, var(--ink) 14%, transparent);
    border-radius: 6px;
    box-shadow: 0 8px 22px rgba(60, 40, 10, 0.18);
    opacity: 0; pointer-events: none;
    transition: opacity .16s ease, transform .16s ease;
  }
  .score-chip-tip::after {       /* caret */
    content: '';
    position: absolute; top: 100%; left: 50%;
    transform: translateX(-50%);
    border: 6px solid transparent;
    border-top-color: var(--paper, #f4ecd8);
    filter: drop-shadow(0 1px 0 color-mix(in srgb, var(--ink) 14%, transparent));
  }
  .score-chip:hover .score-chip-tip,
  .score-chip:focus-visible .score-chip-tip {
    opacity: 1; transform: translateX(-50%) translateY(0);
  }
  .score-chip-tip .tip-ja {
    font-family: var(--font-title); font-size: 14px; color: var(--ink); line-height: 1.2;
  }
  .score-chip-tip .tip-en {
    font-family: var(--font-en); font-style: italic; font-size: 12px; color: var(--ink-soft); line-height: 1.2;
  }
  .score-chip-tip .tip-desc {
    font-family: var(--font-body); font-size: 12px; line-height: 1.5; color: var(--ink-soft);
    margin-top: 3px;
  }
  .score-chip-dots .dot {
    width: 7px; height: 7px;
    border-radius: 999px;
    background: var(--score-dot-empty);
    transition: background .25s;
  }
  .score-chip-dots .dot.is-filled { background: var(--score-dot-filled); }
  .score-overall {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    background: color-mix(in srgb, var(--ink) 4%, transparent);
    border-radius: 999px;
    width: 80px; height: 80px;
    align-self: end;
  }
  .score-overall-num {
    font-family: var(--font-en);
    font-size: 32px;
    font-weight: 500;
    color: var(--score-chip);
    line-height: 1;
  }
  .score-overall-denom {
    font-family: var(--font-en);
    font-style: italic;
    font-size: 11px;
    color: var(--ink-faint);
    margin-top: 2px;
  }
  .score-overall[data-has-score="false"] .score-overall-num { color: var(--ink-faint); font-weight: 400; }

  .speaking-phrase-notes {
    margin: 22px 0 0;
    padding: 12px 14px;
    background: color-mix(in srgb, var(--ink) 3%, transparent);
    border-left: 2px solid color-mix(in srgb, var(--accent-gold, #a8843c) 60%, transparent);
  }
  /* The phrase meaning — the headline of the note, sat above a hairline. */
  .speaking-note-en {
    margin: 0 0 9px;
    padding-bottom: 9px;
    border-bottom: 1px solid color-mix(in srgb, var(--ink) 8%, transparent);
    font-family: var(--font-en);
    font-style: italic;
    font-size: clamp(15px, 1.4vw, 18px);
    line-height: 1.4;
    color: var(--ink);
  }
  /* The pitch/usage explanation beneath it. */
  .speaking-note-detail {
    margin: 0;
    font-family: var(--font-body);
    font-size: 13px;
    line-height: 1.55;
    color: var(--ink-soft);
  }

  /* Filmstrip */
  /* Up Next — vertical list in the bottom-left rail. Fills the grid's 1fr
     bottom row (the space below the portrait, beside the tall stage) and
     scrolls internally so it never grows the page. */
  .speaking-filmstrip {
    grid-area: filmstrip;
    display: flex; flex-direction: column;
    min-height: 0;                          /* let the track scroll inside the row */
    background: var(--paper-soft, #ece3d2); /* match the portrait + stage panels */
    border-radius: 4px;
    padding: 14px 12px 10px;
    box-shadow: inset 0 0 0 1px rgba(141,102,48,0.10);
  }
  .filmstrip-head {
    display: flex; align-items: flex-start; justify-content: space-between;
    gap: 10px;
    flex-shrink: 0;
    padding: 0 4px 10px;
    margin-bottom: 6px;
    border-bottom: 1px solid color-mix(in srgb, var(--ink) 8%, transparent);
  }
  .filmstrip-label { display: flex; flex-direction: column; line-height: 1.25; }
  .filmstrip-label .ja {
    font-family: var(--font-title);
    font-size: 14px;
    color: var(--ink-soft);
  }
  .filmstrip-label .en {
    font-family: var(--font-menu-en);
    font-size: 10px;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--ink-faint);
    margin-top: 1px;
  }
  .filmstrip-nav { display: flex; gap: 6px; flex-shrink: 0; }
  .filmstrip-arrow {
    width: 28px; height: 28px;
    border-radius: 999px;
    border: 1px solid color-mix(in srgb, var(--ink) 14%, transparent);
    background: var(--paper);
    color: var(--ink-soft);
    cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    transition: background .15s, color .15s;
  }
  .filmstrip-arrow svg { width: 14px; height: 14px; }
  .filmstrip-arrow:hover { background: color-mix(in srgb, var(--ink) 6%, var(--paper)); color: var(--ink); }

  .filmstrip-track {
    list-style: none; margin: 0; padding: 2px;
    position: relative;                     /* offsetParent for auto-scroll-to-current */
    display: flex; flex-direction: column; gap: 4px;
    flex: 1 1 0; min-height: 0;
    overflow-y: auto;
    scrollbar-width: thin;
    scrollbar-color: color-mix(in srgb, var(--ink) 14%, transparent) transparent;
  }
  .filmstrip-item { flex: 0 0 auto; }
  .filmstrip-card {
    display: flex; align-items: center; gap: 10px;
    width: 100%;
    padding: 9px 10px;
    border-radius: 4px;
    background: transparent;
    border: 1px solid transparent;
    cursor: pointer;
    text-align: left;
    font: inherit; color: inherit;
    transition: background .15s, border-color .15s;
    min-width: 0;
  }
  .filmstrip-card:hover { background: color-mix(in srgb, var(--ink) 4%, var(--paper)); }
  .filmstrip-num {
    width: 24px; height: 24px;
    border-radius: 999px;
    background: color-mix(in srgb, var(--ink) 6%, transparent);
    color: var(--ink-soft);
    display: flex; align-items: center; justify-content: center;
    font-family: var(--font-en);
    font-size: 12px; font-weight: 500;
    flex-shrink: 0;
  }
  .filmstrip-item.is-current .filmstrip-card {
    border-color: color-mix(in srgb, var(--accent-vermilion, #c34b30) 45%, transparent);
    background: color-mix(in srgb, var(--accent-vermilion, #c34b30) 5%, var(--paper));
  }
  .filmstrip-item.is-current .filmstrip-num {
    background: var(--accent-vermilion, #c34b30);
    color: var(--paper);
  }
  .filmstrip-item.is-past .filmstrip-card { opacity: 0.6; }
  .filmstrip-item.is-past:hover .filmstrip-card { opacity: 0.9; }
  .filmstrip-text { display: flex; flex-direction: column; min-width: 0; gap: 1px; }
  .filmstrip-kana {
    font-family: var(--font-title);
    font-size: 15px;
    color: var(--ink);
    line-height: 1.2;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .filmstrip-en {
    font-family: var(--font-en);
    font-style: italic;
    font-size: 11px;
    color: var(--ink-faint);
    line-height: 1.2;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }

  /* Responsive — single column: portrait, stage, then Up Next at the
     bottom. The list is no longer height-driven by a sibling, so cap it and
     let it scroll on its own. */
  @media (max-width: 900px) {
    .speaking-studio {
      grid-template-columns: 1fr;
      grid-template-rows: auto auto auto;
      grid-template-areas:
        "portrait"
        "stage"
        "filmstrip";
    }
    .speaking-portrait-frame { aspect-ratio: 16 / 9; }
    .speaking-filmstrip { max-height: 360px; }
    .filmstrip-track { flex: 0 1 auto; }
    .speaking-scores { grid-template-columns: 1fr 1fr; }
    .score-overall { grid-column: 1 / -1; width: 100%; height: 56px; border-radius: 6px; flex-direction: row; gap: 6px; }
    .score-overall-num { font-size: 24px; }
  }
  @media (max-width: 540px) {
    .pitch-mora { width: 38px; font-size: 32px; }
  }

  /* ─── Pitch & Tones basics page (Writing → Pitch) ───────────────── */
  .pitch-page {
    --pitch-line:          #8f3f2e;
    --pitch-line-user:     #5e7d3e;
    --pitch-pattern-heiban:    #a8843c;
    --pitch-pattern-atamadaka: #c34b30;
    --pitch-pattern-nakadaka:  #8c6a3a;
    --pitch-pattern-odaka:     #7a9a5a;
    /* No own max-width / margin / padding: the page flows inside .main-inner
       like every other page (capped at --content-max, centred, with .main's
       page padding) so its body width and insets match the rest of the site.
       The header uses the standard .page-head set below. */
  }
  .pitch-section {
    margin-top: clamp(32px, 3.6vw, 56px);
    padding-top: clamp(20px, 2.2vw, 32px);
    border-top: 1px solid color-mix(in srgb, var(--ink) 8%, transparent);
  }
  .pitch-section-title {
    display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap;
    font: inherit;
    margin: 0 0 18px;
  }
  .pitch-section-title .num {
    font-family: var(--font-title);
    font-size: 26px;
    color: var(--accent-vermilion, #c34b30);
  }
  .pitch-section-title .ja {
    font-family: var(--font-title);
    font-size: clamp(20px, 2vw, 28px);
    color: var(--ink);
  }
  .pitch-section-title .en {
    font-family: var(--font-en);
    font-style: italic;
    font-size: clamp(14px, 1.2vw, 17px);
    color: var(--ink-faint);
  }
  .pitch-section-body {
    font-family: var(--font-body);
    font-size: 15px;
    line-height: 1.7;
    color: var(--ink-soft);
    max-width: 68ch;
  }
  .pitch-section-body p { margin: 0 0 14px; }
  .pitch-section-body strong { color: var(--ink); font-weight: 600; }
  .pitch-section-body code {
    font-family: var(--font-en);
    background: color-mix(in srgb, var(--ink) 5%, transparent);
    padding: 1px 6px;
    border-radius: 3px;
    font-size: 0.9em;
  }
  .pitch-markup-code {
    font-family: var(--font-en);
    background: color-mix(in srgb, var(--ink) 6%, transparent);
    border: 1px solid color-mix(in srgb, var(--ink) 10%, transparent);
    border-radius: 6px;
    padding: 12px 16px;
    margin: 12px 0;
    font-size: 18px;
    color: var(--ink);
    letter-spacing: 0.02em;
    white-space: pre-wrap;
  }
  .pitch-trio {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 16px;
    margin-top: 22px;
  }
  .pitch-trio-card {
    background: var(--paper-soft, #ece3d2);
    border-radius: 4px;
    padding: 18px 16px 16px;
    display: flex; flex-direction: column; align-items: center; gap: 10px;
    text-align: center;
  }
  .pitch-trio-glyph {
    display: inline-flex; align-items: flex-end;
  }
  .pitch-trio-gloss { display: flex; flex-direction: column; gap: 2px; margin: 0; }
  .pitch-trio-tts { margin-top: 2px; }
  .pitch-trio-gloss .ja {
    font-family: var(--font-title);
    font-size: 22px;
    color: var(--ink);
  }
  .pitch-trio-gloss .en {
    font-family: var(--font-en);
    font-style: italic;
    font-size: 13px;
    color: var(--ink-faint);
  }
  @media (max-width: 720px) {
    .pitch-trio { grid-template-columns: 1fr; }
  }
  .pitch-examples {
    display: flex; flex-direction: column;
    gap: 14px; margin-top: 8px;
  }
  .pitch-example {
    background: var(--paper-soft, #ece3d2);
    border-radius: 4px;
    padding: 18px 22px;
  }
  .pitch-example-head {
    display: flex; align-items: center; justify-content: space-between;
    margin-bottom: 14px;
  }
  .pitch-example-play {
    width: 34px; height: 34px;
    border-radius: 999px;
    border: 1px solid color-mix(in srgb, var(--ink) 14%, transparent);
    background: transparent;
    color: var(--ink-soft);
    cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    transition: background .15s, color .15s;
  }
  .pitch-example-play svg { width: 12px; height: 12px; }
  .pitch-example-play:hover {
    background: color-mix(in srgb, var(--ink) 6%, transparent);
    color: var(--ink);
  }
  .pitch-example-glyph {
    display: flex; align-items: flex-end;
    margin-bottom: 8px;
  }
  .pitch-example-en {
    font-family: var(--font-en);
    font-style: italic;
    font-size: 14px;
    color: var(--ink-soft);
    margin: 4px 0 8px;
  }
  .pitch-example-desc {
    font-family: var(--font-body);
    font-size: 14px;
    line-height: 1.6;
    color: var(--ink-soft);
    margin: 0;
    max-width: 60ch;
  }
  .pitch-legend {
    margin: 22px 0 0;
    display: grid;
    grid-template-columns: 1fr;
    gap: 4px 0;
  }
  .pitch-legend-row {
    display: grid;
    grid-template-columns: 200px 1fr;
    gap: 16px;
    padding: 10px 0;
    border-bottom: 1px solid color-mix(in srgb, var(--ink) 6%, transparent);
  }
  .pitch-legend-row:last-child { border-bottom: none; }
  .pitch-legend-row dt {
    display: flex; align-items: center; gap: 12px;
    font-family: var(--font-title);
    color: var(--ink);
  }
  .pitch-legend-row dd {
    margin: 0;
    font-family: var(--font-body);
    font-size: 13px;
    line-height: 1.6;
    color: var(--ink-soft);
  }
  .pitch-legend-glyph {
    width: 28px; height: 28px;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 20px;
    color: var(--accent-vermilion, #c34b30);
    background: color-mix(in srgb, var(--ink) 4%, transparent);
    border-radius: 4px;
    flex-shrink: 0;
  }
  .pitch-legend-name {
    font-size: 14px;
  }
  @media (max-width: 720px) {
    .pitch-legend-row { grid-template-columns: 1fr; }
  }
  .pitch-section-cta { text-align: center; }
  .pitch-cta-prompt {
    font-family: var(--font-en);
    font-style: italic;
    font-size: 16px;
    color: var(--ink-soft);
    margin: 0 0 14px;
  }
  .pitch-cta-btn {
    display: inline-flex; flex-direction: column; align-items: center;
    gap: 2px;
    padding: 14px 36px;
    background: var(--accent-vermilion, #c34b30);
    color: var(--paper);
    border: none;
    border-radius: 4px;
    cursor: pointer;
    transition: background .15s, transform .15s;
  }
  .pitch-cta-btn:hover { background: color-mix(in srgb, var(--accent-vermilion, #c34b30) 90%, #000); transform: translateY(-1px); }
  .pitch-cta-btn .ja {
    font-family: var(--font-title);
    font-size: 22px;
  }
  .pitch-cta-btn .en {
    font-family: var(--font-en);
    font-style: italic;
    font-size: 12px;
    opacity: 0.85;
  }
