/* kx_entry.css — DESIGN PASS 2c section E.   🆕 P1-20E  2026-09-26
 *
 * One request instead of eight. This file is the VERBATIM concatenation, in browser
 * order, of the sheets listed below plus the page's own inline <style>. Nothing was
 * reordered, rewritten or de-duplicated, so the cascade result is identical by
 * construction; the old versioned files stay on disk and are simply no longer linked.
 *
 *   css/v1/kx_page.css
 *   css/v2/kx_words.css
 *   css/v1/kx_vt.css
 *   css/v4/kx_layout.css
 *   css/v5/kx_fix.css
 *   css/v3/kx_paper.css
 *   kanji_entry.php inline <style> (lines 318-445)
 *   css/v9/kx_a11y.css
 */

/* ==========================================================================
   css/v1/kx_page.css
   ========================================================================== */
/* kx_page.css v1 — Kanji World page interactions (D1 + D2 + D3).            🆕 P1-16
 *
 * Ported from the locked prototype (design/kanji-world-prototype.html). The page already
 * owns the Paper & Ink tokens, so every colour here is var(--token, fallback) and nothing
 * re-declares a palette. Motion is opt-out at the bottom, once, for the whole file.
 */

/* ---------- D1: hero stage tilt ---------- */
.kx-stage-wrap{perspective:900px}
.kx-stage{transform:rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg));transition:transform .25s ease;
  transform-style:preserve-3d;will-change:transform}

/* ---------- D1: ink ripple ---------- */
.kx-rip-host{position:relative;overflow:hidden}
.kx-ripple{position:absolute;border-radius:50%;background:rgba(255,255,255,.45);
  transform:scale(0);animation:kx-rip .6s ease-out forwards;pointer-events:none}
.btn.ghost .kx-ripple,.sheet .kx-ripple{background:var(--border-strong,rgba(237,31,36,.26))}
@keyframes kx-rip{to{transform:scale(4);opacity:0}}

/* ---------- D1: level badge as a hanko seal ---------- */
.badge.kx-hanko{display:inline-grid;place-items:center;width:54px;height:54px;padding:0;
  border:2.5px solid var(--red,#ed1f24);border-radius:10px;background:var(--red-soft,rgba(237,31,36,.12));
  color:var(--red,#ed1f24);font-family:var(--font-display,serif);font-weight:700;font-size:16px;
  line-height:1;transform:rotate(-6deg)}

/* ---------- D1: brush underline on headings ---------- */
.kx-brush{position:relative;display:inline-block}
.kx-brush::after{content:"";position:absolute;left:-2px;right:-2px;bottom:2px;height:8px;
  background:var(--red-soft,rgba(237,31,36,.12));border-radius:4px;
  transform:scaleX(0);transform-origin:left;transition:transform .35s ease}
h2:hover .kx-brush::after,.kx-brush:focus-visible::after,.kx-brush.on::after{transform:scaleX(1)}

/* ---------- D1/D2: part cards ---------- */
button.comp{width:100%;text-align:left;font:inherit;color:inherit;cursor:pointer;
  transition:border-color .25s,transform .25s,box-shadow .25s}
button.comp:hover{transform:translateY(-2px);box-shadow:0 8px 20px rgba(60,30,10,.08)}
button.comp.on{border-color:var(--red,#ed1f24);box-shadow:0 0 0 3px var(--red-soft,rgba(237,31,36,.12))}
button.comp.flash{animation:kx-flash .9s ease}
@keyframes kx-flash{0%,100%{box-shadow:0 0 0 0 transparent}
  25%{box-shadow:0 0 0 3px var(--red-soft,rgba(237,31,36,.12));border-color:var(--red,#ed1f24)}}

/* ---------- D2: hero stroke lighting ---------- */
#kx-glyph path{transition:fill .35s ease,filter .35s ease}
#kx-glyph.kx-pick path{cursor:pointer}
#kx-glyph path.kx-lit{fill:var(--red,#ed1f24);filter:drop-shadow(0 0 10px rgba(237,31,36,.45))}
#kx-glyph path.kx-dim{fill:var(--ghost,#d9cdbf)}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]) #kx-glyph path.kx-dim{fill:#4a403a}}
:root[data-theme="dark"] #kx-glyph path.kx-dim{fill:#4a403a}

/* ---------- D3: stroke scrubber ---------- */
.kx-scrub{display:grid;grid-template-columns:auto 1fr;gap:22px;align-items:center;margin:6px 0 4px}
@media (max-width:640px){.kx-scrub{grid-template-columns:1fr;justify-items:center}}
.kx-scrub-stage{width:min(62vw,210px);aspect-ratio:1;background:var(--paper,#f7f0e6);
  border-radius:18px;border:1px solid var(--border,rgba(36,29,27,.12));position:relative;flex:0 0 auto}
.kx-scrub-stage svg{position:absolute;inset:0;width:100%;height:100%}
.kx-scrub-stage path{transition:fill .18s linear}
.kx-scrub-stage path.s-ghost{fill:var(--ghost,#e7ddd0)}
.kx-scrub-stage path.s-done{fill:var(--text,#241d1b)}
.kx-scrub-stage path.s-now{fill:var(--red,#ed1f24)}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]) .kx-scrub-stage path.s-ghost{fill:#4a403a}}
:root[data-theme="dark"] .kx-scrub-stage path.s-ghost{fill:#4a403a}
.kx-scrub-ctl{width:100%;min-width:0}
.kx-scrub-count{font-family:var(--font-display,serif);font-size:30px;font-weight:700;line-height:1.1}
.kx-scrub-count small{font-size:14px;color:var(--muted,#5b514d);font-family:var(--font-body,sans-serif);font-weight:400}
.kx-scrub-ctl input[type=range]{width:100%;accent-color:var(--red,#ed1f24);height:36px;margin:2px 0 0}
/* the server-rendered step panels become the thumbnail row */
.steps.kx-thumbs figure{cursor:pointer;transition:border-color .2s,box-shadow .2s}
.steps.kx-thumbs figure:hover{border-color:var(--red,#ed1f24)}
.steps.kx-thumbs figure.cur{border-color:var(--red,#ed1f24);box-shadow:0 0 0 2px var(--red-soft,rgba(237,31,36,.12))}
.steps.kx-thumbs figure:focus-visible{outline:2px solid var(--red,#ed1f24);outline-offset:2px}

@media (prefers-reduced-motion:reduce){
  .kx-stage,.kx-brush::after,button.comp,#kx-glyph path,.kx-scrub-stage path{transition-duration:.01ms!important}
  .kx-ripple,button.comp.flash{animation:none!important}
}

/* ==========================================================================
   css/v2/kx_words.css
   ========================================================================== */
/* kx_words.css v2 — D4 words globe on the kanji page.                       🆕 P1-17
 * The list is the source of truth and stays in the DOM; this only dresses the orbit the
 * script builds from it. Per Umesh's spec the centre has NO ring and NO background circle,
 * and there are NO wires — the words simply orbit close around the kanji.
 */
.kx-wtabs{display:inline-flex;border:1px solid var(--border-strong,rgba(237,31,36,.26));
  border-radius:12px;overflow:hidden;margin:0 0 12px}
.kx-wtabs button{border:0;background:transparent;padding:7px 15px;cursor:pointer;font:inherit;
  font-size:14px;color:var(--muted)}
.kx-wtabs button[aria-selected=true]{background:var(--red,#ed1f24);color:#fff;font-weight:700}
.kx-wtabs button:focus-visible{outline:2px solid var(--red);outline-offset:-2px}

.kx-orbit{position:relative;height:340px;touch-action:none;cursor:grab;overflow:hidden;
  border-radius:20px;border:1px solid var(--border,rgba(36,29,27,.12));background:var(--surface,#fdf9f4)}
.kx-orbit:active{cursor:grabbing}
.kx-orbit.drag{cursor:grabbing}
@media (max-width:480px){.kx-orbit{height:300px}}

/* the page kanji at the centre — red glyph only, nothing behind it */
.kx-core{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  font-family:var(--font-display,serif);font-weight:700;font-size:62px;line-height:1;
  color:var(--red,#ed1f24);pointer-events:none;z-index:400;user-select:none}
@media (max-width:480px){.kx-core{font-size:52px}}

.kx-w{position:absolute;left:50%;top:50%;border:1px solid var(--border,rgba(36,29,27,.12));
  background:var(--bg,#faf5ef);border-radius:999px;padding:5px 13px;white-space:nowrap;
  font-family:var(--font-display,serif);font-size:17px;line-height:1.5;cursor:pointer;
  color:var(--text);will-change:transform,opacity;transform-origin:center}
.kx-w:hover,.kx-w:focus-visible{border-color:var(--red,#ed1f24)}
.kx-w .hk{color:var(--red,#ed1f24)}
.kx-w ruby rt{font-size:.5em}

.kx-wdetail{margin-top:12px;border:1px solid var(--red,#ed1f24);border-radius:16px;
  padding:14px 16px;background:var(--surface,#fdf9f4);display:none}
.kx-wdetail.on{display:block}
.kx-wdetail .dj{font-family:var(--font-display,serif);font-size:26px;line-height:1.6}
.kx-wdetail .dj .hk{color:var(--red,#ed1f24)}
.kx-wdetail .dr{font-size:13.5px;color:var(--muted);margin-top:2px}
.kx-wdetail .de{margin-top:4px}
.kx-wdetail .dn{font-family:var(--font-np,sans-serif);color:var(--text-2,var(--text))}

@media (prefers-reduced-motion:reduce){ .kx-w{transition:none!important} }

/* ==========================================================================
   css/v1/kx_vt.css
   ========================================================================== */
/* kx_vt.css v1 — the cross-document View Transition between /kanji and /kanji/<char>.
 * Kept in its own file because BOTH pages must opt in for a cross-document transition to
 * happen at all, and neither page should carry the other's rules.
 * Browsers without support simply ignore all of this and get the scripted fade instead.
 */
@view-transition { navigation: auto; }

::view-transition-old(kx-hero),
::view-transition-new(kx-hero){ animation-duration:.42s; animation-timing-function:cubic-bezier(.2,.8,.2,1) }

@media (prefers-reduced-motion:reduce){
  @view-transition { navigation: none; }
}

/* ==========================================================================
   css/v4/kx_layout.css
   ========================================================================== */
/* kx_layout.css v4 — desktop layouts.                                       🛠️ P1-19
 *
 * Two jobs:
 *   1. the hub globe must never spill out of its grid column (at 1920 it was 820px wide in
 *      an 812px column, which is why it intersected the search column);
 *   2. the kanji page becomes two columns on a wide screen instead of a phone-width strip.
 *
 * MOBILE IS UNTOUCHED. Everything below lives inside a min-width query, and the desktop
 * arrangement is done with GRID PLACEMENT, not by reordering the DOM — so the reading order
 * a phone and a screen reader get is exactly the order that shipped.
 */

/* ---------- 1. hub: keep the globe inside its column ---------- */
.kx-globe-box{max-width:100%}
@media (min-width:900px){
  .kx-globe-col{min-width:0}          /* a grid child will not shrink below content without this */
}

/* ---------- 2. kanji page: two columns ---------- */
@media (min-width:1000px){
  main.wrap{max-width:1240px}
  header.kx-nav,.kxft{max-width:1240px;margin-left:auto;margin-right:auto}

  main.wrap.kx-two{
    display:grid;
    grid-template-columns:minmax(320px,400px) minmax(0,1fr);
    column-gap:clamp(28px,3vw,56px);
    align-items:start;
  }
  /* the two rails. display:contents on mobile means these wrappers do nothing there. */
  .kx-col-l{grid-column:1;position:sticky;top:16px;align-self:start}
  .kx-col-r{grid-column:2;min-width:0}
  .kx-col-full{grid-column:1 / -1}

  /* a bigger hero now that it has room */
  .kx-col-l .stage{width:min(400px,100%)}
  .kx-col-l .hero{display:block}
  .kx-col-l .facts{margin-top:16px}

  /* the scrubber can breathe */
  .kx-scrub-stage{width:min(34vw,260px)}
}
@media (min-width:1280px){
  main.wrap{max-width:1280px}
  header.kx-nav,.kxft{max-width:1280px}
  main.wrap.kx-two{grid-template-columns:minmax(360px,420px) minmax(0,1fr)}
  .kx-col-l .stage{width:min(420px,100%)}
}
@media (min-width:1600px){
  main.wrap{max-width:1360px}
  header.kx-nav,.kxft{max-width:1360px}
}

/* results page and the friendly 404 get the same wider container */
@media (min-width:1000px){
  body.kx-results main.wrap{max-width:1240px}
}

/* mobile: the rails are not boxes, they are nothing at all */
@media (max-width:999.98px){
  .kx-col-l,.kx-col-r,.kx-col-full{display:contents}
}

/* ==========================================================================
   css/v5/kx_fix.css
   ========================================================================== */
/* kx_fix.css v5 — three measured defects from the size matrix.             🛠️ P1-19b
 * Loads after v3/v4 (versioned files are never overwritten), so it wins on order.
 */

/* 1. 320px horizontal overflow on /kanji/愛.
 *    The P1-14 furigana fallback pill was white-space:nowrap, so a long sentence reading
 *    (307px) could not wrap inside a 320px viewport. Measured: 20px of overflow. */
.kx-rdfb{white-space:normal;overflow-wrap:anywhere}

/* 2. The kanji-page container widths were written as `main.wrap`, which also matched the
 *    HUB and shrank it from 1520 to 1360 — that is why the globe column stopped at 760px
 *    and the globe never earned its large-desktop population. Scope them to the grid. */
@media (min-width:1000px){
  main.wrap:not(.kx-two){max-width:none}          /* hand the hub back to kx_hub2.css */
  main.wrap.kx-two{max-width:1240px}
}
@media (min-width:1280px){ main.wrap.kx-two{max-width:1280px} }
@media (min-width:1600px){ main.wrap.kx-two{max-width:1360px} }

/* 3. On the results page there is no hero grid, so nothing bounded the globe and it grew to
 *    1181px at 2560. Only the globe INSIDE the hero column is allowed to be large. */
.kx-globe-box{max-width:min(100%,560px)}
@media (min-width:900px){
  .kx-globe-col .kx-globe-box{max-width:min(100%,820px)}
}

/* ==========================================================================
   css/v3/kx_paper.css
   ========================================================================== */
/* kx_paper.css v3 — one rule: ink drawn ON PAPER is a fixed colour.        🛠️ P1-19
 *
 * THE BUG THIS FIXES (found by Umesh on /kanji/琴 in dark mode)
 * --paper is cream in BOTH themes — kanji_entry.php:303 already calls this out as a
 * landmine: "it is the ONLY element that may use it". The P1-16/P1-17 CSS ignored that and
 * painted strokes on the paper stages with page tokens, so in dark mode:
 *
 *   scrubber "done" strokes  fill:var(--text)  -> #f2ebe6, near-white on cream = invisible
 *   scrubber "ghost" strokes overridden to #4a403a -> DARK on cream = looks like real ink
 *   hero dimmed strokes      same inversion
 *
 * which is exactly why "12 of 12" on 琴 read as 11 ghosts and one stroke.
 *
 * The page's own code already had this right — .stage svg.glyph is fill:#241d1b and the
 * server-rendered step panels use literal rgba(36,29,27,.82) / #dc2626. These rules bring
 * the P1-16/P1-17 surfaces back in line and pin every paper tone to a literal.
 *
 * Loaded AFTER css/v1 and css/v2 (never overwrite a versioned file), so it wins on order.
 */

:root{
  --kx-sumi:#241d1b;        /* finished stroke — matches .stage svg.glyph */
  --kx-sumi-soft:rgba(36,29,27,.82);   /* matches the server-rendered step panels */
  --kx-ghost:#ded3c6;       /* not yet written: warm, clearly lighter than the paper grain */
  --kx-brush:#dc2626;       /* the stroke being written — matches the step panels */
}

/* ---- hero stage ---- */
#kx-glyph path.kx-lit{fill:var(--kx-brush)!important;filter:drop-shadow(0 0 10px rgba(220,38,38,.45))}
#kx-glyph path.kx-dim{fill:var(--kx-ghost)!important}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]) #kx-glyph path.kx-dim{fill:var(--kx-ghost)!important}
  :root:not([data-theme="light"]) #kx-glyph path.kx-lit{fill:var(--kx-brush)!important}
}
:root[data-theme="dark"] #kx-glyph path.kx-dim,html.dark #kx-glyph path.kx-dim{fill:var(--kx-ghost)!important}
:root[data-theme="dark"] #kx-glyph path.kx-lit,html.dark #kx-glyph path.kx-lit{fill:var(--kx-brush)!important}

/* ---- stroke-order scrubber stage ---- */
.kx-scrub-stage path.s-ghost{fill:var(--kx-ghost)!important}
.kx-scrub-stage path.s-done {fill:var(--kx-sumi)!important}
.kx-scrub-stage path.s-now  {fill:var(--kx-brush)!important}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]) .kx-scrub-stage path.s-ghost{fill:var(--kx-ghost)!important}
  :root:not([data-theme="light"]) .kx-scrub-stage path.s-done {fill:var(--kx-sumi)!important}
  :root:not([data-theme="light"]) .kx-scrub-stage path.s-now  {fill:var(--kx-brush)!important}
}
:root[data-theme="dark"] .kx-scrub-stage path.s-ghost,html.dark .kx-scrub-stage path.s-ghost{fill:var(--kx-ghost)!important}
:root[data-theme="dark"] .kx-scrub-stage path.s-done ,html.dark .kx-scrub-stage path.s-done {fill:var(--kx-sumi)!important}
:root[data-theme="dark"] .kx-scrub-stage path.s-now  ,html.dark .kx-scrub-stage path.s-now  {fill:var(--kx-brush)!important}

/* ---- thumbnail row: the numbers sit on paper too ---- */
.steps.kx-thumbs .no{color:rgba(36,29,27,.45)}
/* the little index number inside each step panel, both themes */
.steps .no{color:rgba(36,29,27,.45)}

/* ==========================================================================
   kanji_entry.php inline <style> (lines 318-445)
   ========================================================================== */
*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent;}
/* .btn sets display:inline-flex, which OUT-SPECIFIES the UA's [hidden]{display:none} and
   left the Replay button visible before the animation had ever run. */
[hidden]{display:none!important;}
/* Paper & Ink tokens — the same set kanji_student.php:276 uses, so the two screens match. */
:root{--bg:#faf5ef;--surface:#fdf9f4;--surface-2:#faf5ef;--subtle:#f3eadf;--text:#241d1b;--text-2:#3a302d;--muted:#5b514d;--muted-2:#6f635e;--border:rgba(237,31,36,.14);--border-strong:rgba(237,31,36,.26);--paper:#fffdfa;--tint-pink:#fdf0f3;--tint-pink-border:#f6c3d0;--red:#ed1f24;--sakura:#f6c3d0;--gold:#c9a227;--on:#dc2626;--kun:#2563eb;
 /* 🆕 P1-6e — the three Paper & Ink families, named once. Local fallbacks keep the page
    readable during the swap and on a device that blocks webfonts. */
 --font-display:'Shippori Mincho','Hiragino Mincho ProN','Yu Mincho',serif;
 --font-body:'Hiragino Sans','Hiragino Kaku Gothic ProN','Noto Sans JP','Noto Sans CJK JP','Yu Gothic UI','Yu Gothic',Meiryo,system-ui,-apple-system,'Segoe UI',sans-serif;   /* 🛠️ P1-20C — Zen Kaku dropped; this stack ships on every target device */
 --font-np:'Noto Sans Devanagari','Hiragino Sans','Hiragino Kaku Gothic ProN','Noto Sans JP','Noto Sans CJK JP','Yu Gothic UI','Yu Gothic',Meiryo,system-ui,-apple-system,'Segoe UI',sans-serif;}   /* 🛠️ P1-20C */
/* ⚠️ LANDMINE (carried over verbatim in spirit from kanji_student.php): --paper is
   deliberately LIGHT IN BOTH THEMES because the stage is a writing sheet you draw dark ink
   on. It is the ONLY element that may use it. Never write background:var(--paper) with
   color:var(--text) — in dark that is #f7f2eb behind #f2ebe6, i.e. invisible. */
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){
  --bg:#151011;--surface:#1e1719;--surface-2:#151011;--subtle:#2a2124;--text:#f2ebe6;--text-2:#f2ebe6;--muted:#b5a8a3;--muted-2:#cdc2bd;--border:rgba(255,95,99,.18);--border-strong:rgba(255,95,99,.30);--paper:#f7f2eb;--tint-pink:#33222a;--tint-pink-border:#8c5a68;--red:#ff5f63;--sakura:#8c5a68;--gold:#d9b64a;--on:#ff7b7f;--kun:#93c5fd;}}
:root[data-theme="dark"],html.dark{--bg:#151011;--surface:#1e1719;--surface-2:#151011;--subtle:#2a2124;--text:#f2ebe6;--text-2:#f2ebe6;--muted:#b5a8a3;--muted-2:#cdc2bd;--border:rgba(255,95,99,.18);--border-strong:rgba(255,95,99,.30);--paper:#f7f2eb;--tint-pink:#33222a;--tint-pink-border:#8c5a68;--red:#ff5f63;--sakura:#8c5a68;--gold:#d9b64a;--on:#ff7b7f;--kun:#93c5fd;}
body{background:var(--bg);color:var(--text);font:16px/1.65 var(--font-body);   /* 🛠️ P1-6e */
 padding-bottom:calc(48px + env(safe-area-inset-bottom));}
.wrap{max-width:720px;margin:0 auto;padding:0 16px;}
a{color:inherit}
/* nav */
.kx-nav{display:flex;align-items:center;justify-content:space-between;gap:12px;max-width:720px;margin:0 auto;
 padding:12px 16px calc(12px);border-bottom:1px solid var(--border);}
.kx-brand{display:flex;align-items:center;gap:8px;font-weight:700;text-decoration:none;font-size:15px}
.kx-nav nav{display:flex;gap:14px;font-size:14px}.kx-nav nav a{text-decoration:none;color:var(--muted)}
.kx-nav nav a:hover{color:var(--text)}
/* dark-build banner */
.darkbar{max-width:720px;margin:12px auto 0;padding:9px 12px;border:1px dashed var(--border-strong);
 border-radius:10px;background:var(--subtle);font-size:13px;color:var(--muted);}
h2{font-size:17px;margin:30px 0 10px;letter-spacing:.01em;font-family:var(--font-display);font-weight:700}
h1{font-family:var(--font-display);font-weight:700}   /* 🆕 P1-6e — headings are mincho, like the rest of the site */
.card{background:var(--surface);border:1px solid var(--border);border-radius:14px;padding:16px}
.muted{color:var(--muted);font-size:14px}
/* 1. hero stage */
.hero{display:flex;gap:18px;align-items:flex-start;flex-wrap:wrap;margin-top:18px}
.stage{view-transition-name:kx-hero;   /* 🆕 P1-17 D5 — paired with the hub flyer */
  position:relative;width:min(260px,64vw);aspect-ratio:1;background:var(--paper);border:1px solid var(--border-strong);
 border-radius:16px;display:grid;place-items:center;overflow:hidden;flex:0 0 auto;color:#241d1b;
 /* 🆕 P1-6c — kx_anim.js reads these two custom properties off the stage, so the colours live
    here in CSS and the animation file stays untouched at v1. --kx-ghost is what the kanji
    STARTS as and --kx-ink is what each finished stroke turns into: black paper-ink before you
    write, brand red once written, and Replay rebuilds the SVG so it resets to black by itself.
    Both are literals, not var(--text)/var(--red): the stage is light in BOTH themes (see the
    --paper landmine above), so a dark-theme token would paint pale ink on pale paper. */
 --kx-ghost:#241d1b; --kx-ink:#ed1f24;}
/* 🆕 P1-6e — the resting kanji is now the SAME OUTLINES the animation draws, so pressing
   "Show me how to write" changes the colour and nothing else. A font glyph could never do
   that: Shippori Mincho's 愛 is a different drawing from KanjiVG's, so the old hero visibly
   jumped at the handover, and for Ext-B kanji the font had no glyph at all. */
.stage svg.glyph{display:block;width:100%;height:100%;fill:#241d1b}
.stage .glyph-text{font-family:var(--font-display);font-size:min(190px,46vw);line-height:1;color:#241d1b;}
/* 🆕 P1-6f — the "no stroke data" note, in the facts column. Quiet, never alarming. */
.nostroke{margin-top:14px;font-size:13.5px;color:var(--muted);line-height:1.6;
 border-left:2px solid var(--border-strong);padding-left:11px}
.nostroke .np{display:block;font-family:var(--font-np);font-weight:500}
.stage .wm{position:absolute;bottom:6px;right:9px;font-size:9px;color:rgba(36,29,27,.28);letter-spacing:.04em}
.stage svg.kx-anim{position:absolute;inset:0;margin:auto}
.facts{flex:1 1 220px;min-width:200px}
.badge{display:inline-block;background:var(--gold);color:#fff;border-radius:6px;padding:2px 9px;font-size:13px;font-weight:700}
.tag{font-size:11px;color:var(--muted);border:1px solid var(--border);border-radius:4px;padding:0 5px;white-space:nowrap}
.btn{display:inline-flex;align-items:center;gap:8px;border:0;border-radius:11px;padding:11px 18px;font-size:15px;
 font-weight:700;background:var(--red);color:#fff;cursor:pointer;text-decoration:none;margin:10px 8px 0 0}
.btn.ghost{background:transparent;color:var(--text);border:1px solid var(--border-strong)}
.btn[disabled]{opacity:.45;cursor:not-allowed}
/* component chips */
.chips{display:flex;flex-wrap:wrap;gap:7px;margin-top:12px;min-height:34px}
.kx-chip{display:inline-flex;align-items:baseline;gap:6px;border:1px solid var(--tint-pink-border);background:var(--tint-pink);
 border-radius:999px;padding:5px 12px;font-size:13.5px;opacity:0;transform:translateY(5px);transition:opacity .25s,transform .25s}
.kx-chip.in{opacity:1;transform:none}
.kx-chip b{font-family:var(--font-display);font-size:18px}
.kx-chip .j{font-weight:600}.kx-chip .m{color:var(--muted);font-size:12px}
@media (prefers-reduced-motion:reduce){.kx-chip{transition-duration:.01ms}}
/* 2. step sequence */
.steps{display:grid;grid-template-columns:repeat(auto-fill,minmax(62px,1fr));gap:7px}
.steps figure{background:var(--paper);border:1px solid var(--border);border-radius:9px;padding:3px;position:relative}
.steps svg{display:block;width:100%;height:auto}
.steps .no{position:absolute;bottom:1px;right:4px;font-size:9px;color:rgba(36,29,27,.45)}
/* 3. readings */
.rd{display:flex;gap:10px;flex-wrap:wrap;align-items:baseline}
.rd .lab{font-weight:800;min-width:2em}
.rd .on{color:var(--on)}.rd .kun{color:var(--kun)}
.ok{opacity:.62}.hy{opacity:.4}   /* 🛠️ P1-20D — .55 measured 3.71:1 */
.np-mean{font-family:var(--font-np);font-weight:500}
/* 5. components */
.comps{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:10px}
.comp{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:12px;display:flex;gap:11px;align-items:center}
.comp .c{font-family:var(--font-display);font-size:34px;line-height:1}
.comp .j{font-weight:700;font-size:14px}.comp .m{font-size:12.5px;color:var(--muted)}
/* 6. course chips */
.chip{display:inline-flex;align-items:center;gap:7px;border:1px solid var(--border-strong);border-radius:999px;
 padding:7px 14px;margin:0 6px 7px 0;font-size:14px;text-decoration:none;color:var(--text-2)}
.chip:hover{background:var(--subtle)}
/* 8/9. words + sentences */
ul.words{list-style:none}
ul.words li{padding:10px 0;border-bottom:1px solid var(--border);display:flex;gap:10px;align-items:baseline;flex-wrap:wrap}
ul.words li:last-child{border-bottom:0}
.w{font-size:19px;font-family:var(--font-display);font-weight:500}
.gloss{color:var(--text-2);font-size:14.5px}.np{color:var(--muted);font-size:14px;font-family:var(--font-np);font-weight:500}   /* 🛠️ P1-6e */
.spk{border:0;background:transparent;cursor:pointer;font-size:15px;padding:2px 4px;color:var(--muted)}
.sent{padding:12px 0;border-bottom:1px solid var(--border)}.sent:last-child{border-bottom:0}
.sent .jp{font-size:17px;line-height:2.1;font-family:var(--font-display);font-weight:500}   /* 🛠️ P1-6e */
ruby rt{font-size:.5em;opacity:0;transition:opacity .15s}
.show-furi ruby rt{opacity:.75}
.furi-btn{float:right;border:1px solid var(--border-strong);background:transparent;color:var(--muted);
 border-radius:8px;padding:3px 9px;font-size:12.5px;cursor:pointer}
/* 7. practice-sheet TEASERS. 🛠️ P1-6d — small thumbnail cards, side by side at EVERY width
   (the phone carousel is gone: these are thumbnails now, not sheets). flex-basis 150 with a
   210 cap gives ~173px each on a 390px phone and 210px on desktop. Nothing is clickable. */
.sheets{display:flex;flex-wrap:wrap;gap:12px;align-items:stretch}
.sheet{flex:1 1 150px;max-width:210px;min-width:0;margin:0;display:flex;flex-direction:column}
/* margin-top:auto keeps the two download buttons level though the two crops differ in height */
.sheet figcaption{margin-top:auto;line-height:1.4}
.thumb{position:relative;border:1px solid var(--border-strong);border-radius:10px;overflow:hidden;
 line-height:0;background:#fffdfa}
.thumb img{display:block;width:100%;height:auto}
/* The sheet dissolves into the paper instead of ending on a hard cut. Literal colours, like
   .stage above: this is a picture of white paper and stays white in both themes. */
.thumb::after{content:"";position:absolute;left:0;right:0;bottom:0;height:62%;pointer-events:none;
 background:linear-gradient(to bottom,rgba(255,253,250,0) 0%,rgba(255,253,250,.85) 55%,#fffdfa 100%)}
.thumb .lock{position:absolute;left:0;right:0;bottom:5px;z-index:1;text-align:center;
 font-size:10px;line-height:1.3;color:#5b514d;letter-spacing:.01em;padding:0 4px}
.sheet .btn{margin:9px 0 0;width:100%;justify-content:center;padding:9px 10px;font-size:13.5px}
/* 10. credits */
.kx-rdfb{font-size:.82em;color:var(--muted);white-space:normal;overflow-wrap:anywhere}   /* 🛠️ P1-19b — nowrap overflowed a 320px screen by 20px */
footer.attrib{margin-top:40px;padding-top:16px;border-top:1px solid var(--border);font-size:12.5px;color:var(--muted)}
footer.attrib a{color:var(--muted)}

/* ==========================================================================
   css/v9/kx_a11y.css
   ========================================================================== */
/* kx_a11y.css v9 — DESIGN PASS 2c section D: contrast on the kanji engine.  🆕 P1-20D
 *
 * #ed1f24 STAYS the display red. Hero strokes, the globe, the hanko frame and any
 * heading >=24px (or >=18.66px bold) only need 3:1, and it clears that on every paper
 * tone (4.29 / 4.02 / 3.91 / 3.34). What fails 4.5:1 is small red text and the
 * white-on-red controls. Measured on live at 390px, 2026-09-26:
 *
 *   .hint a, .kx-w .hk             #ed1f24 on #faf5ef   4.02
 *   .badge.kx-hanko                #ed1f24 on #f8dbd7   3.34  (16px/700 is NOT large text)
 *   .btn / .sbox button /          #fff    on #ed1f24   4.35  light
 *     .kotd-cta / selected tab     #fff    on #ff5f63   2.97  dark
 *   .badge  (JLPT gold seal)       #fff    on #c9a227   2.42 light / 1.96 dark
 *   .steps .no                     rgba(36,29,27,.45)   2.82
 *   .stage .wm                     rgba(36,29,27,.28)   1.82
 *
 * --kx-red-text #c2181c is the LIGHTEST red that clears 4.5 on all four paper tones
 * (6.01 / 5.63 / 5.48 / 4.68) and gives a white label 6.10 on the fill.
 *
 * DARK KEEPS #ff5f63. Darkening the fill there would drop it to 3.03 against the
 * #151011 ground, under the 3:1 that 1.4.11 wants of a control, so in dark the LABEL
 * goes dark instead: #241d1b on #ff5f63 = 5.58, fill vs ground 6.22. Dark red TEXT is
 * left exactly as it is — it measures passing today and it sits on other surfaces.
 *
 * Linked AFTER each page's own inline <style>, so equal-specificity rules win on order.
 * .btn.ghost (0,2,0) still out-specifies the .btn rule below and keeps its own colours.
 */
:root{--kx-red-text:#c2181c;--kx-btn-bg:#c2181c;--kx-btn-fg:#fff}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]){--kx-red-text:#ff5f63;--kx-btn-bg:#ff5f63;--kx-btn-fg:#241d1b}
}
:root[data-theme="dark"],html.dark{--kx-red-text:#ff5f63;--kx-btn-bg:#ff5f63;--kx-btn-fg:#241d1b}

/* ---- small red text ---- */
.hint a{color:var(--kx-red-text)}
.kx-w .hk,.kx-wdetail .dj .hk{color:var(--kx-red-text)}

/* ---- white-on-red controls ---- */
.btn,.sbox button,.kotd-cta,.kx-wtabs button[aria-selected=true]{
  background:var(--kx-btn-bg);color:var(--kx-btn-fg)}

/* ---- JLPT gold seal: keep the gold, ink the label (2.42 / 1.96 -> 6.85 / 8.48) ---- */
.badge{color:#241d1b}
/* the hanko seal is the red badge, not the gold one — (0,2,0) keeps it out of the rule above */
.badge.kx-hanko{color:var(--kx-red-text);border-color:var(--kx-red-text)}

/* ---- muted ink ON PAPER. --paper is cream in BOTH themes, so this is one literal in
        both, exactly as kx_paper.css does it (.45/.28 -> .62 = 4.67 light / 4.51 dark) ---- */
.steps .no,.steps.kx-thumbs .no{color:rgba(36,29,27,.62)}
.stage .wm{color:rgba(36,29,27,.62)}

/* ==========================================================================
   🆕 P1-21 — css/v11 = the v10 bundle above, byte-for-byte, + THIS layer.
   Design pass 2d (Umesh, real Mac): the left rail overlapped the hero.
   ========================================================================== */

/* admin preview bar (only ever emitted to an admin in preview) */
.kx-previewbar{position:fixed;left:12px;bottom:12px;z-index:70;padding:7px 12px;border-radius:10px;
  background:#241d1b;color:#fff;font:600 12.5px/1.4 system-ui,sans-serif;box-shadow:0 4px 18px rgba(0,0,0,.25)}
.kx-previewbar a{color:#ffb3b5;margin-left:6px}

/* BUG (v4 kx_layout): `.kx-col-l{position:sticky}` matched BOTH left blocks — the hero AND
   the parts + course block. A sticky element is bounded by its containing block, which for
   these is the whole page grid, not their grid cell — so the second block rode up and stuck
   ON TOP of the hero. Neither block is sticky now.

   LAYOUT (grid placement only; DOM and phone order untouched):
     row 1          hero                   | stroke order · readings · story  (spans 1–2)
     rows 2–3       parts + course (rail)  |
     row 3                                 | sheets · words · sentences
   Row 1 is exactly the hero's height and row 2 (1fr) absorbs whatever the right-hand first
   section needs beyond it, so the parts start DIRECTLY under "Show me how to write" — no
   blank gap. The rail is sticky only inside its own two-row area, so it follows you down
   beside the words and sentences and can never cross the hero. */
@media (min-width:1000px){
  body.kxn main.wrap.kx-two{grid-template-rows:auto 1fr auto auto}
  body.kxn .kx-col-l{position:static;top:auto}
  body.kxn main.wrap.kx-two > .kx-col-l:nth-child(1){grid-column:1;grid-row:1;align-self:start}
  body.kxn main.wrap.kx-two > .kx-col-r:nth-child(2){grid-column:2;grid-row:1 / span 2;align-self:start}
  body.kxn main.wrap.kx-two > .kx-col-l:nth-child(3){grid-column:1;grid-row:2 / span 2;align-self:stretch;min-width:0}
  body.kxn main.wrap.kx-two > .kx-col-r:nth-child(4){grid-column:2;grid-row:3;align-self:start}
  body.kxn main.wrap.kx-two > .kx-col-full{grid-row:4}
  body.kxn .kx-rail{position:sticky;top:16px}
  body.kxn .kx-rail > h2:first-child{margin-top:18px}
}
@media (max-width:999.98px){
  body.kxn .kx-rail{display:contents}               /* phone: the wrapper is nothing at all */
}

/* ==========================================================================
   🆕 P1-21b — css/v12 = the v11 bundle above, byte-for-byte, + THIS layer.
   Umesh (preview feedback): the hero must NEVER scroll away on a desktop. Only the right
   column moves with the page.

   The left rail is two sticky blocks that together are one rail, top under the header:
     1. the hero (stage, meaning, hanko/badges, "Show me how to write") sticks at its OWN
        natural top (--kx-rail-top), so it never moves at all;
     2. the parts + course block sticks exactly at the hero's bottom (--kx-hero-h, kept
        current by a ResizeObserver in the page) and is capped to what is left of the
        viewport — if it does not fit, it scrolls inside its own area. It can never reach the
        hero: its top IS the hero's bottom.
   Both are bounded by the page grid, so they stay for the whole scroll. DOM order untouched.
   ========================================================================== */
@media (min-width:1000px){
  /* --kx-rail-gap: at the very bottom of the page <main> ends above the viewport by the body's
     bottom padding (40px + safe area); a sticky block cannot pass its container, so a rail taller
     than that allowance was pushed UP into the hero (measured: 24px at 1920x1080). */
  body.kxn{--kx-rail-top:72px;--kx-hero-h:620px;--kx-rail-gap:calc(60px + env(safe-area-inset-bottom, 0px))}
  /* the stage shrinks on a short screen so the parts always keep a usable area */
  body.kxn .kx-col-l .stage{width:clamp(250px, calc(100vh - 470px), 420px)}
  body.kxn main.wrap.kx-two > .kx-col-l:nth-child(1){
    position:sticky;top:var(--kx-rail-top);align-self:start;z-index:2;background:var(--bg)}
  body.kxn main.wrap.kx-two > .kx-col-l:nth-child(3){
    position:sticky;top:calc(var(--kx-rail-top) + var(--kx-hero-h));align-self:start;
    max-height:calc(100vh - var(--kx-rail-top) - var(--kx-hero-h) - var(--kx-rail-gap));
    overflow-y:auto;overscroll-behavior:contain;
    scrollbar-width:thin;scrollbar-color:var(--border-strong,rgba(237,31,36,.26)) transparent;
    padding-right:4px}
  body.kxn main.wrap.kx-two > .kx-col-l:nth-child(3)::-webkit-scrollbar{width:6px}
  body.kxn main.wrap.kx-two > .kx-col-l:nth-child(3)::-webkit-scrollbar-thumb{background:var(--border-strong,rgba(237,31,36,.26));border-radius:3px}
  /* the credits (EDRDG licence) sit in the RIGHT column: the sticky rail would otherwise cover
     their left half at the bottom of the page (seen in the first 1280x800 run) */
  body.kxn main.wrap.kx-two > .kx-col-full{grid-column:2}
  body.kxn .kx-rail{position:static}          /* v11's inner sticky is replaced by the block above */
  body.kxn .kx-rail > h2:first-child{margin-top:14px}
}
