/* ==========================================================================
   Reading practice — design system

   Every colour in the app comes from the tokens below and every measurement
   from the 4px step. If you need a value that isn't here, add a token rather
   than a literal, otherwise this file starts drifting again.

   Palette: aizuri — the indigo of Japanese woodblock printing — on washi
   paper. Celadon means learned or correct, ochre means due soon, clay means
   wrong or destructive. The accent is the indigo itself, not a pop colour.
   ========================================================================== */

:root {
    /* ground */
    --washi: #f4f2ec;
    --surface: #fffdf8;
    --surface-2: #edeae1;
    --surface-3: #e4e0d4;
    --hairline: #d9d5c9;

    /* ink */
    --ink: #191d2b;
    --muted: #5c6274;
    --faint: #7b8194;

    /* accent */
    --indigo: #2b4c9b;
    --indigo-hover: #1f3b7d;
    --indigo-soft: #e2e7f4;
    --indigo-line: #b9c6e6;
    --on-indigo: #fffdf8;

    /* semantic */
    --celadon: #3f7a5e;
    --celadon-soft: #e1ede6;
    --ochre: #8d6318;
    --ochre-soft: #f5ecdb;
    --clay: #a8402f;
    --clay-soft: #f5e3df;

    /* type */
    --ui: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
        "Helvetica Neue", Arial, sans-serif;
    --read: "Iowan Old Style", "Palatino Linotype", Palatino, "Book Antiqua",
        Georgia, serif;
    --cjk: "Hiragino Mincho ProN", "Yu Mincho", YuMincho, "Songti SC",
        "Noto Serif CJK JP", serif;
    --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
        "Liberation Mono", monospace;

    /* step scale — everything spatial is a multiple of 4px */
    --s1: 4px;
    --s2: 8px;
    --s3: 12px;
    --s4: 16px;
    --s5: 20px;
    --s6: 24px;
    --s8: 32px;
    --s10: 40px;
    --s12: 48px;

    --radius-sm: 6px;
    --radius: 10px;
    --radius-lg: 14px;
    --radius-pill: 999px;

    --shadow-sm: 0 1px 2px rgba(25, 29, 43, .06);
    --shadow-lg: 0 8px 20px rgba(25, 29, 43, .12), 0 32px 64px -32px rgba(25, 29, 43, .4);

    --rail-w: 68px;
    --tap: 44px;

    color-scheme: light;
}

@media (prefers-color-scheme: dark) {
    :root {
        --washi: #12151f;
        --surface: #1b1f2c;
        --surface-2: #232838;
        --surface-3: #2b3145;
        --hairline: #2e3446;

        --ink: #e9eaef;
        --muted: #98a0b4;
        --faint: #7d8599;

        --indigo: #93aeee;
        --indigo-hover: #aec3f5;
        --indigo-soft: #232c45;
        --indigo-line: #3b4560;
        --on-indigo: #12151f;

        --celadon: #7fb79a;
        --celadon-soft: #1d2e27;
        --ochre: #d8a24f;
        --ochre-soft: #2e2718;
        --clay: #e08a78;
        --clay-soft: #331f1c;

        --shadow-sm: 0 1px 2px rgba(0, 0, 0, .3);
        --shadow-lg: 0 8px 20px rgba(0, 0, 0, .4), 0 32px 64px -32px rgba(0, 0, 0, .9);

        color-scheme: dark;
    }
}

/* The in-app toggle must beat the media query in both directions. */
:root[data-theme="dark"] {
    --washi: #12151f;
    --surface: #1b1f2c;
    --surface-2: #232838;
    --surface-3: #2b3145;
    --hairline: #2e3446;
    --ink: #e9eaef;
    --muted: #98a0b4;
    --faint: #7d8599;
    --indigo: #93aeee;
    --indigo-hover: #aec3f5;
    --indigo-soft: #232c45;
    --indigo-line: #3b4560;
    --on-indigo: #12151f;
    --celadon: #7fb79a;
    --celadon-soft: #1d2e27;
    --ochre: #d8a24f;
    --ochre-soft: #2e2718;
    --clay: #e08a78;
    --clay-soft: #331f1c;
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, .3);
    --shadow-lg: 0 8px 20px rgba(0, 0, 0, .4), 0 32px 64px -32px rgba(0, 0, 0, .9);
    color-scheme: dark;
}

:root[data-theme="light"] {
    --washi: #f4f2ec;
    --surface: #fffdf8;
    --surface-2: #edeae1;
    --surface-3: #e4e0d4;
    --hairline: #d9d5c9;
    --ink: #191d2b;
    --muted: #5c6274;
    --faint: #7b8194;
    --indigo: #2b4c9b;
    --indigo-hover: #1f3b7d;
    --indigo-soft: #e2e7f4;
    --indigo-line: #b9c6e6;
    --on-indigo: #fffdf8;
    --celadon: #3f7a5e;
    --celadon-soft: #e1ede6;
    --ochre: #8d6318;
    --ochre-soft: #f5ecdb;
    --clay: #a8402f;
    --clay-soft: #f5e3df;
    --shadow-sm: 0 1px 2px rgba(25, 29, 43, .06);
    --shadow-lg: 0 8px 20px rgba(25, 29, 43, .12), 0 32px 64px -32px rgba(25, 29, 43, .4);
    color-scheme: light;
}

/* --------------------------------------------------------------- reset -- */

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

html { background: var(--washi); }

body {
    margin: 0;
    background: var(--washi);
    color: var(--ink);
    font-family: var(--ui);
    font-size: 15px;
    line-height: 1.55;
    -webkit-font-smoothing: antialiased;
    overscroll-behavior-y: none;
}

h1, h2, h3, h4 { margin: 0; font-weight: 600; text-wrap: balance; }
p { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }
img, svg { max-width: 100%; }
a { color: var(--indigo); }

button, input, select, textarea { font: inherit; color: inherit; }

:focus-visible {
    outline: 2px solid var(--indigo);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}

.visually-hidden {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
}

.hidden { display: none !important; }
/* Components set display, which would otherwise beat the hidden attribute. */
[hidden] { display: none !important; }

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .01ms !important;
        scroll-behavior: auto !important;
    }
}

/* ------------------------------------------------------------ app shell -- */

.app {
    display: grid;
    grid-template-columns: var(--rail-w) minmax(0, 1fr);
    min-height: 100vh;
    min-height: 100dvh;
}

.rail {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--s1);
    padding: var(--s3) 0 calc(var(--s3) + env(safe-area-inset-bottom));
    background: var(--surface-2);
    border-right: 1px solid var(--hairline);
    position: sticky;
    top: 0;
    height: 100vh;
    height: 100dvh;
}

.rail-item {
    position: relative;
    width: 52px;
    padding: var(--s2) 0 var(--s1);
    border: 1px solid transparent;
    border-radius: var(--radius);
    background: none;
    color: var(--muted);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 3px;
    cursor: pointer;
    text-decoration: none;
    transition: background .12s, color .12s;
}

.rail-item svg {
    width: 20px; height: 20px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.rail-label { font-size: 10px; letter-spacing: .02em; }

.rail-item:hover { color: var(--ink); background: var(--surface-3); }

.rail-item[aria-current="page"] {
    background: var(--indigo-soft);
    border-color: var(--indigo-line);
    color: var(--indigo);
}

.rail-spacer { flex: 1; }

.rail-badge {
    position: absolute;
    top: 2px; right: 4px;
    min-width: 17px; height: 17px;
    padding: 0 4px;
    border-radius: var(--radius-pill);
    background: var(--clay);
    color: #fff;
    font-family: var(--mono);
    font-size: 9px;
    line-height: 17px;
    text-align: center;
    font-variant-numeric: tabular-nums;
}

/* One icon per theme; the other is hidden. */
.icon-moon { display: none; }
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .icon-sun { display: none; }
    :root:not([data-theme="light"]) .icon-moon { display: block; }
}
:root[data-theme="dark"] .icon-sun { display: none; }
:root[data-theme="dark"] .icon-moon { display: block; }
:root[data-theme="light"] .icon-sun { display: block; }
:root[data-theme="light"] .icon-moon { display: none; }

.main { min-width: 0; display: flex; flex-direction: column; }

/* ----------------------------------------------------------- page head -- */

.page-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s2);
    padding: var(--s3) var(--s5);
    border-bottom: 1px solid var(--hairline);
    background: var(--surface);
}

.page-title { font-size: 15px; margin-right: var(--s2); }

.page-sub {
    font-family: var(--mono);
    font-size: 11px;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--muted);
}

.grow { flex: 1; }

/* ---------------------------------------------------------- primitives -- */

.chip {
    display: inline-flex;
    align-items: center;
    gap: var(--s1);
    height: var(--s8);
    padding: 0 var(--s3);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-pill);
    background: var(--surface);
    color: var(--ink);
    font-size: 13px;
    white-space: nowrap;
    cursor: pointer;
    text-decoration: none;
    transition: border-color .12s, background .12s, color .12s;
}

.chip:hover { border-color: var(--indigo-line); }
.chip[aria-pressed="true"], .chip.is-active {
    background: var(--indigo-soft);
    border-color: var(--indigo-line);
    color: var(--indigo);
    font-weight: 600;
}
.chip .caret { color: var(--faint); font-size: 10px; }
.chip-value { color: var(--muted); }
.chip[aria-pressed="true"] .chip-value, .chip.is-active .chip-value { color: inherit; }

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--s2);
    height: var(--s8);
    padding: 0 var(--s4);
    border: 1px solid var(--indigo);
    border-radius: var(--radius-pill);
    background: var(--indigo);
    color: var(--on-indigo);
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    text-decoration: none;
    transition: background .12s, border-color .12s;
}

.btn:hover { background: var(--indigo-hover); border-color: var(--indigo-hover); }
.btn[disabled] { opacity: .45; cursor: not-allowed; }
.btn svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }

.btn-ghost { background: none; color: var(--ink); border-color: var(--hairline); font-weight: 500; }
.btn-ghost:hover { background: var(--surface-2); border-color: var(--indigo-line); color: var(--indigo); }

.btn-danger { background: var(--clay); border-color: var(--clay); color: #fff; }
.btn-danger:hover { background: var(--clay); border-color: var(--clay); filter: brightness(.92); }

.btn-lg { height: var(--s10); padding: 0 var(--s6); font-size: 14px; }
.btn-block { width: 100%; }
.btn-on { background: var(--celadon-soft); border-color: var(--celadon); color: var(--celadon); }
.btn-on:hover { background: var(--celadon-soft); border-color: var(--celadon); color: var(--celadon); }

.icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--s8); height: var(--s8);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--muted);
    cursor: pointer;
    transition: color .12s, border-color .12s;
}
.icon-btn:hover { color: var(--indigo); border-color: var(--indigo-line); }
.icon-btn svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.icon-btn.is-danger:hover { color: var(--clay); border-color: var(--clay); }
.icon-btn[disabled] { opacity: .4; cursor: not-allowed; }

.card {
    border: 1px solid var(--hairline);
    border-radius: var(--radius-lg);
    background: var(--surface);
    box-shadow: var(--shadow-sm);
}

.label {
    display: block;
    font-family: var(--mono);
    font-size: 10.5px;
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--muted);
    font-weight: 400;
}

.field { display: grid; gap: var(--s2); }
.label-hint { color: var(--faint); letter-spacing: .04em; text-transform: none; }
.field-hint { font-size: 12.5px; color: var(--muted); }

/* The entry a look-up found, shown for confirmation before it is kept. */
.lookup-card {
    padding: var(--s5);
    border: 1px solid var(--hairline);
    border-radius: var(--radius);
    background: var(--surface-2);
    display: grid;
    gap: var(--s1);
    margin-bottom: var(--s3);
}
.lookup-word { font-family: var(--cjk); font-size: 30px; line-height: 1.25; word-break: break-word; }
.lookup-word[data-latin="true"] { font-family: var(--read); }
.lookup-romaji { font-family: var(--mono); font-size: 12.5px; color: var(--muted); }
.lookup-meaning { font-family: var(--read); font-size: 17px; margin-top: var(--s2); }

.input, .select {
    width: 100%;
    height: var(--s10);
    padding: 0 var(--s3);
    border: 1px solid var(--hairline);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--ink);
    font-size: 14px;
}
.input::placeholder { color: var(--faint); }
.input:focus-visible, .select:focus-visible { border-color: var(--indigo); }
.select { appearance: none; padding-right: var(--s8); cursor: pointer; }

.select-wrap { position: relative; }
.select-wrap::after {
    content: "";
    position: absolute;
    right: var(--s4); top: 50%;
    width: 7px; height: 7px;
    margin-top: -5px;
    border-right: 1.5px solid var(--muted);
    border-bottom: 1.5px solid var(--muted);
    transform: rotate(45deg);
    pointer-events: none;
}

.check {
    display: inline-flex;
    align-items: center;
    gap: var(--s2);
    height: var(--s10);
    padding: 0 var(--s3);
    border: 1px solid var(--hairline);
    border-radius: var(--radius);
    background: var(--surface);
    cursor: pointer;
    font-size: 14px;
}
.check input { accent-color: var(--indigo); width: 16px; height: 16px; }
.check:has(input:checked) { border-color: var(--indigo-line); background: var(--indigo-soft); color: var(--indigo); }

.pill {
    display: inline-block;
    padding: 2px var(--s2);
    border-radius: var(--radius-pill);
    background: var(--indigo-soft);
    color: var(--indigo);
    font-family: var(--mono);
    font-size: 10px;
    letter-spacing: .07em;
    text-transform: uppercase;
}
.pill-muted { background: var(--surface-3); color: var(--muted); }
.pill-good { background: var(--celadon-soft); color: var(--celadon); }
.pill-warn { background: var(--ochre-soft); color: var(--ochre); }
.pill-bad { background: var(--clay-soft); color: var(--clay); }

.banner {
    display: flex;
    align-items: flex-start;
    gap: var(--s2);
    padding: var(--s3) var(--s4);
    border: 1px solid var(--hairline);
    border-radius: var(--radius);
    font-size: 13.5px;
}
.banner-error { background: var(--clay-soft); border-color: var(--clay); color: var(--clay); }
.banner-ok { background: var(--celadon-soft); border-color: var(--celadon); color: var(--celadon); }
.banner ul { display: grid; gap: var(--s1); }

.empty {
    display: grid;
    justify-items: center;
    gap: var(--s3);
    padding: var(--s12) var(--s5);
    text-align: center;
    color: var(--muted);
}
.empty svg { width: 40px; height: 40px; fill: none; stroke: currentColor; stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; opacity: .5; }
.empty-title { font-size: 16px; font-weight: 600; color: var(--ink); }
.empty p { max-width: 44ch; font-size: 13.5px; }

/* --------------------------------------------------------------- toasts -- */

.toasts {
    position: fixed;
    left: 50%;
    bottom: calc(var(--s5) + env(safe-area-inset-bottom));
    transform: translateX(-50%);
    display: grid;
    gap: var(--s2);
    z-index: 90;
    width: min(420px, calc(100vw - var(--s8)));
    pointer-events: none;
}

.toast {
    display: flex;
    align-items: center;
    gap: var(--s2);
    padding: var(--s3) var(--s4);
    border: 1px solid var(--hairline);
    border-radius: var(--radius);
    background: var(--surface);
    box-shadow: var(--shadow-lg);
    font-size: 13.5px;
    pointer-events: auto;
    animation: toast-in .18s ease-out;
}
.toast-error { border-color: var(--clay); color: var(--clay); background: var(--clay-soft); }
.toast-ok { border-color: var(--celadon); color: var(--celadon); background: var(--celadon-soft); }
.toast.is-leaving { animation: toast-out .18s ease-in forwards; }
.toast-close { margin-left: auto; border: 0; background: none; color: inherit; cursor: pointer; opacity: .7; font-size: 16px; line-height: 1; }

@keyframes toast-in { from { opacity: 0; transform: translateY(8px); } }
@keyframes toast-out { to { opacity: 0; transform: translateY(8px); } }

/* --------------------------------------------------------------- modal -- */

.modal {
    position: fixed;
    inset: 0;
    z-index: 100;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--s5);
    background: rgba(25, 29, 43, .5);
}
.modal-card {
    width: min(460px, 100%);
    max-height: calc(100dvh - var(--s10));
    overflow-y: auto;
    padding: var(--s6);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-lg);
    background: var(--surface);
    box-shadow: var(--shadow-lg);
    display: grid;
    gap: var(--s4);
}
.modal-title { font-size: 17px; }
.modal-actions { display: flex; gap: var(--s2); justify-content: flex-end; }

/* One saved word, opened from its row. Wider than the plain modal because it
   carries the character breakdown, which is a list rather than a sentence. */
.word-modal-card { width: min(520px, 100%); }
.word-detail-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--s3);
}
.word-detail-word {
    font-family: var(--cjk);
    font-size: 28px;
    line-height: 1.25;
    word-break: break-word;
}
.word-detail-word[data-latin="true"] { font-family: var(--read); }

.word-stats {
    display: grid;
    gap: var(--s2);
    padding-top: var(--s4);
    border-top: 1px solid var(--hairline);
}
.word-stat-rows { display: grid; gap: var(--s1); }
.word-stat {
    display: grid;
    grid-template-columns: 1fr auto auto;
    align-items: center;
    gap: var(--s3);
    padding: var(--s2) var(--s3);
    border-radius: var(--radius);
    background: var(--surface-2);
    font-size: 13px;
}
.word-stat-name { font-weight: 500; }
.word-stat-when {
    font-family: var(--mono);
    font-size: 11.5px;
    color: var(--muted);
    white-space: nowrap;
}
/* A question that has never been put is not a question going badly, and five
   empty bars on a full-strength row would read as the latter. */
.word-stat.is-untouched { opacity: .62; }

/* The row opens the word, so it has to look like it does something. */
tr[data-word-row] { cursor: pointer; }
tr[data-word-row]:hover { background: var(--surface-2); }
tr[data-word-row]:focus-visible {
    outline: 2px solid var(--indigo);
    outline-offset: -2px;
}

/* --------------------------------------------------------------- table -- */

.table-wrap { overflow-x: auto; }

.table { width: 100%; border-collapse: collapse; font-size: 14px; }
.table th {
    text-align: left;
    padding: var(--s2) var(--s4);
    border-bottom: 1px solid var(--hairline);
    font-family: var(--mono);
    font-weight: 400;
    font-size: 10px;
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--muted);
    white-space: nowrap;
}
.table td {
    padding: var(--s2) var(--s4);
    border-bottom: 1px solid var(--hairline);
    vertical-align: middle;
}
.table tbody tr:hover td { background: var(--surface-2); }
.table .num { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.table .right { text-align: right; }

.pager {
    display: flex;
    align-items: center;
    gap: var(--s3);
    padding: var(--s3) var(--s5);
    border-top: 1px solid var(--hairline);
    font-size: 13px;
    color: var(--muted);
}

/* ================================================================ READ == */

.reader { display: grid; grid-template-columns: minmax(0, 1fr) 300px; flex: 1; min-height: 0; }
.reader-col { display: flex; flex-direction: column; min-width: 0; }

.passage { padding: var(--s8) var(--s8) var(--s10); min-width: 0; }
.passage-meta {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s4);
    margin-bottom: var(--s5);
    font-family: var(--mono);
    font-size: 10.5px;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--muted);
}

.line {
    font-family: var(--cjk);
    font-size: 27px;
    line-height: 2.05;
    letter-spacing: .02em;
    max-width: 28em;
    /* Kinsoku: a Japanese line may not begin with 。or 、 */
    line-break: strict;
    overflow-wrap: break-word;
}
.line[data-latin="true"] { font-family: var(--read); line-height: 1.85; max-width: 34em; }

.word {
    border: 0;
    background: none;
    font: inherit;
    color: inherit;
    text-align: left;
    padding: 1px 2px;
    margin: 0 1px;
    border-radius: var(--radius-sm);
    border-bottom: 1.5px solid transparent;
    cursor: pointer;
    transition: background .12s;
}

/* When the passage carries its own spacing and punctuation, the words must sit
   flush against it — any margin would invent gaps the sentence does not have. */
.line[data-tight="true"] .word { margin: 0; padding: 0; }

/* Holds a word together with the punctuation that follows it, so a line can
   never begin with 。or 、. Browsers ignore display:inline on a <button>, so
   the break has to be prevented around it instead. */
.word-group { white-space: nowrap; }
.word:hover { background: var(--indigo-soft); }
.word.is-seen { border-bottom-color: var(--indigo-line); }
.word.is-saved { border-bottom-color: var(--celadon); }
.word[aria-pressed="true"] { background: var(--indigo); color: var(--on-indigo); }

.reveal {
    max-width: 52em;
    margin-top: var(--s6);
    padding-top: var(--s5);
    border-top: 1px solid var(--hairline);
    display: grid;
    gap: var(--s5);
}
.reveal-head { display: flex; align-items: center; gap: var(--s3); margin-bottom: var(--s2); }
.reveal p { font-family: var(--read); font-size: 16.5px; line-height: 1.6; }
.reveal p.romaji { font-family: var(--mono); font-size: 13.5px; color: var(--muted); }
.is-blurred { filter: blur(5px); opacity: .5; user-select: none; transition: filter .18s, opacity .18s; }

.reveal-toggle {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 24px;
    padding: 0 var(--s2);
    font-size: 11px;
    border-radius: var(--radius-pill);
    border: 1px solid var(--hairline);
    background: var(--surface);
    color: var(--muted);
    cursor: pointer;
}
.reveal-toggle:hover { color: var(--indigo); border-color: var(--indigo-line); }

.gloss {
    border-left: 1px solid var(--hairline);
    background: var(--surface-2);
    padding: var(--s5);
    display: flex;
    flex-direction: column;
    gap: var(--s4);
    min-width: 0;
    /* Stays with you down a long text. As an ordinary column it scrolled away
       with the paragraphs, so on anything longer than a screen you could tap a
       word and be looking at an empty margin.
       `align-self: start` is what makes the sticking possible: a grid item is
       stretched to the row by default, and an item as tall as its container
       has nowhere to stick. The height then has to be given back, or the panel
       would shrink to its contents and leave the sidebar half painted. */
    position: sticky;
    top: 0;
    align-self: start;
    height: 100vh;
    height: 100dvh;
    overflow-y: auto;
}
/* The sheet's handle, and only a sheet has one — see the bottom-sheet rules
   under the 1020px breakpoint, where this is turned on. */
.gloss-grip { display: none; }

.gloss-empty { font-size: 13.5px; color: var(--muted); }
.gloss-word { font-family: var(--cjk); font-size: 30px; line-height: 1.25; word-break: break-word; }
.gloss-word[data-latin="true"] { font-family: var(--read); }
.gloss-romaji { font-family: var(--mono); font-size: 12.5px; color: var(--muted); margin-top: 2px; }
.gloss-translation { font-family: var(--read); font-size: 17px; margin-top: var(--s3); }
.gloss-tags { display: flex; flex-wrap: wrap; gap: var(--s1); margin-top: var(--s2); }
.gloss-actions { display: flex; gap: var(--s2); margin-top: var(--s5); }
.gloss-actions .btn { flex: 1; }
.gloss-foot {
    margin-top: auto;
    padding-top: var(--s4);
    border-top: 1px solid var(--hairline);
    font-size: 12.5px;
    color: var(--muted);
    display: grid;
    gap: var(--s3);
}
.gloss-foot b { color: var(--ink); font-family: var(--mono); font-variant-numeric: tabular-nums; }

/* ----------------------------------------------------------- alphabet -- */

.chip-tabs { display: flex; gap: var(--s1); }
/* Separates the section chips from the controls that belong to that section. */
.head-rule {
    width: 1px;
    align-self: stretch;
    margin: 0 var(--s1);
    background: var(--hairline);
}
.chip[aria-current="page"] {
    background: var(--indigo-soft);
    border-color: var(--indigo-line);
    color: var(--indigo);
    font-weight: 600;
}

.kana-page { padding: var(--s6) var(--s5) var(--s10); display: grid; gap: var(--s8); }
.kana-intro {
    font-family: var(--read);
    font-size: 15px;
    line-height: 1.6;
    color: var(--muted);
    max-width: 62ch;
}
.kana-group-head { display: grid; gap: var(--s1); margin-bottom: var(--s4); }
.kana-hint {
    font-size: 13px;
    line-height: 1.5;
    color: var(--muted);
    max-width: 46ch;
}

/* One measure for the whole page. The chart headings are laid out on the same
   two blocks as the rows beneath them, so "Voiced" sits over the voiced half
   rather than merely near it. */
.kana-page { --kana-cell: clamp(58px, 5.6vw, 92px); }

.kana-grid {
    display: grid;
    grid-template-columns: repeat(var(--kana-cols, 5), var(--kana-cell));
    gap: var(--s2);
}

.kana-chart { display: grid; gap: var(--s2); }
.kana-chart-head,
.kana-row {
    display: grid;
    grid-template-columns:
        calc(5 * var(--kana-cell) + 4 * var(--s2))
        calc(5 * var(--kana-cell) + 4 * var(--s2));
    gap: var(--s2) var(--s10);
    justify-content: start;
}
.kana-chart-head { align-items: end; margin-bottom: var(--s3); }
/* Without this the は row's cells stretch to the height of ば plus ぱ beside
   them, and one row of the chart ends up twice as tall as the rest. */
.kana-row { align-items: start; }
/* は is the one row that voices two ways, ば and ぱ; they stack rather than
   running off to the right, so every other row keeps its place. */
.kana-voiced { display: grid; gap: var(--s2); align-content: start; }

.kana-cell {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    min-height: 76px;
    padding: var(--s2);
    border: 1px solid var(--hairline);
    border-radius: var(--radius);
    background: var(--surface);
    cursor: pointer;
    transition: border-color .12s, background .12s, transform .08s;
}
.kana-cell:hover { border-color: var(--indigo-line); background: var(--indigo-soft); }
.kana-cell:active { transform: scale(.97); }
.kana-cell.is-gap {
    border-style: none;
    background: none;
    cursor: default;
    min-height: 0;
}
/* Recording a character that has never been played takes a moment. */
.kana-cell.is-loading { opacity: .55; cursor: progress; }
.kana-cell.is-played { border-color: var(--indigo); }

.kana-char { font-family: var(--cjk); font-size: 28px; line-height: 1.15; }
.kana-romaji {
    font-family: var(--mono);
    font-size: 11px;
    letter-spacing: .04em;
    color: var(--muted);
}
.kana-flag {
    position: absolute;
    top: 4px; right: 7px;
    font-family: var(--mono);
    font-size: 13px;
    line-height: 1;
    color: var(--ochre);
}

/* ----------------------------------------------------- alphabet drill -- */

.drill-settings {
    display: grid;
    gap: var(--s4);
    padding: var(--s4);
    border: 1px solid var(--hairline);
    border-radius: var(--radius);
    background: var(--surface);
}
.drill-set { border: 0; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--s2); }
.drill-set legend { padding: 0; margin-bottom: var(--s2); float: left; width: 100%; }
.drill-set-hint { width: 100%; margin: calc(var(--s1) * -1) 0 var(--s1); }

.drill-task { display: flex; align-items: center; justify-content: center; gap: var(--s2); }
/* What is being asked and what you answer with are two things, not one block:
   the character needs room around it to be looked at. */
.drill-body { display: grid; gap: var(--s6); }
.drill-task .drill-prompt-sub { margin-top: 0; }
.option-kana { font-family: var(--cjk); font-size: 26px; line-height: 1.2; }

.drill-answer-actions { display: flex; gap: var(--s2); margin-top: var(--s2); }

/* Writing the word out. The field takes Japanese, so it gets the reading face
   and room for a whole word rather than the app's usual input sizing. */
.typed { display: grid; gap: var(--s3); }
.typed-input {
    width: 100%;
    height: auto;
    padding: var(--s3) var(--s4);
    font-family: var(--cjk);
    font-size: 24px;
    text-align: center;
}
.typed-note { font-size: 13.5px; line-height: 1.45; min-height: 1.45em; color: var(--muted); }
.typed-note.is-ok { color: var(--celadon); }
.typed-note.is-warn { color: var(--ochre); }
.typed-note.is-bad { color: var(--clay); }

/* The answer sits beside the square when there is room and underneath it when
   there is not. Without the wrap the square gives up all its width to the
   answer panel and there is nothing left to have drawn on. */
.draw-area {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    justify-content: center;
    gap: var(--s3);
}
.draw-pad {
    position: relative;
    /* A basis rather than a width: as a flex item, width:100% resolves against
       the wrong box and the square collapses to half its size. */
    flex: 1 1 220px;
    min-width: 200px;
    max-width: 260px;
    aspect-ratio: 1;
    border: 1px solid var(--hairline);
    border-radius: var(--radius);
    background: var(--surface);
}
#draw-canvas {
    display: block;
    width: 100%;
    height: 100%;
    color: var(--ink);
    /* Named here so the canvas can read it and stay in step with the theme. */
    --guide: var(--hairline);
    border-radius: var(--radius);
    cursor: crosshair;
    /* Without this a finger scrolls the page instead of drawing on it. */
    touch-action: none;
}
.draw-answer {
    display: grid;
    align-content: center;
    justify-items: center;
    gap: var(--s2);
    padding: var(--s3);
    border: 1px solid var(--celadon);
    border-radius: var(--radius);
    background: var(--celadon-soft);
    flex: 0 0 auto;
    min-width: 108px;
}
.draw-target { font-family: var(--cjk); font-size: 56px; line-height: 1.1; color: var(--ink); }

/* The character writing itself. The stroke files colour themselves from these
   two properties, so the playback follows the theme like everything else. */
.draw-play { width: 108px; }
.kana-play {
    display: block;
    width: 100%;
    height: auto;
    fill: none;
    stroke: var(--ink);
    /* The source draws at this weight in a 1024 box; anything thinner stops
       looking like a brush and starts looking like a diagram. */
    stroke-width: 96;
    stroke-linecap: round;
    stroke-linejoin: round;
}
.draw-play:empty { display: none; }

.draw-verdict {
    font-size: 13.5px;
    line-height: 1.45;
    padding: var(--s2) var(--s3);
    border-radius: var(--radius);
    border: 1px solid var(--hairline);
}
.draw-verdict.is-ok { border-color: var(--celadon); background: var(--celadon-soft); color: var(--celadon); }
.draw-verdict.is-warn { border-color: var(--ochre); background: var(--ochre-soft); color: var(--ochre); }
.draw-verdict.is-bad { border-color: var(--clay); background: var(--clay-soft); color: var(--clay); }
/* Undo and Clear act on the square but are not part of it. Sitting close under
   its edge they read as a border of the pad rather than as controls. */
.draw-tools { display: flex; justify-content: center; gap: var(--s2); margin-top: var(--s4); }

/* The kanji you have kept. A list rather than a grid: each one carries a
   meaning and a reading, which a chart cell has no room for. */
.kanji-kept {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
    gap: var(--s3);
    /* Cards fill their row rather than each stopping at its own content, so a
       row of them ends on one line instead of a ragged edge. Rows are still
       sized to what is in them, so one character with a long note does not
       stretch every card on the page. */
}
.kanji-kept-item {
    display: flex;
    flex-direction: column;
    gap: var(--s3);
    padding: var(--s4);
    border: 1px solid var(--hairline);
    border-radius: var(--radius);
    background: var(--surface);
}
.kanji-kept-head { display: flex; align-items: flex-start; gap: var(--s3); }
.kanji-kept-char { font-family: var(--cjk); font-size: 44px; line-height: 1.05; flex: none; }
.kanji-kept-sense { min-width: 0; flex: 1; display: grid; gap: 2px; }
.kanji-kept-meaning { font-family: var(--read); font-size: 16px; line-height: 1.35; }
.kanji-kept-actions { flex: none; display: flex; flex-direction: column; gap: var(--s1); }
.kanji-kept-drop { flex: none; }
.kanji-kept-item .kanji-tags,
.kanji-kept-item .kanji-note { margin-top: 0; }
.kanji-kept-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s3);
    /* Pushed to the foot of the card, so the rule above it lands at the same
       height on every card of a row however long the note above it runs. */
    margin-top: auto;
    padding-top: var(--s3);
    border-top: 1px solid var(--hairline);
}

.kana-marks { display: flex; flex-wrap: wrap; gap: var(--s3); }
.kana-mark {
    display: flex;
    align-items: center;
    gap: var(--s3);
    padding: var(--s3) var(--s4);
    border: 1px solid var(--hairline);
    border-radius: var(--radius);
    background: var(--surface);
    max-width: 380px;
}
.kana-mark-name { font-size: 14px; font-weight: 600; }
.kana-mark-note { font-size: 12.5px; color: var(--muted); line-height: 1.45; }
.kana-foot { font-size: 12.5px; color: var(--muted); }
.kana-foot b { color: var(--ochre); }

/* ----------------------------------------------------------- numbers -- */

/* Every tappable thing on the numbers page is one of these: a figure, its
   reading, and a hit area big enough to mean it. They differ in what else they
   carry, so the shared part is here and the rest is per block. */
.num-cell,
.num-rank,
.num-change,
.num-row {
    display: flex;
    border: 1px solid var(--hairline);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--ink);
    cursor: pointer;
    text-align: left;
    transition: border-color .12s, background .12s, transform .08s;
}
.num-cell:hover,
.num-rank:hover,
.num-change:hover,
.num-row:hover { border-color: var(--indigo-line); background: var(--indigo-soft); }
.num-cell:active,
.num-rank:active,
.num-change:active,
.num-row:active { transform: scale(.98); }
.num-cell.is-loading,
.num-rank.is-loading,
.num-change.is-loading,
.num-row.is-loading { opacity: .55; cursor: progress; }
.num-cell.is-played,
.num-rank.is-played,
.num-change.is-played,
.num-row.is-played { border-color: var(--indigo); }

.num-reading { font-family: var(--cjk); font-size: 14px; line-height: 1.35; }
.num-note { font-size: 12.5px; color: var(--muted); line-height: 1.45; }

/* The five sound changes are marked in every table they appear in, so a
   learner scanning the hundreds sees at a glance which three to be careful
   about. Colour alone would not say it, so the marked ones also carry a rule
   down their leading edge. */
.num-reading.is-change,
.num-row.is-change .num-reading { color: var(--ochre); font-weight: 600; }
.num-row.is-change { border-left: 3px solid var(--ochre); }

.num-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
    gap: var(--s2);
}
.num-cell {
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    min-height: 96px;
    padding: var(--s3) var(--s2);
    text-align: center;
}
.num-figure { font-family: var(--mono); font-size: 24px; font-weight: 600; }
.num-kanji { font-family: var(--cjk); font-size: 15px; color: var(--muted); }
.num-alt { font-family: var(--cjk); font-size: 12px; color: var(--muted); }

.num-ranks {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
    gap: var(--s2);
}
.num-rank { align-items: center; gap: var(--s3); padding: var(--s3) var(--s4); }
.num-rank-kanji { font-family: var(--cjk); font-size: 30px; line-height: 1; flex: none; }
.num-rank-body { display: grid; gap: 1px; min-width: 0; }
.num-rank-figure {
    font-family: var(--mono);
    font-size: 13px;
    font-variant-numeric: tabular-nums;
    color: var(--muted);
}

.num-changes {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: var(--s2);
}
.num-change {
    flex-direction: column;
    gap: 2px;
    padding: var(--s3) var(--s4);
    border-left: 3px solid var(--ochre);
}
.num-change-figure {
    font-family: var(--mono);
    font-size: 20px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}
.num-change-not {
    font-family: var(--cjk);
    font-size: 12.5px;
    color: var(--muted);
    text-decoration: line-through;
}

.num-tables {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: var(--s6);
    align-items: start;
}
.num-table { display: grid; gap: var(--s1); align-content: start; }
.num-table .label { margin-bottom: var(--s1); }
.num-row {
    align-items: baseline;
    gap: var(--s3);
    padding: var(--s2) var(--s3);
}
.num-row-figure {
    flex: none;
    min-width: 4.5ch;
    font-family: var(--mono);
    font-size: 13px;
    font-variant-numeric: tabular-nums;
    color: var(--muted);
}

.num-counters {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    gap: var(--s4);
}
.num-counter {
    display: grid;
    gap: var(--s3);
    align-content: start;
    padding: var(--s4);
    border: 1px solid var(--hairline);
    border-radius: var(--radius);
    background: var(--surface-2);
}
.num-counter-head { display: flex; align-items: center; gap: var(--s3); }
.num-counter-kanji { font-family: var(--cjk); font-size: 28px; line-height: 1; flex: none; }
.num-counter-body { display: grid; gap: 1px; min-width: 0; }
.num-counter-what { font-size: 12.5px; color: var(--muted); line-height: 1.45; }
.num-counter-rows { display: grid; gap: var(--s1); }

/* The characters a word is written with, under the word itself. */
.kanji-title { padding-top: var(--s4); border-top: 1px solid var(--hairline); }
.kanji-list { display: grid; gap: var(--s3); margin-top: var(--s3); }
.kanji-item {
    border: 1px solid var(--hairline);
    border-radius: var(--radius);
    background: var(--surface);
    padding: var(--s3) var(--s4) var(--s4);
}
.kanji-head { display: flex; gap: var(--s3); align-items: flex-start; }
.kanji-char {
    font-family: var(--cjk);
    font-size: 34px;
    line-height: 1.1;
    flex: none;
}
.kanji-sense { min-width: 0; }
.kanji-meaning { font-family: var(--read); font-size: 15px; line-height: 1.35; }
.kanji-readings {
    font-family: var(--cjk);
    font-size: 12.5px;
    color: var(--muted);
    margin-top: 2px;
    word-break: break-word;
}
.kanji-readings b {
    font-family: var(--mono);
    font-size: 10.5px;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--muted);
    margin-right: var(--s2);
}
.kanji-tags { display: flex; flex-wrap: wrap; gap: var(--s1); margin-top: var(--s3); }
.kanji-note {
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--muted);
    margin-top: var(--s3);
}
.kanji-keep { width: 100%; margin-top: var(--s3); }
.kanji-loading {
    display: flex;
    align-items: center;
    gap: var(--s2);
    font-size: 12.5px;
    color: var(--muted);
    margin-top: var(--s3);
}
.kanji-limit { font-size: 12px; color: var(--muted); margin-top: var(--s3); }

.legend { display: flex; flex-wrap: wrap; gap: var(--s3); font-size: 12px; color: var(--muted); }
.legend i { display: inline-block; width: 18px; border-bottom: 1.5px solid; margin-right: var(--s1); vertical-align: 4px; }

.picker { position: relative; }
.picker-panel {
    position: absolute;
    z-index: 30;
    top: calc(100% + var(--s1));
    left: 0;
    width: 220px;
    padding: var(--s2);
    border: 1px solid var(--hairline);
    border-radius: var(--radius);
    background: var(--surface);
    box-shadow: var(--shadow-lg);
    display: grid;
    gap: var(--s1);
    max-height: 280px;
    overflow-y: auto;
}
.picker-item {
    display: flex;
    align-items: center;
    gap: var(--s2);
    padding: var(--s1) var(--s2);
    border-radius: var(--radius-sm);
    font-size: 13.5px;
    cursor: pointer;
}
.picker-item:hover { background: var(--surface-2); }
.picker-item input { accent-color: var(--indigo); }

/* =============================================================== DRILL == */

/* The drill claims the height left under the head, so it has to use it. It
   used to pin the card to the top of that box and leave the rest empty. */
.drill { flex: 1; display: grid; place-items: start center; padding: var(--s8) var(--s5); }
.drill-inner {
    width: 100%;
    /* Grows with the window between about 1100 and 1600px, along with the
       prompt and the answer buttons below, so the card scales as one thing.
       440 was chosen for a laptop and looks marooned on anything larger. */
    max-width: clamp(440px, 40vw, 560px);
    display: grid;
    gap: var(--s4);
    /* Centred by auto margins rather than by place-items, because the tallest
       card — the alphabet drill with its deck panel open and the drawing
       square — is taller than a laptop screen. Auto margins take the free
       space when there is some and collapse to nothing when there is not, so
       a card too big for the window starts at the top where it can be reached
       instead of hanging off both ends. */
    margin-block: auto;
}

.drill-head { display: flex; align-items: center; gap: var(--s3); }
.progress { flex: 1; height: 4px; border-radius: 2px; background: var(--surface-3); overflow: hidden; }
.progress > i { display: block; height: 100%; width: 0; background: var(--indigo); transition: width .25s; }
.count { font-family: var(--mono); font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; }

.drill-card { padding: var(--s6) var(--s5); text-align: center; display: grid; gap: var(--s5); }
.drill-prompt { font-family: var(--cjk); font-size: clamp(40px, 3.6vw, 56px); line-height: 1.2; word-break: break-word; }
.drill-prompt[data-latin="true"] { font-family: var(--read); }
.drill-prompt-sub { font-family: var(--mono); font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); margin-top: var(--s1); }

.options { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s2); }
.option {
    /* Floor is the touch target; the cap holds two lines of a long meaning
       without the button becoming a slab. */
    min-height: clamp(var(--tap), 4vw, 64px);
    padding: var(--s2) var(--s3);
    border: 1px solid var(--hairline);
    border-radius: var(--radius);
    background: var(--surface-2);
    color: var(--ink);
    font-family: var(--read);
    font-size: clamp(15.5px, 1.4vw, 17.5px);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--s2);
    text-align: center;
    transition: border-color .12s, background .12s;
}
.option:hover:not([disabled]) { border-color: var(--indigo-line); }
.option kbd {
    font-family: var(--mono);
    font-size: 10px;
    color: var(--muted);
    border: 1px solid var(--hairline);
    border-radius: 3px;
    padding: 1px 4px;
    background: var(--surface);
    flex: none;
}
.option.is-right { background: var(--celadon-soft); border-color: var(--celadon); color: var(--celadon); }
.option.is-wrong { background: var(--clay-soft); border-color: var(--clay); color: var(--clay); }
.option[disabled] { cursor: default; }

.drill-answer { display: grid; gap: var(--s2); padding-top: var(--s4); border-top: 1px solid var(--hairline); }
.drill-answer-romaji { font-family: var(--mono); font-size: 13px; color: var(--muted); }
.drill-answer-meaning { font-family: var(--read); font-size: 16px; }

.drill-foot { display: flex; gap: var(--s2); }

/* The card is the focus; the controls under it get room to sit apart from it. */
#unanswered-actions { margin-top: var(--s4); }
/* The draw card is taller and its own contents are spaced further apart, so
   the same 16px underneath it reads as no gap at all. */
.draw-foot { margin-top: var(--s6); }

.drill-help { display: grid; justify-items: center; gap: var(--s3); margin-top: var(--s2); }
.drill-help-toggle {
    width: 26px; height: 26px;
    border: 1px solid var(--hairline);
    border-radius: 50%;
    background: none;
    color: var(--faint);
    font-size: 13px;
    line-height: 1;
    cursor: pointer;
    display: grid;
    place-items: center;
    transition: color .12s, border-color .12s;
}
.drill-help-toggle:hover { color: var(--indigo); border-color: var(--indigo-line); }
.drill-help-toggle[aria-expanded="true"] {
    color: var(--indigo);
    border-color: var(--indigo-line);
    background: var(--indigo-soft);
}

.scale {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s2);
    justify-content: center;
    font-family: var(--mono);
    font-size: 10.5px;
    color: var(--muted);
}
.scale span { padding: 2px var(--s2); border-radius: var(--radius-pill); background: var(--surface-2); }

/* ========================================================== DICTIONARY == */

.dict-tools {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s2);
    align-items: center;
    padding: var(--s3) var(--s5);
    border-bottom: 1px solid var(--hairline);
    background: var(--surface);
}
.dict-search { flex: 1; min-width: 180px; height: var(--s8); border-radius: var(--radius-pill); padding: 0 var(--s4); }
.dict-select { width: auto; height: var(--s8); border-radius: var(--radius-pill); font-size: 13px; padding: 0 var(--s6) 0 var(--s3); }

.dict-word { font-family: var(--cjk); font-size: 19px; }
.dict-word[data-latin="true"] { font-family: var(--read); }
.dict-romaji { font-family: var(--mono); font-size: 12px; color: var(--muted); }
.dict-verb { display: block; font-size: 11px; color: var(--faint); }
.dict-meaning { font-family: var(--read); font-size: 15px; }

.bars { display: inline-flex; gap: 2px; vertical-align: middle; }
.bars i { width: 5px; height: 13px; border-radius: 1px; background: var(--surface-3); }
.bars i.on { background: var(--celadon); }

.due { font-family: var(--mono); font-size: 11.5px; font-variant-numeric: tabular-nums; white-space: nowrap; }
.due-now { color: var(--clay); }
.due-soon { color: var(--ochre); }
.due-later { color: var(--muted); }

/* ============================================================= LIBRARY == */

.tag-bar {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s2);
    padding: var(--s3) var(--s5);
    border-bottom: 1px solid var(--hairline);
}

.lib-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: var(--s4);
    padding: var(--s5);
    align-content: start;
}

.lib-card {
    display: flex;
    flex-direction: column;
    gap: var(--s3);
    padding: var(--s5);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-lg);
    background: var(--surface);
    box-shadow: var(--shadow-sm);
}
.lib-card:hover { border-color: var(--indigo-line); }

.lib-card-link { text-decoration: none; color: inherit; display: grid; gap: var(--s2); }
.lib-title { font-size: 17px; line-height: 1.3; }
.lib-preview {
    font-family: var(--cjk);
    font-size: 15px;
    line-height: 1.7;
    color: var(--muted);
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.lib-preview[data-latin="true"] { font-family: var(--read); line-height: 1.5; }

.lib-meta,
.lib-tags {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s2) var(--s3);
    font-family: var(--mono);
    font-size: 10.5px;
    letter-spacing: .07em;
    text-transform: uppercase;
    color: var(--faint);
}
.lib-tags { text-transform: none; letter-spacing: 0; }
.lib-tags .pill { text-decoration: none; }

.lib-foot { display: flex; align-items: center; gap: var(--s2); margin-top: auto; }
.lib-rating { font-size: 13px; color: var(--ochre); font-variant-numeric: tabular-nums; }
.lib-rating-count { color: var(--faint); }
.lib-rating-none { color: var(--faint); }
.lib-owner { font-size: 12px; color: var(--faint); }

.lib-progress { display: flex; align-items: center; gap: var(--s2); }
.lib-progress-bar { display: flex; align-items: center; gap: var(--s3); margin-bottom: var(--s5); max-width: 52em; }

/* ---- the paragraph reader ---- */

.lib-passage { padding-bottom: var(--s12); }
.lib-hint { max-width: 52em; margin-bottom: var(--s6); }

.para {
    max-width: 52em;
    padding: var(--s4) var(--s4) var(--s5);
    margin-bottom: var(--s4);
    border: 1px solid transparent;
    border-radius: var(--radius);
    cursor: pointer;
    transition: border-color .12s, background .12s;
}
.para:hover { border-color: var(--hairline); background: var(--surface); }
.para.is-read { border-color: var(--hairline); }

/* Only a words load dims the paragraph, because that call replaces the text.
   Translating leaves it readable — you may still be reading it while it runs. */
.para.is-loading { cursor: progress; }
.para.is-loading .para-body { opacity: .5; }

.para-status {
    display: inline-flex;
    align-items: center;
    gap: var(--s2);
    font-family: var(--mono);
    font-size: 10.5px;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--indigo);
}

.spinner {
    width: 13px;
    height: 13px;
    flex: none;
    border: 2px solid var(--indigo-line);
    border-top-color: var(--indigo);
    border-radius: 50%;
    animation: spin .6s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* With motion reduced the ring cannot spin, so the wording carries it and the
   ring pulses gently instead of sitting there looking frozen. */
@media (prefers-reduced-motion: reduce) {
    .spinner { animation: none; border-top-color: var(--indigo); opacity: .8; }
}

.para-head {
    display: flex;
    align-items: center;
    gap: var(--s3);
    margin-bottom: var(--s2);
}
.para-number {
    font-family: var(--mono);
    font-size: 11px;
    color: var(--faint);
    min-width: 2ch;
    font-variant-numeric: tabular-nums;
}
/* Script toggle: icon only, with its name on hover and on focus. */
.para-script {
    position: relative;
    width: 32px;
    height: 28px;
    border-radius: var(--radius-pill);
}
.para-script svg { width: 19px; height: 19px; }
.para-script .icon-to-kanji { display: none; }
.para-script[aria-pressed="true"] {
    background: var(--indigo-soft);
    border-color: var(--indigo-line);
    color: var(--indigo);
}
.para-script[aria-pressed="true"] .icon-to-kana { display: none; }
.para-script[aria-pressed="true"] .icon-to-kanji { display: block; }

.para-script-label {
    position: absolute;
    bottom: calc(100% + 6px);
    left: 50%;
    transform: translateX(-50%);
    padding: var(--s1) var(--s2);
    border-radius: var(--radius-sm);
    background: var(--ink);
    color: var(--washi);
    font-family: var(--ui);
    font-size: 11px;
    letter-spacing: 0;
    text-transform: none;
    white-space: nowrap;
    opacity: 0;
    pointer-events: none;
    transition: opacity .12s;
    z-index: 20;
}
.para-script:hover .para-script-label,
.para-script:focus-visible .para-script-label { opacity: 1; }

/* A rewritten paragraph is still tappable: the conversion comes back split
   into words, each mapping back to its gloss. */
.para.is-hiragana { border-color: var(--indigo-line); }

.para-body { cursor: pointer; }
.para .word { cursor: pointer; }

.para-gloss {
    display: grid;
    gap: var(--s4);
    margin-top: var(--s4);
    padding-top: var(--s4);
    border-top: 1px solid var(--hairline);
}
.para-transcription { font-family: var(--mono); font-size: 13.5px; color: var(--muted); }
.para-translation { font-family: var(--read); font-size: 16.5px; line-height: 1.6; }

/* ---- ratings ---- */

.rating { display: inline-flex; align-items: center; gap: 2px; }
.rating-star {
    border: 0;
    background: none;
    color: var(--surface-3);
    font-size: 18px;
    line-height: 1;
    padding: 2px;
    cursor: pointer;
    transition: color .12s;
}
.rating-star[aria-pressed="true"] { color: var(--ochre); }
.rating:hover .rating-star { color: var(--ochre); }
.rating-star:hover ~ .rating-star { color: var(--surface-3); }
.rating-summary {
    margin-left: var(--s2);
    font-family: var(--mono);
    font-size: 11px;
    color: var(--muted);
    font-variant-numeric: tabular-nums;
}

/* ---- the upload form ---- */

.lib-form { max-width: 720px; }
.lib-body { height: auto; min-height: 320px; padding: var(--s3); line-height: 1.6; resize: vertical; }
.lib-share { width: fit-content; }
.field-hint.is-over { color: var(--clay); font-weight: 600; }

.chip-icon { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }

/* ============================================================= ACCOUNT == */

.account { display: grid; grid-template-columns: 232px minmax(0, 1fr); flex: 1; min-height: 0; }

.tabs {
    display: flex;
    flex-direction: column;
    gap: var(--s1);
    padding: var(--s4);
    border-right: 1px solid var(--hairline);
    background: var(--surface-2);
}
.tab {
    display: grid;
    gap: 2px;
    padding: var(--s3);
    border: 1px solid transparent;
    border-radius: var(--radius);
    color: var(--ink);
    text-decoration: none;
}
.tab:hover { background: var(--surface-3); }
.tab[aria-current="page"] { background: var(--indigo-soft); border-color: var(--indigo-line); }
.tab-name { font-size: 14px; font-weight: 600; }
.tab[aria-current="page"] .tab-name { color: var(--indigo); }
.tab-meta { font-size: 12px; color: var(--muted); }

.panel { padding: var(--s6) var(--s6) var(--s10); min-width: 0; display: grid; gap: var(--s5); align-content: start; }
.panel-head { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; }
.panel-title { font-size: 17px; }

.stats { display: flex; flex-wrap: wrap; gap: var(--s2); }
.stat {
    padding: var(--s3) var(--s4);
    border: 1px solid var(--hairline);
    border-radius: var(--radius);
    background: var(--surface);
    min-width: 128px;
}
.stat-value { font-size: 22px; font-weight: 600; font-variant-numeric: tabular-nums; }
.stat-value small { font-size: 13px; font-weight: 400; color: var(--muted); }

.form-grid { display: grid; gap: var(--s4); max-width: 480px; }
.check-row { display: flex; flex-wrap: wrap; gap: var(--s2); }

.saved-list { display: grid; gap: var(--s2); }
.saved-item {
    display: flex;
    align-items: flex-start;
    gap: var(--s3);
    padding: var(--s4);
    border: 1px solid var(--hairline);
    border-radius: var(--radius);
    background: var(--surface);
    color: inherit;
}
.saved-item:hover { border-color: var(--indigo-line); background: var(--surface-2); }

/* The card opens the text; Share is a separate destination, so the two are
   separate links rather than one nested inside the other. */
.saved-open {
    display: flex;
    flex: 1;
    min-width: 0;
    text-decoration: none;
    color: inherit;
}
.saved-share { flex: none; align-self: center; }
.saved-body { min-width: 0; flex: 1; display: grid; gap: var(--s1); }
.saved-text { font-family: var(--cjk); font-size: 17px; line-height: 1.7; }
.saved-text[data-latin="true"] { font-family: var(--read); }
.saved-translation { font-family: var(--read); font-size: 14px; color: var(--muted); }
.saved-meta {
    font-family: var(--mono);
    font-size: 10px;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--faint);
    display: flex;
    flex-wrap: wrap;
    gap: var(--s3);
}

/* ================================================================ AUTH == */

.auth-page { display: grid; place-items: center; min-height: 100vh; min-height: 100dvh; padding: var(--s6); }
.auth-card { width: min(420px, 100%); padding: var(--s8); display: grid; gap: var(--s5); }
.auth-brand { font-family: var(--mono); font-size: 11px; letter-spacing: .14em; text-transform: uppercase; color: var(--muted); }
.auth-title { font-size: 26px; letter-spacing: -.01em; }
.auth-sub { color: var(--muted); font-size: 14px; margin-top: var(--s1); }
.auth-switch { font-size: 13.5px; color: var(--muted); text-align: center; }

/* ========================================================== RESPONSIVE == */

@media (max-width: 1020px) {
    .reader { grid-template-columns: minmax(0, 1fr); }
    .gloss {
        border-left: 0;
        border-top: 1px solid var(--hairline);
        position: sticky;
        /* Undo the full-height sidebar above: down here it is a sheet held to
           the bottom of the screen, and a column that is one viewport tall
           held to the top is the opposite of that. */
        top: auto;
        /* And sat at the bottom of its row rather than stretched across it. A
           stretched item that cannot fill its row — which is any item with a
           max-height, so this one always — is laid out at the row's start, and
           the shorter the sheet the further it floated above the tab bar.
           Anchoring the end instead keeps it against the bottom edge at either
           of its two heights, while still taking up room in the flow so the
           passage never runs underneath it. */
        align-self: end;
        height: auto;
        bottom: 0;
        max-height: 46vh;
        overflow-y: auto;
        padding-bottom: calc(var(--s5) + env(safe-area-inset-bottom));
        box-shadow: 0 -8px 24px -18px rgba(0, 0, 0, .6);
        /* Between its two heights. Both ends are real lengths, so this
           animates; the sheet dropping out of the way rather than vanishing is
           what says it is still there to bring back. */
        transition: max-height .22s ease;
    }
    /* Once the gloss is a bottom sheet its height is capped, and the legend and
       the kept-count are reference material rather than something you act on.
       The word itself gets that space instead. */
    .gloss-foot { display: none; }

    /* The grip carries the heading down here, so the heading itself would be
       one of two. */
    .gloss > .label { display: none; }

    .gloss-grip {
        display: flex;
        align-items: center;
        gap: var(--s3);
        /* Out to the sheet's edges: a handle you have to aim for is not much
           of a handle. The negative margins undo the sheet's padding, and
           sticking to the top keeps it in reach after the gloss has been
           scrolled. */
        margin: calc(var(--s5) * -1) calc(var(--s5) * -1) 0;
        padding: var(--s3) var(--s5);
        position: sticky;
        top: 0;
        z-index: 1;
        border: 0;
        background: var(--surface-2);
        color: var(--ink);
        font-family: var(--cjk);
        font-size: 13px;
        text-align: left;
        cursor: pointer;
        /* No double-tap zoom to wait on before the tap counts. */
        touch-action: manipulation;
    }
    .gloss-grip-bar {
        flex: none;
        width: 32px;
        height: 4px;
        border-radius: var(--radius-pill);
        background: var(--hairline);
    }
    .gloss-grip-label {
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
    }
    .gloss-grip-label[data-latin="true"] { font-family: var(--read); }

    /* Down: capped at the grip, which is measured as it goes so the figure is
       right whatever the type does. Scrolling goes with it — there is nothing
       to scroll to, and a sheet that moved under the finger while it was shut
       would just be a smaller version of the problem. */
    .gloss.is-peeked {
        max-height: var(--gloss-peek, 44px);
        overflow: hidden;
    }
    .passage { padding: var(--s6) var(--s5) var(--s8); }
}

@media (max-width: 860px) {
    .account { grid-template-columns: minmax(0, 1fr); }
    .tabs {
        flex-direction: row;
        overflow-x: auto;
        border-right: 0;
        border-bottom: 1px solid var(--hairline);
        padding: var(--s3) var(--s4);
    }
    .tab { flex: none; }
    .tab-meta { display: none; }
    .panel { padding: var(--s5) var(--s4) var(--s10); }
}

/* Two five-cell blocks need about 800px between them and the rail. Below that
   the voiced row drops beneath the row it comes from — still next to its
   origin, just downwards rather than across. */
@media (max-width: 860px) {
    .kana-chart-head, .kana-row { grid-template-columns: minmax(0, 1fr); gap: var(--s1); }
    .kana-chart { gap: var(--s4); }
    .kana-chart-head { gap: var(--s4); margin-bottom: 0; align-items: start; }
    .kana-voiced:not(:empty) { margin-bottom: var(--s3); }
}

@media (max-width: 720px) {
    /* One value for the tab bar's height: the bar is set to it, and everything
       that must clear the bar offsets by it. Guessing separately is how the
       gloss buttons ended up underneath it. */
    :root { --rail-w: 0px; --tabbar-h: calc(64px + env(safe-area-inset-bottom)); }

    .app { grid-template-columns: minmax(0, 1fr); }

    .rail {
        position: fixed;
        left: 0; right: 0; bottom: 0;
        top: auto;
        height: var(--tabbar-h);
        flex-direction: row;
        justify-content: space-around;
        gap: 0;
        border-right: 0;
        border-top: 1px solid var(--hairline);
        padding: var(--s1) var(--s2) calc(var(--s1) + env(safe-area-inset-bottom));
        z-index: 80;
    }
    /* Seven items for somebody reading Japanese, six for everyone else, so
       each takes an equal share of whatever there is rather than a fixed
       width. At the narrow end that is about 50px a piece, which the labels
       are sized to sit inside without truncating. */
    .rail-item {
        width: auto;
        flex: 1;
        max-width: 84px;
        min-height: var(--tap);
        padding-left: 0;
        padding-right: 0;
        gap: 2px;
    }
    .rail-label {
        font-size: 9.5px;
        max-width: 100%;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
    .rail-spacer { display: none; }

    /* The grid keeps its five columns and gives up cell width instead: a chart
       that wraps mid-row stops being a chart. */
    .kana-page { padding: var(--s5) var(--s4) var(--s8); gap: var(--s6); }
    .kana-grid { gap: var(--s1); grid-template-columns: repeat(var(--kana-cols, 5), minmax(0, 1fr)); }
    .kana-cell { min-height: 62px; padding: var(--s1); }
    .kana-char { font-size: 22px; }
    .kana-romaji { font-size: 10px; }
    .kana-mark { max-width: none; }

    .main { padding-bottom: var(--tabbar-h); }

    /* The sheet sits on the tab bar, not behind it. */
    .gloss {
        bottom: var(--tabbar-h);
        padding-bottom: var(--s5);
        max-height: calc(52vh - var(--tabbar-h));
    }

    .line { font-size: 23px; }
    .drill-prompt { font-size: 34px; }
    .options { grid-template-columns: 1fr; }
    .passage { padding: var(--s5) var(--s4) var(--s6); }
    .page-head { padding: var(--s3) var(--s4); }
    .lib-grid { grid-template-columns: minmax(0, 1fr); padding: var(--s4); gap: var(--s3); }
    .tag-bar { padding: var(--s3) var(--s4); }
    .para { padding: var(--s3); }
    .dict-tools { padding: var(--s3) var(--s4); }

    /* Eight columns will not fit a phone. The table becomes one block per word:
       the word and its reading lead, the meaning follows, and the details sit
       on a single line underneath. Nothing is dropped — it is re-stacked, and
       the header row is redundant once each value is on its own line. */
    .dict-table thead { display: none; }
    .dict-table,
    .dict-table tbody,
    .dict-table tr,
    .dict-table td { display: block; }

    .dict-table tr {
        position: relative;
        padding: var(--s4);
        border-bottom: 1px solid var(--hairline);
    }
    /* Only the first two lines sit beside the buttons; the detail line below
       them gets the full width instead of being needlessly indented. */
    .dict-table .c-word,
    .dict-table .c-reading { padding-right: 84px; }
    .dict-table tbody tr:hover td { background: none; }

    .dict-table td { padding: 0; border: 0; }
    .dict-table .c-word { margin-bottom: 2px; }
    .dict-table .c-word .dict-word { font-size: 22px; }
    .dict-table .c-meaning { margin-top: var(--s1); font-size: 16px; }

    /* Part of speech, strength and next review share one line. */
    .dict-table .c-part,
    .dict-table .c-owner,
    .dict-table .c-strength,
    .dict-table .c-due {
        display: inline-block;
        vertical-align: middle;
        margin: var(--s2) var(--s3) 0 0;
    }
    .dict-table .c-part .dict-verb { display: inline; }
    .dict-table .c-part .dict-verb::before { content: " · "; }

    .dict-table .c-actions {
        position: absolute;
        top: var(--s4);
        right: var(--s4);
        display: flex;
        gap: var(--s2);
    }

    .table-wrap { overflow-x: visible; }
    .toasts { bottom: calc(var(--tabbar-h) + var(--s3)); }
}
