/* ============================================================
   yomihon — reset (Tailwind CSS Preflight 4.1.17)
   ------------------------------------------------------------
   Source: packages/tailwindcss/preflight.css at tag v4.1.17 of
   https://github.com/tailwindlabs/tailwindcss, the file the
   pinned Tailwind CLI embeds (the two are byte-identical).

   Changes, and the only ones:
   - Tailwind's --theme(--name, fallback) function, which a browser
     does not know, is written as the var(--name, fallback) it
     compiles to.
   - The rules sit inside one @layer base block, as Tailwind
     emitted them, so every unlayered product rule outranks the
     reset whatever its specificity.
   Nothing else is edited, comments included, so the file can be
   compared with upstream line for line. The stylesheet lint does
   not read it, for the same reason.

   The reset takes the page and code font stacks from
   --default-font-family and --default-mono-font-family, which
   tokens.css defines from the product's own font tokens.

   MIT License

   Copyright (c) Tailwind Labs, Inc.

   Permission is hereby granted, free of charge, to any person obtaining a copy
   of this software and associated documentation files (the "Software"), to deal
   in the Software without restriction, including without limitation the rights
   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   copies of the Software, and to permit persons to whom the Software is
   furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in all
   copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
   SOFTWARE.
   ============================================================ */
@layer base {
  /*
    1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
    2. Remove default margins and padding
    3. Reset all borders.
  */

  *,
  ::after,
  ::before,
  ::backdrop,
  ::file-selector-button {
    box-sizing: border-box; /* 1 */
    margin: 0; /* 2 */
    padding: 0; /* 2 */
    border: 0 solid; /* 3 */
  }

  /*
    1. Use a consistent sensible line-height in all browsers.
    2. Prevent adjustments of font size after orientation changes in iOS.
    3. Use a more readable tab size.
    4. Use the user's configured `sans` font-family by default.
    5. Use the user's configured `sans` font-feature-settings by default.
    6. Use the user's configured `sans` font-variation-settings by default.
    7. Disable tap highlights on iOS.
  */

  html,
  :host {
    line-height: 1.5; /* 1 */
    -webkit-text-size-adjust: 100%; /* 2 */
    tab-size: 4; /* 3 */
    font-family: var(
      --default-font-family,
      ui-sans-serif,
      system-ui,
      sans-serif,
      'Apple Color Emoji',
      'Segoe UI Emoji',
      'Segoe UI Symbol',
      'Noto Color Emoji'
    ); /* 4 */
    font-feature-settings: var(--default-font-feature-settings, normal); /* 5 */
    font-variation-settings: var(--default-font-variation-settings, normal); /* 6 */
    -webkit-tap-highlight-color: transparent; /* 7 */
  }

  /*
    1. Add the correct height in Firefox.
    2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
    3. Reset the default border style to a 1px solid border.
  */

  hr {
    height: 0; /* 1 */
    color: inherit; /* 2 */
    border-top-width: 1px; /* 3 */
  }

  /*
    Add the correct text decoration in Chrome, Edge, and Safari.
  */

  abbr:where([title]) {
    -webkit-text-decoration: underline dotted;
    text-decoration: underline dotted;
  }

  /*
    Remove the default font size and weight for headings.
  */

  h1,
  h2,
  h3,
  h4,
  h5,
  h6 {
    font-size: inherit;
    font-weight: inherit;
  }

  /*
    Reset links to optimize for opt-in styling instead of opt-out.
  */

  a {
    color: inherit;
    -webkit-text-decoration: inherit;
    text-decoration: inherit;
  }

  /*
    Add the correct font weight in Edge and Safari.
  */

  b,
  strong {
    font-weight: bolder;
  }

  /*
    1. Use the user's configured `mono` font-family by default.
    2. Use the user's configured `mono` font-feature-settings by default.
    3. Use the user's configured `mono` font-variation-settings by default.
    4. Correct the odd `em` font sizing in all browsers.
  */

  code,
  kbd,
  samp,
  pre {
    font-family: var(
      --default-mono-font-family,
      ui-monospace,
      SFMono-Regular,
      Menlo,
      Monaco,
      Consolas,
      'Liberation Mono',
      'Courier New',
      monospace
    ); /* 1 */
    font-feature-settings: var(--default-mono-font-feature-settings, normal); /* 2 */
    font-variation-settings: var(--default-mono-font-variation-settings, normal); /* 3 */
    font-size: 1em; /* 4 */
  }

  /*
    Add the correct font size in all browsers.
  */

  small {
    font-size: 80%;
  }

  /*
    Prevent `sub` and `sup` elements from affecting the line height in all browsers.
  */

  sub,
  sup {
    font-size: 75%;
    line-height: 0;
    position: relative;
    vertical-align: baseline;
  }

  sub {
    bottom: -0.25em;
  }

  sup {
    top: -0.5em;
  }

  /*
    1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
    2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
    3. Remove gaps between table borders by default.
  */

  table {
    text-indent: 0; /* 1 */
    border-color: inherit; /* 2 */
    border-collapse: collapse; /* 3 */
  }

  /*
    Use the modern Firefox focus style for all focusable elements.
  */

  :-moz-focusring {
    outline: auto;
  }

  /*
    Add the correct vertical alignment in Chrome and Firefox.
  */

  progress {
    vertical-align: baseline;
  }

  /*
    Add the correct display in Chrome and Safari.
  */

  summary {
    display: list-item;
  }

  /*
    Make lists unstyled by default.
  */

  ol,
  ul,
  menu {
    list-style: none;
  }

  /*
    1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
    2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
        This can trigger a poorly considered lint error in some tools but is included by design.
  */

  img,
  svg,
  video,
  canvas,
  audio,
  iframe,
  embed,
  object {
    display: block; /* 1 */
    vertical-align: middle; /* 2 */
  }

  /*
    Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
  */

  img,
  video {
    max-width: 100%;
    height: auto;
  }

  /*
    1. Inherit font styles in all browsers.
    2. Remove border radius in all browsers.
    3. Remove background color in all browsers.
    4. Ensure consistent opacity for disabled states in all browsers.
  */

  button,
  input,
  select,
  optgroup,
  textarea,
  ::file-selector-button {
    font: inherit; /* 1 */
    font-feature-settings: inherit; /* 1 */
    font-variation-settings: inherit; /* 1 */
    letter-spacing: inherit; /* 1 */
    color: inherit; /* 1 */
    border-radius: 0; /* 2 */
    background-color: transparent; /* 3 */
    opacity: 1; /* 4 */
  }

  /*
    Restore default font weight.
  */

  :where(select:is([multiple], [size])) optgroup {
    font-weight: bolder;
  }

  /*
    Restore indentation.
  */

  :where(select:is([multiple], [size])) optgroup option {
    padding-inline-start: 20px;
  }

  /*
    Restore space after button.
  */

  ::file-selector-button {
    margin-inline-end: 4px;
  }

  /*
    Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
  */

  ::placeholder {
    opacity: 1;
  }

  /*
    Set the default placeholder color to a semi-transparent version of the current text color in browsers that do not
    crash when using `color-mix(…)` with `currentcolor`. (https://github.com/tailwindlabs/tailwindcss/issues/17194)
  */

  @supports (not (-webkit-appearance: -apple-pay-button)) /* Not Safari */ or
    (contain-intrinsic-size: 1px) /* Safari 17+ */ {
    ::placeholder {
      color: color-mix(in oklab, currentcolor 50%, transparent);
    }
  }

  /*
    Prevent resizing textareas horizontally by default.
  */

  textarea {
    resize: vertical;
  }

  /*
    Remove the inner padding in Chrome and Safari on macOS.
  */

  ::-webkit-search-decoration {
    -webkit-appearance: none;
  }

  /*
    1. Ensure date/time inputs have the same height when empty in iOS Safari.
    2. Ensure text alignment can be changed on date/time inputs in iOS Safari.
  */

  ::-webkit-date-and-time-value {
    min-height: 1lh; /* 1 */
    text-align: inherit; /* 2 */
  }

  /*
    Prevent height from changing on date/time inputs in macOS Safari when the input is set to `display: block`.
  */

  ::-webkit-datetime-edit {
    display: inline-flex;
  }

  /*
    Remove excess padding from pseudo-elements in date/time inputs to ensure consistent height across browsers.
  */

  ::-webkit-datetime-edit-fields-wrapper {
    padding: 0;
  }

  ::-webkit-datetime-edit,
  ::-webkit-datetime-edit-year-field,
  ::-webkit-datetime-edit-month-field,
  ::-webkit-datetime-edit-day-field,
  ::-webkit-datetime-edit-hour-field,
  ::-webkit-datetime-edit-minute-field,
  ::-webkit-datetime-edit-second-field,
  ::-webkit-datetime-edit-millisecond-field,
  ::-webkit-datetime-edit-meridiem-field {
    padding-block: 0;
  }

  /*
    Center dropdown marker shown on inputs with paired `<datalist>`s in Chrome. (https://github.com/tailwindlabs/tailwindcss/issues/18499)
  */

  ::-webkit-calendar-picker-indicator {
    line-height: 1;
  }

  /*
    Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
  */

  :-moz-ui-invalid {
    box-shadow: none;
  }

  /*
    Correct the inability to style the border radius in iOS Safari.
  */

  button,
  input:where([type='button'], [type='reset'], [type='submit']),
  ::file-selector-button {
    appearance: button;
  }

  /*
    Correct the cursor style of increment and decrement buttons in Safari.
  */

  ::-webkit-inner-spin-button,
  ::-webkit-outer-spin-button {
    height: auto;
  }

  /*
    Make elements with the HTML hidden attribute stay hidden by default.
  */

  [hidden]:where(:not([hidden='until-found'])) {
    display: none !important;
  }
}

/* ============================================================
   yomihon — self-hosted fonts (zero external requests)
   ------------------------------------------------------------
   Vendored woff2 live under assets/fonts/ and are served at
   /static/fonts/*.woff2 (internal/asset). The Google Fonts @import
   the HTML mocks used is deleted — nothing here reaches the network
   (acceptance: the devtools network panel shows no external
   request).

   In hand now (OFL / licensed): Geist + Geist Mono (Latin UI/mono),
   Newsreader (Latin editorial serif, roman + italic). The CJK families
   the token stack names next — Noto Serif/Sans TC/JP and LXGW WenKai TC
   (楷書) — are large OFL binaries not yet vendored (path b). Until then
   there is deliberately NO CJK @font-face here, so nothing 404s; the
   token font stacks (tokens.css) resolve CJK to a matching SYSTEM face
   of the right class (path a): 明體/Mincho for the reading body and kai
   titles (Songti TC / Hiragino Mincho ProN), 黑體/Gothic for the UI
   (PingFang TC / Hiragino Sans). To finish path b: subset the woff2 to
   the vault's codepoints (fonttools / hb-subset / glyphhanger), drop
   them into assets/fonts/, append an @font-face block here with a
   `unicode-range` CJK split. No selector or
   markup change is needed — the stacks already prefer Noto when present.
   ============================================================ */

/* ---------- Latin UI/display: Geist (variable) ---------- */
@font-face {
  font-display: swap;
  font-family: 'Geist';
  font-style: normal;
  font-weight: 300 700;
  src: url('/static/fonts/Geist-Variable.woff2') format('woff2-variations');
}

/* ---------- Latin mono: Geist Mono (variable) ---------- */
@font-face {
  font-display: swap;
  font-family: 'Geist Mono';
  font-style: normal;
  font-weight: 400 500;
  src: url('/static/fonts/GeistMono-Variable.woff2') format('woff2-variations');
}

/* ---------- Latin editorial serif: Newsreader (variable) ----------
   Leads --font-serif so Latin runs inside serif prose/dek are truly
   serif; CJK falls through to Noto Serif / system serif.

   Each style is two faces, split by `unicode-range`: `latin` holds the
   basic Latin letters, digits and punctuation every English run is made
   of, and `latin-ext` holds the Latin Extended letters and is only fetched
   by a page that uses one. The ranges are fontsource's, with one addition:
   U+0300-0301, U+0303, U+0309 and U+0323 (grave, acute, tilde, hook above,
   dot below) are also claimed by `latin-ext`, because fontsource sends them
   to its `vietnamese` subset, which is not vendored, and a Latin Extended
   letter carrying one would otherwise be drawn by a fallback serif.
   `latin-ext` is declared first, so a code point both ranges name is
   tried in `latin` first. A file without the letters its range claims once
   left the reading body drawn by a fallback serif, with Newsreader owning
   only the word spaces; .github/e2e/reading-face.mjs reads which face
   draws it. */
@font-face {
  font-display: swap;
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 400 600;
  src: url('/static/fonts/Newsreader-LatinExt-Variable.woff2') format('woff2-variations');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0300-0301, U+0303, U+0304, U+0308, U+0309, U+0323, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-display: swap;
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 400 600;
  src: url('/static/fonts/Newsreader-Latin-Variable.woff2') format('woff2-variations');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-display: swap;
  font-family: 'Newsreader';
  font-style: italic;
  font-weight: 400 600;
  src: url('/static/fonts/Newsreader-LatinExt-Italic-Variable.woff2') format('woff2-variations');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0300-0301, U+0303, U+0304, U+0308, U+0309, U+0323, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-display: swap;
  font-family: 'Newsreader';
  font-style: italic;
  font-weight: 400 600;
  src: url('/static/fonts/Newsreader-Latin-Italic-Variable.woff2') format('woff2-variations');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ============================================================
   yomihon — design tokens
   ------------------------------------------------------------
   Consolidates the design bundle: theme.css's values +
   colors_and_type.css's alias vocabulary, re-skinned to the product
   accent (theme-adaptive vermilion 朱, reserved for the reader's own place;
   the official mark carries its separately fixed approved vermilion) and the product type
   stack (Geist + Noto Serif/Sans CJK + Newsreader + LXGW WenKai).

   Dark mode is a class/attribute strategy: [data-theme="dark"] is
   stamped on <html> server-side from a cookie (no FOUC). Every colour
   token has a light value and a dark twin; components re-resolve on
   toggle with zero markup change. AA contrast notes carried from
   theme.css (the vermilion / warn / sage / gold values are chosen so
   badge text clears 4.5:1 on its tint in both themes).
   ============================================================ */

:root,
[data-theme='light'] {
  /* ---- Surfaces (warm oatmeal paper) ---- */
  --bg: oklch(0.966 0.005 106);
  --panel: oklch(0.988 0.003 106);
  --elevated: oklch(0.945 0.006 107);
  --overlay: oklch(0.915 0.008 107);
  /* The left rail's ground: a lighter sheet beside the page in light, and the
     page's own ground in dark, where a lighter panel read as a lit strip down
     the side of a dark room. */
  --rail-ground: var(--panel);
  /* A switch that is on: a step down from the ink, so a control that is set
     does not outweigh the words beside it. */
  --switch-on: var(--fg-muted);

  /* ---- Text (sumi inks, 3 tiers: the faint tier is the subtle one under
     its older name, so the quietest text is one value that clears 4.5:1 on
     every surface it is set on) ---- */
  --fg: oklch(0.153 0.012 107);
  --fg-muted: oklch(0.400 0.020 107);
  --fg-subtle: oklch(0.500 0.020 107);
  --fg-faint: var(--fg-subtle);

  /* ---- The one interface accent — 朱 seal-ink vermilion, reserved for the
     reader's own place: the bookmark where they left off, the paragraph the
     voice is reading, and where an address arrived. It marks no status, since
     a status is the author's word and not the reader's. The official mark's
     fixed obi draws the same red but does not consume this theme token. ---- */
  --accent: oklch(0.470 0.175 33);
  --accent-strong: oklch(0.400 0.165 33);
  --accent-muted: oklch(0.550 0.180 33 / 0.12);
  --accent-faint: oklch(0.550 0.180 33 / 0.06);
  --on-accent: oklch(0.985 0.003 106);
  --link: oklch(0.470 0.150 33);

  /* ---- Honest diagnostics — muted ochre, never angry red (金繼ぎ) ---- */
  --warn: oklch(0.470 0.125 70);
  --warn-bg: oklch(0.560 0.120 70 / 0.10);
  --gold: oklch(0.660 0.098 82);   /* kintsugi seam thread */
  --gold-ink: oklch(0.461 0.100 82);   /* corrections (訂正) carry this, never 朱 */
  /* A finding that blocks the folder: a muted plum, its own hue, so it is told
     from a warning at a glance without borrowing the seal ink. */
  --error: oklch(0.450 0.110 325);
  --error-bg: oklch(0.550 0.120 325 / 0.10);

  /* ---- Growth track — muted sage (never out-shouts the accent) ---- */
  --sage: oklch(0.450 0.060 145);
  --sage-bg: oklch(0.560 0.055 145 / 0.11);

  --success: oklch(0.520 0.130 155);
  --success-bg: oklch(0.520 0.130 155 / 0.10);

  /* ---- Slot-machine colours — the 8 grammatical roles. Each coloured
     element reads --c; the .y-slot-<token> class only assigns it, so one token
     tints a slot's select border, its surface, and its ruby together. Muted so
     a card full of coloured words never out-shouts the reading, and hues avoid
     the accent's red-orange (reserved for the reader's own place and echoed
     only by the official mark's fixed obi). ---- */
  --slot-topic: oklch(0.500 0.125 250);
  --slot-pred: oklch(0.560 0.120 65);
  --slot-poss: oklch(0.615 0.070 250);
  --slot-obj: oklch(0.530 0.100 185);
  --slot-place: oklch(0.510 0.115 305);
  --slot-time: oklch(0.560 0.095 45);
  --slot-verb: oklch(0.505 0.095 150);
  --slot-num: oklch(0.520 0.120 350);

  --shadow-1: 0 2px 8px -2px oklch(0.30 0.02 107 / 0.10), 0 1px 2px oklch(0.30 0.02 107 / 0.07);
  --shadow-2: 0 18px 40px -12px oklch(0.28 0.02 107 / 0.18), 0 4px 12px oklch(0.28 0.02 107 / 0.10);

  color-scheme: light;
}

/* ---- Dark twin — warm charcoal ink-stone ---- */
[data-theme='dark'] {
  --bg: oklch(0.153 0.006 107);
  --panel: oklch(0.210 0.011 107);
  --elevated: oklch(0.262 0.013 107);
  --overlay: oklch(0.305 0.014 107);
  --rail-ground: var(--bg);
  --switch-on: var(--fg-subtle);

  --fg: oklch(0.962 0.004 106);
  --fg-muted: oklch(0.760 0.010 106);
  --fg-subtle: oklch(0.720 0.014 107);
  --fg-faint: var(--fg-subtle);

  --accent: oklch(0.820 0.150 34);
  --accent-strong: oklch(0.880 0.130 34);
  --accent-muted: oklch(0.660 0.170 34 / 0.16);
  --accent-faint: oklch(0.660 0.170 34 / 0.07);
  --on-accent: oklch(0.150 0.010 60);
  --link: oklch(0.740 0.150 34);

  --warn: oklch(0.860 0.120 82);
  --warn-bg: oklch(0.800 0.130 80 / 0.14);
  --gold: oklch(0.735 0.098 84);
  --gold-ink: oklch(0.810 0.098 84);
  --error: oklch(0.800 0.090 325);
  --error-bg: oklch(0.700 0.110 325 / 0.15);

  --sage: oklch(0.820 0.065 148);
  --sage-bg: oklch(0.680 0.060 148 / 0.13);

  --success: oklch(0.740 0.120 155);
  --success-bg: oklch(0.740 0.120 155 / 0.14);

  /* Slot roles, brightened for the dark ink-stone (same hues as light). */
  --slot-topic: oklch(0.780 0.120 250);
  --slot-pred: oklch(0.800 0.115 65);
  --slot-poss: oklch(0.820 0.065 250);
  --slot-obj: oklch(0.790 0.095 185);
  --slot-place: oklch(0.790 0.110 305);
  --slot-time: oklch(0.810 0.090 45);
  --slot-verb: oklch(0.795 0.090 150);
  --slot-num: oklch(0.790 0.115 350);

  --shadow-1: 0 2px 8px -2px rgb(0 0 0 / 0.50), 0 1px 2px rgb(0 0 0 / 0.40);
  --shadow-2: 0 18px 40px -12px rgb(0 0 0 / 0.65), 0 4px 12px rgb(0 0 0 / 0.45);

  color-scheme: dark;
}

/* The same ink-stone, entered by the system's preference instead of the
   reader's: a root carrying no stored choice follows a dark desk. A stored
   choice keeps beating the system either way — an explicit light escapes
   this block through the :not() guard, and an explicit dark repeats these
   values. This body must stay declaration-for-declaration identical to
   [data-theme="dark"] above; a Go test compares the two and names any token
   that drifts. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    --bg: oklch(0.153 0.006 107);
    --panel: oklch(0.210 0.011 107);
    --elevated: oklch(0.262 0.013 107);
    --overlay: oklch(0.305 0.014 107);
    --rail-ground: var(--bg);
    --switch-on: var(--fg-subtle);

    --fg: oklch(0.962 0.004 106);
    --fg-muted: oklch(0.760 0.010 106);
    --fg-subtle: oklch(0.720 0.014 107);
    --fg-faint: var(--fg-subtle);

    --accent: oklch(0.820 0.150 34);
    --accent-strong: oklch(0.880 0.130 34);
    --accent-muted: oklch(0.660 0.170 34 / 0.16);
    --accent-faint: oklch(0.660 0.170 34 / 0.07);
    --on-accent: oklch(0.150 0.010 60);
    --link: oklch(0.740 0.150 34);

    --warn: oklch(0.860 0.120 82);
    --warn-bg: oklch(0.800 0.130 80 / 0.14);
    --gold: oklch(0.735 0.098 84);
    --gold-ink: oklch(0.810 0.098 84);
    --error: oklch(0.800 0.090 325);
    --error-bg: oklch(0.700 0.110 325 / 0.15);

    --sage: oklch(0.820 0.065 148);
    --sage-bg: oklch(0.680 0.060 148 / 0.13);

    --success: oklch(0.740 0.120 155);
    --success-bg: oklch(0.740 0.120 155 / 0.14);

    /* Slot roles, brightened for the dark ink-stone (same hues as light). */
    --slot-topic: oklch(0.780 0.120 250);
    --slot-pred: oklch(0.800 0.115 65);
    --slot-poss: oklch(0.820 0.065 250);
    --slot-obj: oklch(0.790 0.095 185);
    --slot-place: oklch(0.790 0.110 305);
    --slot-time: oklch(0.810 0.090 45);
    --slot-verb: oklch(0.795 0.090 150);
    --slot-num: oklch(0.790 0.115 350);

    --shadow-1: 0 2px 8px -2px rgb(0 0 0 / 0.50), 0 1px 2px rgb(0 0 0 / 0.40);
    --shadow-2: 0 18px 40px -12px rgb(0 0 0 / 0.65), 0 4px 12px rgb(0 0 0 / 0.45);

    color-scheme: dark;
  }
}


/* ---- Non-colour tokens (theme-independent) ---- */
:root {
  /* Type families — Latin (Geist/Newsreader) leads; CJK resolves to Noto when
     the woff2 are vendored (path b), else to a SYSTEM CJK face of the right
     class (path a, 2026-07-03): the reading body must stay 明體/Mincho, not
     collapse to PingFang 黑體 — serif-becoming-sans would break visual parity
     with review/*.png. So the serif/kai stacks name macOS Mincho/Kai
     explicitly before the generic serif, and the sans/display stacks name a
     system Gothic. Zero bytes, no @font-face, no request. */
  --font-display: 'Geist', 'Noto Sans TC', 'PingFang TC', 'Hiragino Sans', system-ui, sans-serif;
  --font-serif: 'Newsreader', 'Noto Serif TC', 'Noto Serif JP', 'Songti TC', 'Hiragino Mincho ProN', Georgia, serif;
  --font-sans: 'Geist', 'Noto Sans TC', 'PingFang TC', 'Hiragino Sans', system-ui, sans-serif;
  --font-mono: 'Geist Mono', ui-monospace, 'Noto Sans TC', monospace;
  --font-kai: 'LXGW WenKai TC', 'Kaiti TC', 'Kaiti SC', 'Noto Serif TC', 'Songti TC', 'Hiragino Mincho ProN', serif;

  /* The reset reads these two names for the page's font and for code's, rather
     than the product's tokens, so they are pointed at the tokens here. */
  --default-font-family: var(--font-sans);
  --default-mono-font-family: var(--font-mono);

  /* The face a reader's own choice sets their reading in. Having chosen
     nothing leaves the root without data-font and this base value answers: the
     明體 the charter reads in. A choice replaces this one value and none of
     the stacks above it, so choosing a face never rewrites a face. */
  --font-read: var(--font-serif);

  /* UI scale — the chrome sets in 12, 13 and 14; the larger steps are for a
     note's title on the desk, the search field and the wordmark. */
  --fs-12: 0.75rem;
  --fs-13: 0.8125rem;
  --fs-14: 0.875rem;
  --fs-15: 0.9375rem;
  --fs-16: 1rem;
  --fs-18: 1.125rem;

  /* Editorial scale */
  --fs-ed-17: 1.0625rem;
  --fs-ed-20: 1.25rem;
  --fs-ed-24: 1.5rem;
  --fs-ed-30: 1.875rem;
  --fs-ed-36: 2.25rem;

  /* Weights */
  --fw-regular: 400;
  --fw-medium: 500;
  --fw-semibold: 600;
  --fw-bold: 700;

  /* Line heights */
  --lh-tight: 1.15;
  --lh-snug: 1.35;
  --lh-normal: 1.5;
  --lh-read: 1.8;   /* CJK prose floor (≥1.8) */
  --lh-cjk: 2;      /* ruby-safe reading measure */
  --lh-latin: 1.7647; /* 17px body on a 30px line, where the article is positively Latin */

  /* Vertical rhythm in the reading column. A paragraph sits about one body
     line from the one before it. A heading is closer to what it introduces
     than to what precedes it, and says which heading it is by how far it stands
     from the text above: two and a half lines for a section, a line and a half
     for a subsection, a line for the third step. The space under each is a
     fraction of a line, so the heading reads as belonging to its own text. */
  --sp-block: 24px;
  --sp-h4: 32px;
  --sp-h3: 48px;
  --sp-h2: 72px;
  --sp-after-h4: 8px;
  --sp-after-h3: 12px;
  --sp-after-h2: 16px;

  /* Tracking is never positive. A heading in Latin letters may close up a
     little; Han and kana are never tracked, either way. */
  --track-tight: -0.025em;
  --track-snug: -0.015em;
  --track-normal: 0;

  /* Lines and washes. Every one is the reading ink at a fixed share of
     itself, so a hairline over the paper and a hairline over a panel are the
     same ink at the same weight, and all of them turn over with the theme
     because the ink does. */
  --line: color-mix(in oklch, var(--fg) 10%, transparent);
  --line-faint: color-mix(in oklch, var(--fg) 5%, transparent);
  --line-strong: color-mix(in oklch, var(--fg) 20%, transparent);
  --wash: color-mix(in oklch, var(--fg) 5%, transparent);
  --border: var(--line);
  --border-strong: var(--line-strong);
  --border-faint: var(--line-faint);

  /* Radii — a control is soft, a surface is softer */
  --r-sm: 6px;
  --r-md: 8px;
  --r-lg: 12px;
  --r-xl: 16px;

  /* Overlay mask — the wash behind a dialog, sheet or drawer. One value for
     every kind of overlay rather than one per component: a reader dismissing
     a palette, a concept sheet or the narrow nav drawer should feel the same
     weight of dimming each time. Listed here rather than beside the colour
     tokens above because it does not have a dark twin to keep in step: a
     fixed-alpha black wash reads correctly over either theme's surfaces, the
     same way the literals it replaces already did. */
  --overlay-mask: oklch(0 0 0 / 0.45);

  /* Page shells. A page is one of a few shapes, and each is a line length
     together with the space either side of it. The two are named as a pair
     because the box a shell takes is derived from both: hold the box fixed
     instead and a narrower window spends its reclaimed gutter on longer lines,
     so the measure grows by a tenth exactly when the reader has least room.
     The reading shell is a note, a file, a study path, the reading choices,
     and the two pages a reader only ever reaches by accident. The shelf shell
     is every page that lists what the folder holds instead of setting one
     thing to be read, where a row is read across as well as down. The answer
     shell is what a search comes back as, which takes a narrow column of its
     own beside the results wherever there is room for one.

     The reading measure is counted in characters, not pixels: thirty-eight
     body-size ems is thirty-eight Han characters a line, which is where a line
     of Chinese or Japanese stays easy to carry from its end to the start of
     the next, and it grows and shrinks with the reader's chosen text size so a
     larger setting costs words a line rather than the line's length. */
  --measure-read: calc(var(--fs-ed-17) * 38);
  --gutter-read: 48px;
  --measure-list: 880px;
  --gutter-list: 48px;
  --measure-answer: 720px;
  --gutter-answer: 24px;

  /* The widest a page with no rail grows to. Past it the page stands in the
     middle of the window. A page with rails has no such width: its rails sit
     against the window's edges and the room between them is the reading
     column's. */
  --shell-max: 1280px;

  /* A notice, which is one heading and a sentence or two rather than an
     article. It is narrower than the reading measure on purpose: a page whose
     whole answer is two lines reads as a column, not as a band of text across
     the window. It is a column inside a shell and not a shell, which is what
     keeps its name apart from the measures above. */
  --notice-column: 480px;

  /* Motion — motion here exists so a state change can be read. --dur-base is
     what the pointer and the keyboard touch: a hover, a focus ring, a chevron
     turning, a fold opening. --dur-slow is for a surface the reader summoned
     and will send away again — the palette, the concept sheet, the navigation
     drawer — where the longer arrival says where the surface came from and
     where it will go back to. Both run over --ease-standard. The hover preview
     card looks like the second group and belongs to the first: it is born of a
     pointer resting on a link rather than of a press, so it takes --dur-base,
     and at the slow one it would still be arriving after the pointer had moved
     on. --dur-dwell is not a tempo but a dwell — how long the wash on a
     jumped-to heading stays legible before it leaves — which is a reading time
     rather than an interface one.
     A page arriving is the one motion that is none of these. It answers a
     press in place instead of coming from an edge, so it keeps a duration and
     a curve of its own. --ease-arrival starts moving at once but not at a
     jump, and settles slowly, so the arrival reads as the page coming forward
     rather than snapping in; --dur-arrival is how long that settling is
     allowed to take. Tuning how a page arrives therefore moves neither the
     drawer, the sheet nor the palette, which share the summoned surfaces'
     pair. */
  --ease-standard: cubic-bezier(0.2, 0, 0, 1);
  --ease-arrival: cubic-bezier(0.33, 1, 0.68, 1);
  --dur-base: 120ms;
  --dur-slow: 200ms;
  --dur-arrival: 320ms;
  --dur-dwell: 1100ms;
}

/* The reading column's editorial scale at the owner's chosen size. Only the
   ed tokens move: the chrome keeps its own type, because the preference tunes
   the reading, never the furniture around it. */
:root[data-textsize='l'] {
  --fs-ed-17: 1.1875rem;
  --fs-ed-20: 1.375rem;
  --fs-ed-24: 1.625rem;
  --fs-ed-30: 2rem;
  --fs-ed-36: 2.375rem;
}

:root[data-textsize='xl'] {
  --fs-ed-17: 1.3125rem;
  --fs-ed-20: 1.5rem;
  --fs-ed-24: 1.75rem;
  --fs-ed-30: 2.1875rem;
  --fs-ed-36: 2.5rem;
}

/* The two largest steps at a phone's width. A reading column of about 340px
   carries a 36px title as four or five characters a line, and the step below it
   arrives nearly as wide. Both come down; the rest of the scale is already
   small enough that narrowing it would cost legibility rather than buy any.
   Each root gets its own pair rather than one shared behind a combined
   selector list: the second heading (--fs-ed-24) is never clamped, so it keeps
   climbing with the reader's size choice, and a single pair for the title and
   the first heading eventually meets or passes it — tied at 大, above it at
   特大. Every root is named because a media query adds no specificity: a rule
   here keyed on one root alone ties the block above it on specificity and
   wins only by sitting later in the file — which a bare :root cannot do for
   the other two. */
@media (max-width: 720px) {
  :root {
    --fs-ed-30: 1.625rem;
    --fs-ed-36: 1.875rem;
  }

  :root[data-textsize='l'] {
    --fs-ed-30: 1.75rem;
    --fs-ed-36: 2rem;
  }

  :root[data-textsize='xl'] {
    --fs-ed-30: 1.875rem;
    --fs-ed-36: 2.125rem;
  }
}

/* The reading face at the owner's chosen one. All three are written out, the
   one that matches the base included, so the faces a reader is offered and the
   faces this sheet knows can be compared as two lists rather than trusted to
   have stayed in step. Only this one value moves: the stacks the chrome, the
   headings and the code are set from are untouched. */
:root[data-font='serif'] {
  --font-read: var(--font-serif);
}

:root[data-font='sans'] {
  --font-read: var(--font-sans);
}

:root[data-font='kai'] {
  --font-read: var(--font-kai);
}

/* ============================================================
   yomihon — product layer
   ------------------------------------------------------------
   The DS component classes this page actually uses (ui-navitem,
   ui-side__label, ui-status, ui-type, ui-kbd, ui-scrim, ui-dialog),
   the y-* product classes that translate the mock's inline styles
   into named classes (shell / rails / header / article / status /
   diagnostics), ruby zero-reflow, and the reading
   prose that styles the renderer's REAL emitted classes (.callout /
   .wikilink / .chroma / <mark> / <ruby> — never reshaped, only dressed).
   Everything is scoped under .yomihon so nothing leaks to other surfaces.
   ============================================================ */

/* ============================================================
   Base
   ============================================================ */
html,
body {
  background: var(--bg);
  margin: 0;
}
/* Chrome height, and the extra 16px every fragment landing pad adds so
   the marked line sits just under the bar rather than flush against it.
   On :root so the skip link — a sibling of .yomihon — inherits it too.
   The inset is how far the header's first and last controls stand from the
   window's edge. The left rail's text starts, and the right rail's text ends,
   the same distance from the edge, so each lines up with the header's end
   beside it down the page. */
:root {
  --header-height: 56px;
  --header-clearance: calc(var(--header-height) + 16px);
  --header-inset: 20px;
}

/* Code is the one text a reader retypes character by character, so it has to
   draw the characters that are there. Geist Mono joins pairs such as `<-`,
   `->`, `=>` and `!=` into a single glyph through its `liga` feature, which is
   on unless it is turned off, and a learner who copies what they see from Go's
   `<-chan int` types an arrow Go rejects. `font-feature-settings: "calt" 0`
   does not reach those joins; this property does. It is set on the elements
   Preflight puts in the mono face rather than on `.y-prose`, so the code that
   the diagnostics, the notices and the recovery detail quote reads the same
   way the article's does. The same elements stay upright inside an italic
   line: the mono face has no italic cut, so the browser would slant it itself. */
code,
kbd,
pre,
samp {
  font-style: normal;
  font-variant-ligatures: none;
}

.y-skiplink {
  background: var(--panel);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  color: var(--fg);
  font: var(--fw-medium) var(--fs-13)/var(--lh-normal) var(--font-sans);
  left: 8px;
  padding: 8px 12px;
  position: fixed;
  /* Keep the focused link below the header so it cannot cover the
     navigation trigger or another persistent header control. */
  top: 64px;
  translate: 0 calc(-100% - var(--header-clearance));
  z-index: 101;
}

.y-skiplink:focus {
  outline: 2px solid var(--fg-subtle);
  outline-offset: 2px;
  translate: 0;
}

.yomihon {
  background: var(--bg);
  color: var(--fg);
  display: flex;
  flex-direction: column;
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  line-height: var(--lh-normal);
  min-height: 100vh;
}

/* The shell hands every link the surrounding ink, and it does so at no weight
   of its own: a component that names a link's colour with one class has to win
   over this, and a reset as specific as a class and an element would quietly
   win instead and leave the component's colour unpainted. */
:where(.yomihon) a {
  color: inherit;
  text-decoration: none;
}

.yomihon details > summary {
  cursor: pointer;
  list-style: none;
}

.yomihon summary::-webkit-details-marker {
  display: none;
}

/* The ring follows the control's own corners. Setting a radius here repainted
   every rounded control with a different one the moment it took focus. */
.yomihon :focus-visible {
  outline: 2px solid var(--fg-subtle);
  outline-offset: 2px;
}

.yomihon ::selection {
  background: var(--line-strong);
}

.yomihon *::-webkit-scrollbar {
  height: 11px;
  width: 11px;
}

.yomihon *::-webkit-scrollbar-thumb {
  background: var(--border-strong);
  background-clip: padding-box;
  border: 3px solid transparent;
  border-radius: 8px;
}

.yomihon *::-webkit-scrollbar-track {
  background: transparent;
}

/* ============================================================
   DS components (only the ones this page uses)
   ============================================================ */

/* ---- Sidebar nav item ---- */
.ui-navitem {
  align-items: center;
  border-radius: var(--r-md);
  color: var(--fg-muted);
  cursor: pointer;
  display: flex;
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  gap: 10px;
  line-height: 1.5rem;
  padding: 4px 12px;
  text-decoration: none;
  /* A row's name that takes two lines splits them evenly. Left to fill the first
     line, a Chinese title strands its last character alone on the second. */
  text-wrap: balance;
  transition: background var(--dur-base) var(--ease-standard), color var(--dur-base) var(--ease-standard);
}

.ui-navitem:hover {
  background: var(--wash);
  color: var(--fg);
}

.ui-navitem[aria-current='page'],
.ui-navitem.is-active {
  color: var(--fg);
  font-weight: var(--fw-medium);
}

.ui-navitem svg {
  color: var(--fg-subtle);
  flex: none;
  height: 17px;
  width: 17px;
}

.ui-navitem__count {
  color: var(--fg-subtle);
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  font-variant-numeric: tabular-nums;
  line-height: 1.25rem;
  margin-left: auto;
}

.ui-navitem__count--warn {
  color: var(--warn);
}

/* The row for the note being read keeps the room its status chip would take and
   shows nothing in it, so its name has the same width as the rows around it and
   wraps in the same place. Dropping the chip instead widened the name's column
   and moved the text by a line between sibling lessons. */
.ui-navitem__count--reserved {
  visibility: hidden;
}

/* Where a listing is read by date, the day leads the row here as it does at
   full width, so the desk's corner of that shelf is scanned down the same
   column as the shelf's own page. The day and the kind at the far end are each
   a few words that mean nothing cut in half, so both keep their own width and
   the name is what gives — clipped with an ellipsis rather than pushing the
   row off a phone or folding the kind beside it onto a second line. */
.ui-navitem__when {
  color: var(--fg-subtle);
  flex: none;
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  font-variant-numeric: tabular-nums;
}

.ui-navitem__when + span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ui-navitem__when ~ .ui-navitem__count {
  flex: none;
  white-space: nowrap;
}

/* Where a listing is read for what each document is called, the name opens the
   row and the day follows it, with the kind after that where the row has one.
   The name is still what gives when the corner is narrow, so it takes the room
   the short faces leave and is clipped with an ellipsis; the faces after it keep
   their own width. */
.ui-navitem--by-title > :first-child {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* A kept location or authored source section identifies the row, so a narrow
   shelf must keep those words visible instead of clipping them like a title. */
.ui-navitem--wrap {
  align-items: flex-start;
  flex-direction: column;
  gap: 3px;
}

.ui-navitem--wrap > span {
  max-width: 100%;
  overflow: visible;
  overflow-wrap: anywhere;
  text-overflow: clip;
  white-space: normal;
}

.ui-navitem--wrap > .ui-navitem__count {
  margin-left: 0;
}

.y-here .ui-navitem,
.y-railgroup .ui-navitem,
.y-railreports .ui-navitem,
.y-citedby .ui-navitem,
.y-basedon .ui-navitem,
.y-pair .ui-navitem {
  border-radius: 0;
  box-shadow: inset 1px 0 0 var(--line);
  padding-left: 16px;
}

.y-here .ui-navitem:hover,
.y-railgroup .ui-navitem:hover,
.y-railreports .ui-navitem:hover,
.y-citedby .ui-navitem:hover,
.y-basedon .ui-navitem:hover,
.y-pair .ui-navitem:hover {
  background: transparent;
  box-shadow: inset 1px 0 0 var(--line-strong);
}

.y-here .ui-navitem.is-active,
.y-railgroup .ui-navitem.is-active,
.y-railreports .ui-navitem.is-active,
.y-citedby .ui-navitem.is-active,
.y-basedon .ui-navitem.is-active,
.y-pair .ui-navitem.is-active,
.y-here .ui-navitem[aria-current='page'],
.y-railgroup .ui-navitem[aria-current='page'],
.y-railreports .ui-navitem[aria-current='page'] {
  box-shadow: inset 2px 0 0 var(--fg);
}

/* A row in the left rail starts its words a row's inset in from the rule that
   marks it. A row with a status carries it as a dot set on that rule, the way
   a stop sits on a line, so the dot takes no width beside the name; a row
   whose entry names nothing sets its mark in the same place. The same insets
   hold in every rail the column carries (a course, a map, a book, a folder)
   and in the drawer it becomes on a phone, so a title or a lesson sits at the
   same place whichever of them is open. */
.y-rail-left .ui-navitem {
  column-gap: 6px;
  padding-left: var(--rail-row-inset);
  position: relative;
}

/* A folder's neighbourhood and the reports are lists the way a group's rows
   are, so their rule stands at the same place under their heading. */
.y-rail-left .y-here > .ui-navitem,
.y-rail-left .y-railreports > .ui-navitem {
  margin-left: var(--rail-list-inset);
}

.y-rail-left .ui-navitem > .y-navdot {
  inset-block-start: calc(4px + (1.5rem - 6px) / 2);
  inset-inline-start: -3px;
  position: absolute;
}

/* A row whose entry names nothing carries a warning glyph in the dot's place.
   It is a glyph rather than a dot, so it is given the rail's ground to stand
   on where it crosses the rule. */
.y-rail-left .y-navitem--broken > .y-navmark {
  background: var(--rail-ground);
  inset-block-start: calc(4px + (1.5rem - 12px) / 2);
  inset-inline-start: -4px;
  line-height: 12px;
  position: absolute;
  text-align: center;
  width: 9px;
}

/* ---- Sidebar section label ---- */
.ui-side__label {
  align-items: baseline;
  color: var(--fg);
  display: flex;
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  font-weight: var(--fw-semibold);
  gap: 8px;
  line-height: 1.5rem;
  padding: 0;
}

/* A count beside a heading reads the same wherever the heading stands: in the
   rail's label, or in the summary that folds the same block into the narrow
   column. It is a quiet figure, not a badge, so only the diagnostics' own
   count (a warning) keeps a fill. */
.ui-side__label .ui-navitem__count,
.y-toc-inline__summary .ui-navitem__count {
  color: var(--fg-subtle);
  font-size: var(--fs-14);
  font-weight: var(--fw-regular);
  line-height: 1.5rem;
  margin-left: 0;
}

/* ---- Status badge (dot + name) ----
   The author's own word for a note, drawn the same whatever the word is: the
   interface keeps no colour for any status, because which statuses are
   settled is the contract's to say and a colour per name would be a second
   copy of its vocabulary. On a note the badge is one fact about the page.
   Down a list a status is the exception on a row, so it is quiet text with no
   fill or padding to hold one and no dot. */
.ui-status {
  align-items: center;
  background: var(--wash);
  border-radius: var(--r-sm);
  color: var(--fg-muted);
  display: inline-flex;
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  font-weight: var(--fw-medium);
  gap: 6px;
  line-height: 1.25rem;
  padding: 2px 8px;
}

.ui-status::before {
  background: currentColor;
  border-radius: 50%;
  content: '';
  flex: none;
  height: 6px;
  width: 6px;
}

.y-lesson .ui-status,
.y-homenote__meta .ui-status {
  background: none;
  color: var(--fg-subtle);
  padding: 0;
  white-space: nowrap;
}

.y-lesson .ui-status::before,
.y-homenote__meta .ui-status::before {
  display: none;
}

/* ---- Content-type label (dot + name) ---- */
.ui-type {
  align-items: center;
  color: var(--fg-muted);
  display: inline-flex;
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  gap: 7px;
}

.ui-type::before {
  background: var(--fg-subtle);
  border-radius: 50%;
  content: '';
  flex: none;
  height: 7px;
  width: 7px;
}

/* ---- Kbd ---- */
.ui-kbd {
  color: var(--fg-subtle);
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  font-weight: var(--fw-medium);
  line-height: 1;
}

/* ---- Scrim + dialog (⌘K) ---- */
.ui-scrim {
  background: var(--overlay-mask);
  inset: 0;
  position: fixed;
  z-index: 101;
}

.ui-dialog {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-2);
  box-sizing: border-box;
  width: 100%;
}

/* ============================================================
   Header
   ============================================================ */
.y-header {
  align-items: center;
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  background: color-mix(in oklch, var(--bg) 85%, transparent);
  border-bottom: 1px solid var(--line);
  display: flex;
  flex: none;
  gap: 16px;
  height: var(--header-height);
  padding: 0 var(--header-inset);
  position: sticky;
  top: 0;
  z-index: 30;
}

.y-header__spacer {
  flex: 1;
}
/* The name is what gives when the row runs out of room, and only then. A flex
   item refuses by default to become narrower than its own text, so on a window
   too narrow for the whole row the last control was pushed past the edge
   instead — a reading page at 360 with a scrollbar keeping its own column has
   345 to lay out in and wants 354. Letting the name shorten costs a few of its
   letters at widths no reader spends time at, and costs nothing at every width
   where the row already fits, which is where the narrow lock now watches: the
   name being shortened at all at 360 means something else grew. */
.y-brand {
  align-items: center;
  display: flex;
  gap: 11px;
  min-width: 0;
}

.y-brand__name {
  align-items: center;
  color: var(--fg);
  display: inline-flex;
  font-family: var(--font-display);
  font-size: var(--fs-16);
  font-weight: var(--fw-semibold);
  gap: 8px;
  letter-spacing: var(--track-snug);
  min-width: 0;
}

.y-brand__name > span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.y-brand__mark {
  display: block;
  flex: none;
  height: 24px;
  width: 24px;
}

.y-brand__tag {
  color: var(--fg-subtle);
  font-family: var(--font-sans);
  font-size: var(--fs-14);
}

.y-searchbtn {
  align-items: center;
  background: transparent;
  border: 0;
  border-radius: var(--r-md);
  box-shadow: inset 0 0 0 1px var(--line);
  color: var(--fg-subtle);
  cursor: pointer;
  display: flex;
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  gap: 10px;
  height: 32px;
  min-width: 214px;
  padding: 0 10px 0 11px;
}

.y-searchbtn:hover {
  box-shadow: inset 0 0 0 1px var(--line-strong);
  color: var(--fg-muted);
}

.y-searchfallback {
  color: var(--fg-subtle);
  font-size: var(--fs-14);
  white-space: nowrap;
}

/* With scripting off, the adjacent text link names the search-page route.
   Use the existing compact search-button presentation so two entrances do
   not displace the wordmark. Its accessible name and native command remain. */
.y-header:has(.y-searchfallback) .y-searchbtn {
  min-width: 0;
}

.y-header:has(.y-searchfallback) .y-searchbtn__label,
.y-header:has(.y-searchfallback) .y-searchbtn .ui-kbd {
  display: none;
}

.y-searchbtn__label {
  flex: 1;
  text-align: left;
}

.y-iconbtn {
  align-items: center;
  background: transparent;
  border: 0;
  border-radius: var(--r-md);
  color: var(--fg-muted);
  cursor: pointer;
  display: inline-flex;
  flex: none;
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  font-weight: var(--fw-medium);
  height: 32px;
  justify-content: center;
  width: 32px;
}

.y-iconbtn:hover {
  background: var(--wash);
  color: var(--fg);
}

.y-iconbtn svg {
  height: 16px;
  width: 16px;
}
/* The language control rides inside a form so it works with no script; the
   form's own box dissolves so the button sits in the header row exactly as
   its sibling controls do. */
.y-langform {
  display: contents;
}

.y-langbtn {
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  font-weight: var(--fw-medium);
}

.y-kbdpref {
  border-top: 1px solid var(--border-faint);
  margin-top: 14px;
  padding-top: 12px;
}

.y-shortcutpref {
  align-items: center;
  color: var(--fg);
  cursor: pointer;
  display: inline-flex;
  font-family: var(--font-sans);
  font-size: var(--fs-13);
  gap: 8px;
  min-height: 32px;
}

/* A box or a choice that is set is drawn in the ink: the seal ink is kept for
   the reader's own place, and a control in that colour read as one more thing
   marking where they had stopped. */
.y-shortcutpref__input {
  accent-color: var(--fg);
  cursor: pointer;
  flex: none;
  height: 14px;
  margin: 0;
  width: 14px;
}

.y-shortcutpref__label {
  white-space: nowrap;
}

.y-kbdpref__note {
  color: var(--fg-subtle);
  font-size: var(--fs-12);
  line-height: 1.55;
  margin: 4px 0 0;
}

.y-shortcutpref__on,
.y-shortcutpref__off {
  display: none;
  font-family: var(--font-sans);
  font-size: var(--fs-12);
}

[data-single-key-shortcuts='on'] .y-shortcutpref__on {
  color: var(--fg);
  display: inline;
  font-weight: 500;
}

[data-single-key-shortcuts='off'] .y-shortcutpref__off {
  color: var(--fg-faint);
  display: inline;
}
/* The readings control is a switch with its name beside it: the track and the
   thumb are drawn on the button itself from the pressed state the page already
   carries, so what a reader sees and what a screen reader is told are one
   attribute. Off is a hollow track with the thumb at its start; on is a filled
   track a step down from the ink, with the thumb at its end, so a switch that
   is set does not outweigh the words around it. Both states keep the track's
   edge at 3:1 against the page, so the control reads whichever way it is set. */
.y-rubybtn {
  --switch-inset: 8px;
  gap: 8px;
  padding: 0 var(--switch-inset) 0 10px;
  position: relative;
  width: auto;
}

.y-rubybtn__label {
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  font-weight: var(--fw-medium);
  line-height: 1.25rem;
}

.y-rubybtn__label--short {
  display: none;
}

.y-rubybtn::before {
  background: transparent;
  border-radius: 10px;
  box-shadow: inset 0 0 0 1px var(--fg-subtle);
  content: '';
  flex: none;
  height: 20px;
  order: 2;
  transition: background var(--dur-base) var(--ease-standard), box-shadow var(--dur-base) var(--ease-standard);
  width: 32px;
}

.y-rubybtn::after {
  background: var(--fg-subtle);
  border-radius: 50%;
  content: '';
  height: 14px;
  position: absolute;
  right: calc(var(--switch-inset) + 32px - 3px - 14px);
  top: 9px;
  transition: background var(--dur-base) var(--ease-standard), right var(--dur-base) var(--ease-standard);
  width: 14px;
}

.y-rubybtn[aria-pressed='true']::before {
  background: var(--switch-on);
  box-shadow: none;
}

.y-rubybtn[aria-pressed='true']::after {
  background: var(--bg);
  right: calc(var(--switch-inset) + 3px);
}
/* The row's spare width runs out a little below this point, and further down it
   is already shortening the wordmark to fit what is there; the longer label
   would then be paid for out of the name of the thing the reader is looking at.
   The switch happens here rather than at the last width that fits, so the
   longer label is never the reason the wordmark gives. The short label is the
   tail of the long one rather than a different word, which is what lets the
   spoken name stay the same across the switch. */
@media (max-width: 720px) {
  .y-rubybtn {
    gap: 6px;
    padding-left: 6px;
  }

  .y-rubybtn__label--long {
    display: none;
  }

  .y-rubybtn__label--short {
    display: inline;
  }

  /* Down to its glyph the search entrance is one icon among the row's icons,
     and is drawn as they are: no frame at rest, the wash under a pointer. Only
     its paint changes, so the row it stands in keeps its measured width. */
  .y-searchbtn {
    box-shadow: none;
  }

  .y-searchbtn:hover {
    background: var(--wash);
    box-shadow: none;
  }
}

.y-ico-moon {
  display: none;
}

[data-theme='dark'] .y-ico-sun {
  display: none;
}

[data-theme='dark'] .y-ico-moon {
  display: block;
}
/* The moon enters the same way the dark palette does: a reader who chose
   nothing on a dark system sees dark before any script runs, and the glyph
   has to agree. An explicit light choice escapes through the :not() guard. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) .y-ico-sun {
    display: none;
  }

  :root:not([data-theme='light']) .y-ico-moon {
    display: block;
  }
}

.y-hamburger {
  display: none;
}

/* Six of the header's controls are somewhere to go or something to set rather
   than anything about the note being read — the whole-folder view, the reading
   choices, the text size, the keyboard explanation, the language and the theme
   — and below 938px the row can no longer hold them all. 938px is measured,
   not chosen: at 937 the English chrome, the wider of the two, has begun
   shortening the wordmark to make room, and the name of the thing being read
   is not what should pay for a control that is not about it. Under that width
   the six sit inside a popover this button opens, which brings the browser's
   own light dismiss, Escape, and focus return with it.

   One width serves both languages, though the Traditional Chinese row holds
   out until 617. One breakpoint is one layout; a width per language would be a
   second layout to keep true, and a media query cannot see which language the
   page is in to choose between them.

   At 938px and above the box around them dissolves: display: contents leaves
   its children as the row's own flex items, in the order they are written, so
   the wide row is drawn from the same elements it always was and no control
   is written twice.

   A window dragged wider while the panel is open is the one case the width
   alone cannot answer: an open popover is in the browser's top layer, which
   gives it a box of its own whatever display it is asked for, so it stays a
   panel until something dismisses it. The button therefore stays out on the
   row for as long as the panel is open, at every width — an open panel whose
   button had gone with the window's width would be a panel with nothing
   pointing at it. Pressing it puts the panel away and the controls are back
   in the row. */
.y-foldbtn {
  font-size: var(--fs-18);
  line-height: 1;
}

/* A popover is given the system ink by the browser, and ink is inherited, so
   without this the five controls would be drawn in the operating system's
   black rather than this page's — including at the widths where they are
   sitting in the row and the box around them is not visible at all. */
.y-headerfold {
  color: inherit;
}

.y-headerfold:popover-open {
  align-items: center;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-2);
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  inset: calc(var(--header-height) + 4px) 8px auto auto;
  margin: 0;
  /* As wide as the window allows up to the width of the narrowest phone this
     is read on, so on a phone the panel is only ever kept off the edges and a
     wider window does not stretch it across the screen. A share of the window
     rather than of 100vw: where the scrollbar keeps its own column, 100vw
     counts that column too, and a panel sized from it would hang off the far
     edge by exactly the scrollbar's width. */
  max-width: min(360px, calc(100% - 16px));
  padding: 10px;
  position: fixed;
}

/* Folded, the six are a menu rather than a row of marks: one 36px row each,
   the control's name on the left and what it shows now on the right — the
   size, the language it switches to, the theme's sun or moon. The name is the
   words the control is already announced by, so what is seen and what is heard
   are one string, and a size the script renames is renamed here too. */
.y-headerfold:popover-open {
  align-items: stretch;
  flex-direction: column;
  gap: 0;
  min-width: 248px;
  padding: 6px;
}

/* Each row keeps the display its control already has — a link becomes a block
   as a flex item, an icon button is already a flex box — so a rule elsewhere
   that hides one of them still hides it here. */
.y-headerfold:popover-open > :is(.y-healthlinkbtn, .y-prefslink, .y-iconbtn),
.y-headerfold:popover-open .y-langbtn {
  align-items: center;
  border-radius: var(--r-md);
  color: var(--fg);
  font-size: var(--fs-14);
  font-weight: var(--fw-medium);
  gap: 12px;
  height: 36px;
  justify-content: space-between;
  line-height: 36px;
  padding: 0 10px;
  width: 100%;
}

.y-headerfold:popover-open > .y-iconbtn::before,
.y-headerfold:popover-open .y-langbtn::before {
  color: var(--fg);
  content: attr(aria-label);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.y-headerfold:popover-open > .y-iconbtn > *,
.y-headerfold:popover-open .y-langbtn > * {
  color: var(--fg-subtle);
  flex: none;
}

/* In the menu the page the reader is on is the row on the wash; the line under
   the word is the row's way of saying it. */
.y-headerfold:popover-open > [aria-current='page'] {
  background: var(--wash);
  color: var(--fg);
}

.y-headerfold:popover-open > [aria-current='page']::after {
  content: none;
}

.y-headerfold:popover-open .y-headermark {
  border-top: 1px solid var(--line-faint);
  flex-basis: auto;
  margin-top: 6px;
  padding: 10px 4px 4px;
}

/* Keyboard help opened from the menu stands alone: the menu it was opened from
   steps out of sight instead of showing round the help's edges behind it. It
   is still open underneath, so Escape on the help backs out to it, and a press
   anywhere else puts both away. */
.y-headerfold:popover-open:has(> .y-kbdhelp:popover-open) {
  visibility: hidden;
}

.y-kbdhelp:popover-open {
  visibility: visible;
}

@media (min-width: 938px) {
  .y-foldbtn {
    display: none;
  }

  .y-header:has(.y-headerfold:popover-open) .y-foldbtn {
    display: inline-flex;
  }

  .y-headerfold {
    display: contents;
  }
}

/* ============================================================
   Shell + rails
   ============================================================ */
:root {
  --rail-open: 272px;
  --rail-right: 240px;
}

/* The rails are the window's own edges, as a desktop application's panes are,
   and the reading column takes whatever width is left between them. Nothing
   here centres or caps the shell: capped, it would leave both rails floating in
   from the window's sides, short of the header's ends above them. The article
   keeps its own measure and stands in the middle of the column. */
.y-shell {
  align-items: start;
  display: grid;
  flex: 1;
  grid-template-columns: var(--rail-open) minmax(0, 1fr) var(--rail-right);
  width: 100%;
}
/* The right rail exists to hold reading aids — the table of contents and the
   diagnostics list. A note with neither drops the rail's column entirely and
   the reading column takes the width; the status face at the article's end
   carries write controls wherever the rail is absent. */
.y-shell--rail-empty {
  grid-template-columns: var(--rail-open) minmax(0, 1fr);
}

.y-shell--rail-empty .y-rail-right {
  display: none;
}

.y-nav-scrim {
  background: var(--overlay-mask);
  display: none;
  inset: var(--header-height) 0 0 0;
  position: fixed;
  z-index: 44;
}

/* The rails stay put while the prose moves under them: sticky to just below
   the header, each with its own overflow so a long tree scrolls without
   taking the reading column with it. */
.y-rail-left {
  background: var(--rail-ground);
  border-right: 1px solid var(--line);
  height: calc(100vh - var(--header-height));
  overflow-y: auto;
  padding: 6px var(--rail-pad) 48px var(--rail-pad-start);
  position: sticky;
  top: var(--header-height);
}

/* The rail's first row: the button that folds the whole left column away and,
   beside it, the filter. It is one row, and it sticks to the top of the rail's
   own scroll box, because the rail scrolls by itself and neither the button nor
   the filter may scroll out of reach exactly when the tree is long. The
   negative margins take the row out to the rail's edges so the panel colour,
   not the tree passing underneath, is what sits behind it.

   The button is held to the row's left edge, four pixels in, because that is
   where it stays when the column folds: the strip it is left standing in is
   forty pixels wide, and a control that slid sideways between the two states
   would be a different thing each time it was looked for.

   Its height is fixed, and named, because the rail's scroll padding has to
   equal it: focus moved by the keyboard scrolls a row into the part of the rail
   that is not under this one.

   The script reveals the row before the first paint; with no script it is never
   drawn, filter included, as the filter never was. Below the width where the
   column becomes a drawer the row is only the filter, as it was, and the
   header's own button owns the act of opening it.

   Three lengths set every rail's left edge, so moving it is one change: where
   the rail's titles, part heads and summaries start, how far in from there a
   list's rule stands (under the centre of a summary's chevron, so the line
   reads as running down out of it), and how far a row's words stand from that
   rule. The titles start the header's inset in from the window's edge, in the
   drawer a phone opens as in the column beside the page, so they begin where
   the header does. The button above them is the exception: it keeps to the
   strip's own edge, for the reason given. */
:root {
  --rail-head-height: 44px;
  --rail-list-inset: 6px;
  --rail-pad: 16px;
  --rail-pad-start: var(--header-inset);
  --rail-row-inset: 12px;
}

.y-railtoggle__button {
  display: none;
}

@media (min-width: 901px) {
  .y-railhead {
    align-items: center;
    background: var(--rail-ground);
    box-sizing: border-box;
    display: flex;
    gap: 6px;
    height: var(--rail-head-height);
    margin: -6px calc(-1 * var(--rail-pad)) 4px calc(-1 * var(--rail-pad-start));
    padding: 0 var(--rail-pad) 0 4px;
    position: sticky;
    /* Held six pixels above the content edge, which is the rail's own top
       padding: the row reaches the very top of the rail, so no row of the tree
       passes in the gap above it. */
    top: -6px;
    z-index: 1;
  }

  .y-railhead[hidden] {
    display: none;
  }

  .y-railtoggle__button {
    display: inline-flex;
  }

  .y-railhead .y-filter {
    flex: 1;
    margin: 0;
    min-width: 0;
    width: auto;
  }

  .y-rail-left {
    scroll-padding-top: var(--rail-head-height);
  }
}

/* The mark is a window with its left pane. The pane is filled while the column
   is shown and empty once it is folded, so the glyph is a picture of the state
   rather than of the act. */
.y-railtoggle__pane {
  fill: currentColor;
  fill-opacity: 0.28;
  stroke: none;
}

/* The rail's first line sits on the line of the crumbs opposite it, so the two
   columns start reading at the same height. */
.y-rail-right {
  border-left: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  gap: 32px;
  height: calc(100vh - var(--header-height));
  overflow-y: auto;
  padding: 44px var(--header-inset) 48px 24px;
  position: sticky;
  top: var(--header-height);
}

.y-rail-right > * {
  flex: none;
}
/* The document scrolls, not this element. A reader arrives with focus on the
   body, and the browser scrolls the focused element's scrolling box — so while
   the prose lived in its own overflow container, the viewport had nothing to
   move and space, page-down and the arrows did nothing at all. Keeping the
   scroller here needed the reader to know to press Tab first. */
.y-main {
  min-width: 0;
}

/* left-rail disclosure groups */
.y-railsummary {
  align-items: center;
  color: var(--fg);
  display: flex;
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  font-weight: var(--fw-semibold);
  gap: 5px;
  line-height: 1.25rem;
  padding: 16px 0 6px;
}

.y-railsummary--label {
  font-weight: inherit;
  margin: 0;
}

/* The head of a book rail: the course's name, then how much of it there is.
   The name is the one strong line in the rail; the extent is meta beneath it,
   and a hairline closes the head before the parts begin. The text sits on the
   same gutter as the rows below it. */
.y-railbook__title {
  color: var(--fg);
  display: block;
  font-family: var(--font-sans);
  font-size: var(--fs-16);
  font-weight: var(--fw-semibold);
  line-height: 1.5rem;
  overflow-wrap: anywhere;
  padding: 8px 0 0;
  text-decoration: none;
  text-wrap: balance;
}

.y-railbook__span {
  border-bottom: 1px solid var(--line-faint);
  color: var(--fg-subtle);
  font-family: var(--font-sans);
  font-size: var(--fs-13);
  font-variant-numeric: tabular-nums;
  line-height: 1.25rem;
  margin: 0;
  padding: 4px 0 12px;
}

.y-railgroup {
  margin: 2px 0 8px var(--rail-list-inset);
}

.y-railsep {
  background: var(--line-faint);
  height: 1px;
  margin: 2px 0;
}

.y-chevron {
  flex: none;
  opacity: 0.7;
  transition: transform var(--dur-base) var(--ease-standard);
}

.yomihon details:not([open]) .y-chevron {
  transform: rotate(-90deg);
}

/* Disclosure open/close animates the body height — Chromium-native, CSS-only.
   interpolate-size lets a height:auto transition resolve; ::details-content is
   the wrapper the engine wraps a <details>'s body in, and declaring the
   keyword on that wrapper keeps it off everything else inside the fold. Every
   fold the interface owns opens this way — the rail's groups, the note's
   metadata row, the inline reading aids, the no-return confirm, and the search
   page's value column — so the reader meets one gesture rather than five.
   Disclosures in a note's prose are left alone: those are the author's, and a
   callout is part of the writing. Reduced motion disables all of it below —
   the blanket motion kill reaches elements and ::before/::after, not this
   pseudo-element, so each fold is turned off by name. */
.y-rail-left details::details-content,
.y-metarow::details-content,
.y-toc-inline::details-content,
.y-statusconfirm::details-content,
.y-facets::details-content {
  height: 0;
  interpolate-size: allow-keywords;
  overflow: clip;
  /* The body has to be cut off at the edge it is growing towards, or the whole
     of it would show while the fold is still a few pixels tall. The cut is held
     four pixels out so that it falls outside a focus ring rather than through
     it: a row at the edge of a fold is reached by keyboard like any other, and
     a ring with its sides missing is not the ring anyone learned to look for. */
  overflow-clip-margin: 4px;
  transition: height var(--dur-base) var(--ease-standard), content-visibility var(--dur-base) var(--ease-standard);
  transition-behavior: allow-discrete;
}

.y-rail-left details[open]::details-content,
.y-metarow[open]::details-content,
.y-toc-inline[open]::details-content,
.y-statusconfirm[open]::details-content,
.y-facets[open]::details-content {
  height: auto;
}

/* The remembered filter narrows the rail when the client module runs, which can
   be a frame after the rail first painted. That is a restore and not the
   reader folding a group, so the folds and their chevrons are held still while
   it is written instead of animating the correction. */
.y-rail-left[data-rail-restoring] details::details-content,
.y-rail-left[data-rail-restoring] .y-chevron {
  transition: none;
}

/* lifecycle status rows */
.y-navdot {
  background: var(--fg-subtle);
  border-radius: 50%;
  flex: none;
  height: 6px;
  width: 6px;
}

.y-navicon {
  display: inline-flex;
  flex: none;
  justify-content: center;
  width: 17px;
}

/* filter box — pure enhancement: ships hidden, and the sidebar's pre-paint
   script reveals it in the first frame; with no script it never appears */
.y-filter {
  background: transparent;
  border: 0;
  border-radius: var(--r-md);
  box-shadow: inset 0 0 0 1px var(--line);
  box-sizing: border-box;
  color: var(--fg);
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  margin: 6px 0 4px;
  outline: none;
  padding: 6px 10px;
  width: 100%;
}

.y-filter::placeholder {
  color: var(--fg-subtle);
}

.y-filter:focus {
  box-shadow: inset 0 0 0 1px var(--fg-subtle);
}
/* the filter's designed no-match state — never a silently empty column */
.y-filter-empty {
  color: var(--fg-subtle);
  font-family: var(--font-serif);
  font-size: var(--fs-13);
  font-style: italic;
  line-height: 1.6;
  margin: 8px 0 4px;
}

/* "here" — the current note's same-directory siblings */
.y-here {
  display: flex;
  flex-direction: column;
  padding: 2px 0 4px;
}

.y-here__label {
  color: var(--fg);
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  font-weight: var(--fw-semibold);
  line-height: 1.25rem;
  overflow: hidden;
  padding: 12px 0 6px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* map tree — nested heading disclosures */
.y-tree {
  border-left: 1px solid var(--line);
  margin-left: 6px;
  padding-left: 10px;
}

.y-navmark {
  color: var(--fg-subtle);
  font-family: var(--font-sans);
  font-size: var(--fs-12);
}
/* the row that closes a narrowed shelf and hands the reader its own page */
.y-shelfall {
  color: var(--fg-subtle);
  font-family: var(--font-sans);
  font-size: var(--fs-12);
}

.y-navmark--warn {
  color: var(--warn);
}

/* The word on the briefing the vault keeps current. A report is called by its
   title, which runs longer than a file name does, so the title is what wraps
   and the word keeps its line: "最新" broken across two lines reads as nothing. */
.y-navmark--newest {
  flex: none;
  white-space: nowrap;
}

.y-navitem--broken {
  cursor: default;
}

.y-navitem--broken:hover {
  background: transparent;
  color: var(--fg-muted);
}

.y-navdiag {
  padding-bottom: 8px;
}

.y-navdiag p {
  color: var(--warn);
  font-family: var(--font-serif);
  font-size: var(--fs-13);
  line-height: 1.5;
  margin: 3px 0 7px;
}

/* a course summary (the study-path title) and a section summary (a heading) */
.y-railitem,
.y-treeitem {
  align-items: center;
  border-radius: var(--r-md);
  color: var(--fg-muted);
  cursor: pointer;
  display: flex;
  font-size: var(--fs-14);
  gap: 7px;
  line-height: 1.25rem;
  padding: 6px 8px;
}

.y-railitem {
  color: var(--fg);
  font-weight: var(--fw-semibold);
}

.y-railitem:hover,
.y-treeitem:hover {
  background: var(--wash);
  color: var(--fg);
}

.y-railitem__name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.y-lessonsteps {
  padding: 2px 0 4px;
}

.y-lessonsteps__path {
  color: var(--fg-subtle);
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  font-weight: var(--fw-medium);
  padding: 4px 0 2px;
}

.y-railitem__full {
  color: var(--fg-muted);
  display: block;
  font-family: var(--font-sans);
  font-size: var(--fs-13);
  padding: 4px 8px 6px;
}

.y-railitem__full:hover {
  color: var(--fg);
}

/* the foot of the rail — which folder this is, how large it is, and what the
   health page has to say about it. It is the label face the rail's own headings
   use, quieter still: it is where the reader lands when they have run out of
   tree, not somewhere they are being sent. It flows last rather than pinning to
   the viewport, so a long tree scrolls past it and the drawer ends the same way.
   No box of its own: the rail's frame is the only frame here, and the hairline
   above it is the same separator that stands between every other group. */
.y-railfoot {
  /* The quiet ink is set on the block rather than on the link inside it: every
     anchor in this interface inherits its colour, and a rule naming one class
     is outranked by the rule that says so. */
  color: var(--fg-subtle);
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 12px 0 4px;
}

.y-railfoot__name {
  color: var(--fg-muted);
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  font-weight: var(--fw-medium);
  line-height: 1.25rem;
  /* A folder name is one word somebody chose and can be long. It wraps rather
     than ellipsing, because a truncated name is the one thing this line exists
     to say, and a rail that scrolls sideways has nothing to scroll back to. */
  overflow-wrap: anywhere;
}

.y-railfoot__size {
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  font-variant-numeric: tabular-nums;
  line-height: 1.25rem;
}

.y-railfoot__health {
  align-items: center;
  display: flex;
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  font-weight: var(--fw-medium);
  gap: 6px;
  line-height: 1.25rem;
  margin-top: 2px;
  min-width: 0;
}

.y-railfoot__health:hover {
  color: var(--fg);
}

.y-railfoot__dot {
  background: var(--fg-subtle);
  border-radius: 50%;
  flex: none;
  height: 6px;
  width: 6px;
}
/* The colour repeats what the count already says in words, so a reader who
   cannot tell the two dots apart has lost nothing. The state is read off the
   foot itself rather than off any element carrying the attribute, so nothing
   else that ever states a health can reach in here. */
.y-railfoot[data-health='clear'] .y-railfoot__dot {
  background: var(--sage);
}

.y-railfoot[data-health='findings'] .y-railfoot__dot {
  background: var(--warn);
}

.y-railfoot[data-health='findings'] .y-railfoot__count {
  color: var(--warn);
}

/* ============================================================
   The reading shell
   ============================================================ */
/* Five pages are one shape: a note, a file, a study path, the reading choices,
   and the two a reader only reaches by accident — an address that names
   nothing, and a status write that was refused. A reader moving between them
   is reading the same kind of prose, so they are given the same line, the same
   space either side of it, and the same room above and below, in one place
   where that can be read as one decision.

   The line length is the constant and the gutter is what changes, so the box is
   derived from both rather than fixed on its own. Held the other way round, a
   narrower window spent its reclaimed gutter on longer lines: the measure grew
   by a tenth exactly when the reader had less room, which is backwards. */
.y-article,
.y-listen,
.y-prefs,
.y-recovery,
.y-syl {
  margin: 0 auto;
  max-width: calc(var(--measure-read) + 2 * var(--gutter-read));
  padding: 44px var(--gutter-read) 120px;
}

/* ============================================================
   The shelf shell
   ============================================================ */
/* A page that lists what the folder holds rather than setting one thing to be
   read: the desk, the four ways in, a folder seen whole, and the page naming
   what the folder owes. A list is read across as well as down — a row carries a
   title, a figure and a status — so it is laid out wider than a paragraph, and
   the width is stated once for all of them rather than found again per page. */
.y-healthpage,
.y-home {
  margin: 0 auto;
  max-width: calc(var(--measure-list) + 2 * var(--gutter-list));
  padding: 42px var(--gutter-list) 120px;
}

/* A page arrives as a whole new document, so the block of content under the
   header is the one thing that changes beneath a reader who pressed a link.
   It comes forward: from a little faint and a little small, growing into
   place over a curve that decelerates, which says the page changed without
   sending anything up or down the way the reader scrolls. Only that block
   moves. The header and the rails are the same on both sides of the press and
   stay still, and <main> is left alone because a transform on it would take
   every positioned descendant of the column with it. The reading choices page
   is <main> itself, with no block inside it to move, so it arrives without
   this. It carries no blur: a filter over a layer as tall as the column would
   be repainted on every press, and it reads as a landing page, not a book.

   A scale moves each point by its distance from the origin, and the column
   can be thousands of pixels tall. From its own centre the first line of a
   note three thousand pixels tall would travel about twenty pixels, against
   two on a short page. The origin is therefore the centre of the part of the
   column on screen when the page opens at the top, or the centre of the block
   where the block is shorter than that, so the lines the reader is looking at
   barely move and a short page and a long note arrive alike. That holds only
   at the top. A page read lower down would have its text moved by twenty
   pixels or more, and further the lower it is, so such a page does not play
   the arrival at all.

   The keyframes say only where the movement starts, and the fill is held
   backwards, so once it ends nothing of it is left: a scale that stayed would
   make the block the containing block of whatever is fixed inside it. A page
   the browser put back from history, or reloaded in place, or prepared before
   the reader asked for it, does not play it: nothing new arrives, and the text
   is already where it was being read. Nor does a page that opens at a place in
   it: an address with a fragment, whether it names a heading or a stretch of
   text, or one returning to a kept place, which carries its distance in the
   query. The browser scrolls to the place by where the block is while it is
   still small, and what it was asked for would land a few pixels off once the
   block settled.
   All four are marks the head script writes once, and the rule reads only
   those. A condition worked out here instead, the fragment's own target, say,
   would stop matching when the reader jumped about the page and went back, and
   the block would play the arrival again over text they were reading.
   Reduced motion is answered by the blanket under the interface's own root. */
@keyframes y-come-forward {
  from {
    opacity: 0.5;
    scale: 0.985;
  }
}

html:not([data-prerender], [data-arrival]) :is(
  .y-article,
  .y-healthpage,
  .y-home,
  .y-listen,
  .y-recovery,
  .y-syl
) {
  animation: y-come-forward var(--dur-arrival) var(--ease-arrival) backwards;
  transform-origin: 50% min(50%, 45vh);
}

/* ============================================================
   Center article
   ============================================================ */
.y-crumbs {
  align-items: center;
  color: var(--fg-subtle);
  display: flex;
  flex-wrap: wrap;
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  gap: 8px;
  line-height: 1.25rem;
}

.y-crumbs__sep {
  color: var(--line-strong);
}
/* The title can be a link's destination — a note opening with a heading that
   repeats it hands its anchor here — so it keeps the same clearance from the
   sticky header that every heading in the prose has. */
.y-title {
  color: var(--fg);
  font-family: var(--font-serif);
  font-size: var(--fs-ed-36);
  font-weight: var(--fw-medium);
  line-height: 1.2222;
  margin: 12px 0 0;
  overflow-wrap: anywhere;
  scroll-margin-top: var(--header-clearance);
  text-wrap: balance;
}

.y-article[lang]:not(:lang(zh)):not(:lang(ja)):not(:lang(ko)) .y-title {
  letter-spacing: var(--track-snug);
}

/* Han and kana fill the whole em, so a title that wraps needs more between its
   lines than Latin's 44px on 36px does. */
.y-title:lang(ja),
.y-title:lang(ko),
.y-title:lang(zh) {
  line-height: 1.4;
}

.y-metarow {
  align-items: center;
  color: var(--fg-subtle);
  display: flex;
  flex-wrap: wrap;
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  gap: 4px 12px;
  line-height: 1.25rem;
  margin: 16px 0 0;
}
/* a long unbroken title or path breaks mid-string instead of overflowing the
   column; min-width:0 lets the flex item shrink so the break can take effect */
.y-metarow > span {
  min-width: 0;
  overflow-wrap: anywhere;
}
/* The link to the file's own bytes sits with the path it belongs to, quiet
   enough not to compete with the title above it and underlined so it reads as
   a link rather than another piece of metadata. */
.y-metarow__raw {
  border-radius: var(--r-md);
  color: var(--fg-muted);
  font-size: var(--fs-14);
  font-weight: var(--fw-medium);
  line-height: 1.25rem;
  padding: 6px 10px;
  text-decoration: none;
}

.y-metarow__raw:hover {
  background: var(--wash);
  color: var(--fg);
}
/* On a note the same row folds, so the reader meets the title and then the
   prose. The element name qualifies it: the heads of a file yomihon cannot
   read and of a study path keep the plain row above, and only the note's
   disclosure needs its two halves stacked. Type, size and colour stay on the
   row itself, where they are read by everything that asks what this line looks
   like on a given medium. */
/* stylelint-disable-next-line selector-no-qualifying-type -- the same class
   marks a plain row on the file and study-path pages; only the note's
   disclosure is meant here. */
details.y-metarow {
  display: block;
}

.y-metarow__summary {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: 8px 14px;
}

.y-metarow__detail {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 10px;
}

.y-metarow__detail > span {
  min-width: 0;
  overflow-wrap: anywhere;
}
/* The note's facts, read as a description list: dt/dd pairs wrapped one per
   div (HTML5's dl content model allows it), laid out as a wrapping flex row.
   That single rule is what gives "a quiet two-column row on wide screens and
   stacked on narrow ones" for free — the row lays flat where its container is
   wide (the open .y-headmeta copy below) and wraps to one pair per line where
   it is narrow (inside .y-metarow's disclosure); no separate narrow/wide
   variant of the list itself exists. */
.y-notefacts {
  align-items: baseline;
  color: var(--fg-subtle);
  display: flex;
  flex-wrap: wrap;
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  line-height: 1.25rem;
  margin: 0;
}

.y-notefacts > div {
  align-items: baseline;
  display: flex;
  min-width: 0;
}

.y-notefacts > div + div::before {
  color: var(--line-strong);
  content: '·' / '';
  margin: 0 8px;
}

.y-notefacts dt {
  border: 0;
  clip-path: inset(50%);
  display: inline-block;
  height: 1px;
  margin: 0 -1px 0 0;
  overflow: hidden;
  padding: 0;
  white-space: nowrap;
  width: 1px;
}

.y-notefacts dd {
  margin: 0;
  overflow-wrap: anywhere;
}

.y-notefacts a {
  color: var(--fg-subtle);
  font-family: var(--font-mono);
  font-size: var(--fs-12);
  text-decoration: none;
}

.y-notefacts a:hover {
  color: var(--fg);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* The wide-open placement: the same facts, undisclosed, beside the title.
   Hidden by default and shown only from 1280 up, the split .y-rail-right and
   .y-inlineaids already use — the width where .y-metarow's own disclosure
   hides in turn (Responsive, below). Nothing has to turn it back off under
   that width: display: none is already its only rule outside that one query. */
.y-headmeta {
  align-items: center;
  display: none;
  flex-wrap: wrap;
  gap: 8px 4px;
  margin: 16px 0 0;
}

/* The facts line is one row of its own, and wraps inside itself when the path
   is long. The doors (open in Obsidian, leave a thought) stand together on the
   row beneath, left-aligned, so a long path never pushes one of them alone onto
   a line. The first door is pulled back by its own padding so the word, not the
   button's edge, lines up with the title above. */
.y-headmeta > .y-notefacts,
.y-metarow__detail > .y-notefacts {
  flex: 0 0 100%;
}

.y-notefacts + .y-metarow__raw {
  margin-left: -10px;
}

/* Where the facts are stacked in the narrow disclosure the path is the long one,
   so it takes a line of its own instead of wrapping behind a separator that
   would then open the next line. */
.y-metarow__detail .y-notefacts > div:has(a) {
  flex-basis: 100%;
}

.y-metarow__detail .y-notefacts > div:has(a)::before {
  content: none;
}

/* ============================================================
   Reading prose — styles the renderer's REAL output (never reshaped)
   ============================================================ */
.y-prose {
  color: var(--fg-muted);
  font-family: var(--font-read);
  font-size: var(--fs-ed-17);
  line-height: var(--lh-cjk);
}

/* Latin set on a 30px line, where the article says it is not Han, kana or
   Hangul. Ruby needs the 2.0 line above, so an article that declares nothing
   keeps it: the safe line is the default and the open one has to be earned by
   a positive tag. */
.y-article[lang]:not(:lang(zh)):not(:lang(ja)):not(:lang(ko)) .y-prose {
  line-height: var(--lh-latin);
}

/* The column is sized for Han: thirty-eight characters of it at the reading
   size is 646px, and the same 646px carries about 85 Latin characters, well
   past where a line stays easy to carry back to the start of the next. Where
   the article is positively tagged non-CJK the whole article takes a measure
   of thirty-one reading ems instead: about 70 characters of this face on an
   average line and under 78 on the longest. It is the article's measure
   rather than a cap on its paragraphs, so the title, the facts, the code, the
   tables and the way onward share the text's right edge, and the article
   stays centred in its track: capping the paragraphs alone left a second,
   wider edge beside them and the text sitting off to the left of the space it
   was given. Counted from the reading size, it follows the reader's size
   choice. An article that declares no language, and every CJK article, keep
   the full measure. */
.y-article[lang]:not(:lang(zh)):not(:lang(ja)):not(:lang(ko)) {
  --measure-read: calc(var(--fs-ed-17) * 31);
}

/* The head ends where the body begins: forty pixels of space and no rule. The
   head's own bottom margins are never more than that, so the two collapse into
   one gap whichever of them is the larger. */
.y-article > .y-prose {
  margin-top: 40px;
}

.y-prose > :first-child {
  margin-top: 0;
}

.y-prose p {
  margin: var(--sp-block) 0 0;
}
/* Size and the fragment landing pad follow the authored
   level the renderer stamps, not the HTML tag. The tag is one step down so
   the chrome title stays the only h1; the look has to stay where the author
   wrote it, or a #### that the shell emits as h5 reads as body prose and a
   link to it lands under the sticky header. An authored # that is not the
   note's title stays on the page. Preflight resets headings to inherited
   size, so a level given no size of its own here read at body size —
   smaller than its own subsections. The # step sits between the page title
   (--fs-ed-36) and authored ##.
   Every level takes its size from the editorial scale, which the reader's
   size choice moves; a level set from the chrome scale instead stays where
   it is while the prose under it grows past it. Line-height is declared per
   level rather than in the shared rule below, because that rule ties these
   on specificity and sits later, so a value written here would lose to it.
   The gap above a heading says which heading it is before a word of it is
   read: the two deepest share one step, and the top two share the largest. */
.y-prose [data-level='1'] {
  font-size: var(--fs-ed-30);
  line-height: 1.4;
  margin: var(--sp-h2) 0 var(--sp-after-h2);
}

.y-prose [data-level='2'] {
  font-size: var(--fs-ed-24);
  line-height: 1.3333;
  margin: var(--sp-h2) 0 var(--sp-after-h2);
}

.y-prose [data-level='2']:lang(ja),
.y-prose [data-level='2']:lang(ko),
.y-prose [data-level='2']:lang(zh) {
  line-height: 1.4;
}

.y-prose [data-level='3'] {
  font-size: var(--fs-ed-20);
  line-height: 1.4;
  margin: var(--sp-h3) 0 var(--sp-after-h3);
}

.y-prose [data-level='4'] {
  font-size: var(--fs-ed-17);
  line-height: 1.647;
  margin: var(--sp-h4) 0 var(--sp-after-h4);
}

.y-prose [data-level='1'],
.y-prose [data-level='2'],
.y-prose [data-level='3'],
.y-prose [data-level='4'] {
  color: var(--fg);
  font-family: var(--font-read);
  font-weight: var(--fw-semibold);
  scroll-margin-top: var(--header-clearance);
  text-wrap: balance;
}

.y-article[lang]:not(:lang(zh)):not(:lang(ja)):not(:lang(ko)) .y-prose [data-level] {
  letter-spacing: var(--track-snug);
}

/* The gap under a heading belongs to the heading, not to the block that
   follows it: that block's own top margin would otherwise win the collapse and
   hold the heading as far from its text as from the text above. */
.y-prose [data-level] + * {
  margin-top: 0;
}
/* A link can name any heading, and a heading the header covers on arrival is
   one the reader has to scroll back up to find, so the deeper two are given
   the same clearance as the rest. They stop at the body's size instead of
   shrinking a sixth time: read at the browser's own idea of an h5 and an h6
   they were the size of the paragraph beside them, in the paragraph's face
   and weight, with less room above them than the paragraph had. They take the
   fourth level's measure and its room above and below, and the two are told
   apart by weight rather than by size, which is what keeps the topmost level
   from having to grow to a size CJK cannot carry. */
.y-prose [data-level='5'],
.y-prose [data-level='6'] {
  color: var(--fg);
  font-family: var(--font-read);
  font-size: var(--fs-ed-17);
  font-weight: var(--fw-medium);
  line-height: 1.647;
  margin: var(--sp-h4) 0 var(--sp-after-h4);
  scroll-margin-top: var(--header-clearance);
  text-wrap: balance;
}

.y-prose [data-level='5'] {
  font-weight: var(--fw-semibold);
}

.y-prose [data-level='6'] {
  font-weight: var(--fw-medium);
}

/* The size rows share specificity with `.y-prose > :first-child` (0,2,0) and
   sit later, so an opening heading used to keep its top margin and drop the
   whole prose block. This reset is (0,3,0) and only names headings; a
   first-child paragraph still loses to the generic reset above. Print
   inherits the same margin. */
.y-prose > [data-level]:first-child {
  margin-top: 0;
}

.y-prose ul,
.y-prose ol {
  margin: var(--sp-block) 0 0;
  padding-left: 1.5em;
}

.y-prose ul {
  list-style: disc;
}

.y-prose ol {
  list-style: decimal;
}
/* A GFM task item already carries a checkbox. Goldmark emits two
   shapes and no task-list class: a tight item puts the input on the
   li, a loose or multi-paragraph item wraps it in a p. The disc
   inherited from the ul sits beside either, which reads as two
   markers on one row. Hide the disc on those ul items only; ordinary
   discs stay, and an ordered task keeps the number the author wrote.
   The checkbox then occupies the marker gutter so the text lines up
   with a neighbouring plain list. */
.y-prose ul > li:has(> input[type='checkbox']:first-child),
.y-prose ul > li:has(> p:first-child > input[type='checkbox']:first-child) {
  list-style: none;
}

.y-prose ul > li:has(> input[type='checkbox']:first-child) > input[type='checkbox']:first-child,
.y-prose ul > li:has(> p:first-child > input[type='checkbox']:first-child) > p:first-child > input[type='checkbox']:first-child {
  margin-inline-end: 0.35em;
  margin-inline-start: -1.4em;
  vertical-align: middle;
}

.y-prose li {
  margin: 4px 0;
}

.y-prose blockquote {
  border-left: 2px solid var(--border-strong);
  color: var(--fg-subtle);
  margin: var(--sp-block) 0 0;
  padding: 2px 0 2px 16px;
}

.y-prose hr {
  border: none;
  border-top: 1px solid var(--border);
  margin: 32px 0;
}

.y-prose img {
  border-radius: var(--r-lg);
  height: auto;
  max-width: 100%;
}
/* A link is ink with a quiet underline, so it is findable without colour and
   the accent stays where it means something. The wikilink, the preview link and
   the concept link are all this one link: the faults a wikilink can carry
   change the line, never the ink. */
.y-prose a {
  color: var(--fg);
  text-decoration: underline;
  text-decoration-color: color-mix(in oklch, var(--fg) 25%, transparent);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}

.y-prose a:hover {
  text-decoration-color: color-mix(in oklch, var(--fg) 60%, transparent);
}
/* A link that leaves the library opens in a tab of its own, and says so with a
   small arrow after its words. The arrow is generated content with an empty
   alternative, so it is a picture only: the sentence a listener is told is
   written into the link itself. It is a box of its own so the link's underline
   stops at the last word instead of running under the arrow. */
.y-prose a[target='_blank']::after {
  color: var(--fg-subtle);
  content: '\2197' / '';
  display: inline-block;
  font-size: 0.75em;
  margin-inline-start: 0.2em;
}
/* Footnotes — apparatus, not body. A reference is a small mark inside the
   sentence and the notes themselves collect at the foot of the piece, set
   quieter and tighter than the prose they annotate. Both ends are ordinary
   anchors into the same page, so the jump down and every way back work with
   no script; all they need from here is somewhere to land that the sticky
   header is not sitting on top of, and a return arrow big enough to hit. */
.y-prose .footnotes {
  color: var(--fg-subtle);
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  line-height: var(--lh-read);
  margin: var(--sp-h2) 0 0;
}

.y-prose .footnotes hr {
  margin: 0 0 18px;
}

.y-prose .footnotes ol {
  margin: 0;
  padding-left: 1.4em;
}

.y-prose .footnotes li {
  margin: 8px 0;
  scroll-margin-top: var(--header-clearance);
}

.y-prose .footnotes p {
  margin: 0;
}

.y-prose sup:has(.footnote-ref) {
  scroll-margin-top: var(--header-clearance);
}

.y-prose .footnote-ref,
.y-prose .footnote-backref {
  text-decoration: none;
}

.y-prose .footnote-backref {
  display: inline-block;
  min-width: 1.2em;
  text-align: center;
}
/* A block address is a trailing inline span the fragment names. The browser
   aligns that span with the viewport top, so without the same clearance the
   headings already have, the marked line sits under the sticky header. The
   landing is that line, not the paragraph's first line: wrapping lines above
   the caret can still sit under the fold. The pad is --header-clearance, so a
   taller bar moves every landing with it. */
.y-prose [id^='^'] {
  scroll-margin-top: var(--header-clearance);
}

/* inline code + fenced code (chroma) */
.y-prose code {
  background: var(--wash);
  border-radius: var(--r-sm);
  color: var(--fg);
  font-family: var(--font-mono);
  font-size: 0.82em;
  padding: 1px 5px;
}

.y-prose pre {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  font-family: var(--font-mono);
  font-size: var(--fs-13);
  line-height: 1.75;
  margin: var(--sp-block) 0 0;
  overflow-x: auto;
  padding: 16px 20px;
}

.y-prose pre code {
  background: none;
  color: inherit;
  font-size: inherit;
  padding: 0;
}

.y-prose .chroma {
  color: var(--fg);
}

/* The highlighter's own palette is a separate sheet drawn for a white page;
   code here is set in the reading room's inks instead. Keywords are the ink a
   step heavier, strings the sage, comments the quiet ink, numbers and named
   constants the blue the room already uses for a topic, and functions and
   built-ins a step down from the ink. Each is a token, so each follows the
   theme with no rule of its own for dark, and the rules stand outside the
   highlighter's layer, so they apply after both of its dark scopes. Paper
   keeps the highlighter's light palette, which is what it was drawn for. */
@media not print {
  .chroma :is(.k, .kc, .kd, .kn, .kp, .kr, .kt) {
    color: var(--fg);
    font-weight: var(--fw-medium);
  }

  .chroma :is(.s, .s1, .s2, .sa, .sb, .sc, .sd, .se, .sh, .si, .sr, .ss, .sx, .dl) {
    color: var(--sage);
  }

  .chroma :is(.c, .c1, .ch, .cm, .cp, .cpf, .cs) {
    color: var(--fg-subtle);
  }

  .chroma :is(.m, .mb, .mf, .mh, .mi, .mo, .il, .no) {
    color: var(--slot-topic);
  }

  .chroma :is(.nb, .nf) {
    color: var(--fg-muted);
  }
}

/* tables — a table wider than the column scrolls inside its own box (the
   renderer wraps every table in .y-tablewrap) so the article never scrolls */
.y-prose table {
  border-collapse: collapse;
  font-size: var(--fs-14);
  margin: var(--sp-block) 0 0;
  width: 100%;
}

.y-prose .y-tablewrap {
  margin: var(--sp-block) 0 0;
  overflow-x: auto;
}

.y-prose .y-tablewrap table {
  margin-top: 0;
}

.y-prose th {
  border-bottom: 1px solid var(--line-strong);
  color: var(--fg);
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  font-weight: var(--fw-semibold);
  padding: 8px 14px;
  text-align: left;
}

.y-prose td {
  border-bottom: 1px solid var(--line-faint);
  color: var(--fg-muted);
  font-family: var(--font-serif);
  font-size: var(--fs-15);
  padding: 10px 14px;
}

/* A table stands on the text's own edges: its first column starts where a
   paragraph does and its last ends where one ends, so it does not read as a
   box set a little inside the column. */
.y-prose :is(th, td):first-child {
  padding-inline-start: 0;
}

.y-prose :is(th, td):last-child {
  padding-inline-end: 0;
}

.y-prose tr:last-child td {
  border-bottom: none;
}

/* highlight (==text== → <mark>, no class). Scoped to the shell rather than to
   the prose, because the reading column is not the only place a mark appears:
   a search result marks every hit in its title, its path and its snippet, and
   those sit outside the column. Left prose-scoped, a results page fell back to
   the browser's own pure yellow — the one saturated primary colour anywhere in
   the product, fired dozens of times at once on a page of hits, and on a dark
   evening it landed on a near-black ground.

   The ink is explicit, not inherited: a mark on the path line sits in 11px
   faint type, and inheriting that token through the gold wash lands under
   4.5:1. Horizontal padding is omitted so a stemmed tail sits against its
   highlighted head (`<mark>Goroutine</mark>s` is one word). */
.yomihon mark {
  background: color-mix(in oklch, var(--gold) 26%, transparent);
  border-radius: var(--r-sm);
  color: var(--gold-ink);
}

/* wikilinks — the renderer's real classes (a.wikilink / .wikilink-broken / .wikilink-ambiguous) */
.y-prose .wikilink-broken {
  border-bottom: 1px dashed var(--warn);
  color: var(--fg-muted);
  cursor: help;
  text-decoration: none;
}

.y-prose .wikilink-ambiguous {
  border-bottom: 1px dotted var(--fg-muted);
  color: var(--fg-muted);
  cursor: help;
  text-decoration: none;
}
/* A picture whose file the vault does not hold. The browser draws its own
   broken-image mark and this says why: the same dashed warn line an unwritten
   citation carries, drawn around the image rather than under a word, so the
   two absences read as one kind of thing on a page that has both. */
.y-prose .image-missing {
  border-bottom: 1px dashed var(--warn);
  cursor: help;
  display: inline-block;
}
/* A link whose note was found and whose place inside it was not. It keeps the
   link's ink and stays followable, because the address still leads somewhere
   real; what changes is the line under it. Dotted says the same thing it says
   on an ambiguous name — resolved, but not precisely — and the muted colour
   says this is the smaller of the two faults. The dashed warn line stays
   reserved for a name with no target at all. */
.y-prose .wikilink-degraded {
  border-bottom: 1px dotted var(--fg-muted);
  text-decoration: none;
}

.y-prose .wikilink-degraded:hover {
  border-bottom-color: var(--fg-subtle);
}

/* embeds */
.y-prose .embed {
  border-left: 2px solid var(--line-strong);
  margin: var(--sp-block) 0 0;
  padding: 2px 16px;
}
/* A machine detail is one unbroken identifier as often as not, and the places
   that show one are the narrowest columns on the page — the rail's status
   panel and the sidebar's navigation faults sit near 230px. Left unbreakable
   it overflowed its column and the key naming the fault was clipped off the
   edge, which is the one word the reader needed. Breaking anywhere also lets
   the column reach its own min-content width instead of being held open. */
.y-diagdetail {
  overflow-wrap: anywhere;
}

/* One sentence beside the action that earned it. Empty regions stay available
   to assistive technology; filling one starts the same entrance as an arrival
   receipt. The entrance is a fade and nothing else, because a single line of
   text that moves or changes size reads as a wobble. Caller classes only
   decide where the sentence sits. */
.y-reply {
  color: var(--fg-muted);
  font-family: var(--font-serif);
  font-size: var(--fs-13);
  line-height: 1.7;
}

.y-reply:not(:empty) {
  animation: y-reply-in var(--dur-slow) var(--ease-standard);
}

.y-reply[data-reply-tone='refused'] {
  color: var(--warn);
}

.y-reply:empty {
  margin: 0;
}

.y-flipreceipt {
  margin: 0 0 12px;
}

@keyframes y-reply-in {
  from {
    opacity: 0;
  }
}

/* The freshness line: one quiet sentence the client puts above the article when
   the file has moved on from the words printed below. It takes the flip
   receipt's weight because it reports the same kind of thing — something that
   happened to this note while the page was open — and neither of them is the
   article. The server never sends it, so there is nothing here to hide when
   scripting is off. Its control is a button rather than a link: what it does is
   reload this address, which no href expresses. */
.y-freshness {
  color: var(--fg-muted);
  font-size: var(--fs-13);
  margin: 0 0 12px;
}
/* The moment the sentence becomes true usually finds the reader deep in the
   prose, and the element lives at the top of the column — left in plain flow
   it sat thousands of pixels above the viewport, detected but unseen. Sticky
   keeps it just below the header (the --header-height every pinned edge here clears)
   without moving the reader's place; the page ground behind it, because it
   floats over prose once stuck. Scoped to the state attribute the banner
   carries so the recovery page's hold note, which shares the class, stays in
   flow beside its link. No motion is added, so reduced-motion needs nothing. */
.y-freshness[data-freshness] {
  background: var(--bg);
  border-bottom: 1px solid var(--border-faint);
  padding: 8px 0;
  position: sticky;
  top: var(--header-height);
  z-index: 5;
}

.y-freshness__action {
  color: var(--link);
  font-family: var(--font-sans);
  font-size: var(--fs-13);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}
/* stylelint-disable-next-line selector-no-qualifying-type -- the class sits on
   both a button (the reload control) and an anchor (the search link); native
   button chrome needs stripping only on the former, so the type stays. */
button.y-freshness__action {
  background: none;
  border: 0;
  cursor: pointer;
  padding: 0;
}

.y-syl-nocourse {
  color: var(--fg-muted);
  margin: 18px 0 0;
  max-width: 60ch;
}
/* An embed whose address the note does not answer to leaves a block with no
   excerpt in it: the provenance line, and a notice naming the address. The
   warn ink on its rule is what marks it as a fault the reader can see, since
   the block itself is otherwise as quiet as an excerpt. */
.y-prose .embed--withheld {
  border-left-color: var(--warn);
}
/* The lines an excerpt carries above itself: whose note the words come from,
   and — when it applies — that the fragment named a section the source
   repeats, or that it named a place the note does not have and nothing of
   the note follows. All answer what this block is rather than what it says,
   so all read the same and sit in the same place; the provenance name is a
   link and takes the prose link ink. */
.y-prose .embed__source,
.y-prose .embed__note {
  color: var(--fg-subtle);
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  margin: 0 0 8px;
}

/* An excerpt that opens on a heading starts at its provenance line, not a
   section's worth of room below it: the room above a heading separates it from
   the text before it, and inside an excerpt there is none. */
.y-prose .embed__source + :is(h2, h3, h4, h5, h6) {
  margin-top: 8px;
}

.y-prose .embed-media {
  background: var(--wash);
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-md);
  color: var(--fg-muted);
  font-family: var(--font-mono);
  font-size: var(--fs-12);
  padding: 10px 14px;
}

/* callouts — renderer emits .callout / .callout-note / .callout-warning + title/icon/body */
.y-prose .callout {
  background: var(--wash);
  border-radius: var(--r-lg);
  margin: var(--sp-block) 0 0;
  padding: 16px 20px;
}

.y-prose .callout-warning {
  background: var(--warn-bg);
}

.y-prose .callout-quote .callout-body {
  color: var(--fg-subtle);
}
/* The title row is set in sentence case, never tracked capitals: these titles
   are authored words — 口述紀錄, a person's name — and capitals with spacing
   dressed them as machine labels while doing nothing at all to CJK. */
.y-prose .callout-title {
  align-items: center;
  color: var(--fg);
  cursor: default;
  display: flex;
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  font-weight: var(--fw-semibold);
  gap: 8px;
  line-height: 1.25rem;
  margin: 0 0 6px;
}
/* stylelint-disable-next-line selector-no-qualifying-type -- a callout renders
   as either a details (foldable, clickable summary) or a div (plain, nothing
   to click); the type picks out only the former's title for the pointer cursor. */
.y-prose details.callout > .callout-title {
  cursor: pointer;
}

.y-prose .callout-warning .callout-title {
  color: var(--warn);
}

.y-prose .callout-icon {
  flex: none;
  height: 16px;
  width: 16px;
}

/* A folded callout is one line, its title, so it is given the room of one: a
   closed fold kept the open callout's padding and stood as a slab around a
   single row. */
/* stylelint-disable-next-line selector-no-qualifying-type -- a callout renders
   as either a details or a div; only the details can be closed. */
.y-prose details.callout:not([open]) {
  padding: 12px 16px;
}

/* stylelint-disable-next-line selector-no-qualifying-type -- as above. */
.y-prose details.callout:not([open]) > .callout-title {
  margin-bottom: 0;
}

.y-prose .callout-body {
  color: var(--fg-muted);
  font-family: var(--font-serif);
  font-size: var(--fs-ed-17);
  line-height: var(--lh-read);
  margin: 0;
}

.y-prose .callout-body > :first-child {
  margin-top: 0;
}

/* Carried out of sight but not out of the page: text meant for a reader who
   is listening rather than looking. Clipped to a single pixel it shows
   nothing and displaces nothing, while staying in the accessibility tree,
   which display:none would not.

   It stays in the flow on purpose. Taken out of it, the box hangs from the
   nearest positioned ancestor — and where there is none, from the page
   itself, which leaves it free of any scroll container it appears to sit
   inside and able to hold the whole document open to its own depth. A
   utility whose safety depends on every future caller remembering to anchor
   it is a trap with a delay on it. */
.y-offscreen {
  border: 0;
  clip-path: inset(50%);
  display: inline-block;
  height: 1px;
  margin: 0;
  overflow: hidden;
  padding: 0;
  white-space: nowrap;
  width: 1px;
}

/* mermaid placeholder / rendered svg */
.y-prose .mermaid-diagram {
  background: var(--panel);
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-lg);
  color: var(--fg-subtle);
  font-family: var(--font-mono);
  font-size: var(--fs-13);
  margin: var(--sp-block) 0 0;
  overflow-x: auto;
  padding: 12px;
  text-align: center;
  white-space: pre-wrap;
}

.y-prose .mermaid-diagram svg {
  height: auto;
  max-width: 100%;
}
/* Shimmer while the client render is genuinely coming: gated on the boot
   signal so a page without the script keeps the plain source text, and
   released if the module or one diagram fails (data-mermaid-error on the
   root or block). */
[data-js]:not([data-mermaid-error]) .y-prose .mermaid-diagram:not([data-mermaid-error]):not(:has(svg)) {
  animation: y-shimmer 1.4s linear infinite;
  background: linear-gradient(100deg, var(--panel) 40%, var(--elevated) 50%, var(--panel) 60%);
  background-size: 200% 100%;
  color: transparent;
}
/* Never inside a hover card. The client binds the diagram renderer once, over
   the document it booted on, and a card's prose arrives long afterwards — so
   the wait above would run forever over source text it had already painted
   transparent, and would leave a blank grey box for a reader who asked for
   less motion. The card shows the fence as written, which is what a page with
   no scripting at all shows. The selector carries the waiting rule's own shape
   because it has to outrank it, not merely follow it. */
[data-js]:not([data-mermaid-error]) .y-preview .y-prose .mermaid-diagram:not([data-mermaid-error]):not(:has(svg)) {
  animation-name: none;
  background-color: var(--panel);
  background-image: none;
  color: var(--fg-subtle);
}

[data-js][data-mermaid-error] .y-prose .mermaid-diagram:not(:has(svg)),
[data-js] .y-prose .mermaid-diagram[data-mermaid-error]:not(:has(svg)) {
  animation: none;
  background: var(--panel);
  color: var(--fg-subtle);
}
@keyframes y-shimmer {
  from {
    background-position: 120% 0;
  }

  to {
    background-position: -80% 0;
  }
}

/* ============================================================
   Right rail — TOC · status panel · diagnostics
   ============================================================ */
/* Inline reading aids — the stand-in for the right rail at widths where the
   rail is hidden: one closed native disclosure between the head and the first
   sentence, naming every aid the page carries, and the notes beside this one
   at the end of the text. Wide viewports show the rail and hide both (the
   media rule sits with the other width switches at the end of this sheet). */
.y-inlineaids,
.y-related {
  display: none;
}

/* One row on a forty-pixel pitch: the summary's own line and four pixels above
   and below it, a whole target for a finger and no more room than the row
   needs. A page with many aids wraps their names onto a second line rather
   than cutting one short, with the chevron kept beside the first. */
.y-toc-inline__summary {
  align-items: flex-start;
  color: var(--fg);
  display: flex;
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  font-weight: var(--fw-medium);
  gap: 7px;
  line-height: 2rem;
  padding: 4px 0;
}

.y-toc-inline__summary > .y-chevron {
  margin-top: calc((2rem - 11px) / 2);
}

/* Every name is led by its separator, and the list is pulled back by one
   separator's width and cut there, so the separator that would open a line —
   the first, and the first of a wrapped line — falls outside what is drawn.
   A line therefore never starts with a dot, wherever the row happens to wrap. */
.y-toc-inline__parts {
  --part-separator: 1.25rem;
  clip-path: inset(0 0 0 var(--part-separator));
  display: flex;
  flex-wrap: wrap;
  margin-inline-start: calc(-1 * var(--part-separator));
  min-width: 0;
}

.y-toc-inline__part {
  white-space: nowrap;
}

.y-toc-inline__part::before {
  color: var(--fg-subtle);
  content: '·' / '';
  display: inline-block;
  font-weight: var(--fw-regular);
  text-align: center;
  width: var(--part-separator);
}

.y-toc-inline__part .ui-navitem__count,
.y-toc-inline__part .y-diagcount {
  margin-left: 4px;
}

.y-toc-inline__body {
  display: flex;
  flex-direction: column;
  gap: 24px;
  padding: 4px 0 12px;
}

/* The two aids that wait at the end of the text are named in the summary, so
   the open row leads down to them rather than leaving a name with nothing
   under it. */
.y-toc-inline__jump {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 20px;
  margin: 0;
}

.y-toc-inline__jump a {
  color: var(--fg-subtle);
  font-family: var(--font-sans);
  font-size: var(--fs-13);
  line-height: 1.5rem;
}

.y-toc-inline__jump a:hover {
  color: var(--fg);
}

.y-related__block {
  scroll-margin-top: var(--header-clearance);
}

.y-toc__list {
  display: flex;
  flex-direction: column;
}

/* The contents list and the cited-by list are one anatomy: a hairline down the
   left edge, rows of 14px on a 24px line, and the row the reader is on picked
   out by a heavier ink rule over the hairline and the ink itself. The rule is
   drawn inside each row so a row's height never moves with its state.

   The row's own link is named at no weight beyond the row, so the entry the
   reader is on can repaint it with one class; a selector that counted the
   element as well outweighed that class and left the entry looking like every
   other one. */
.y-toc__row > :where(a:first-child) {
  border-radius: 0;
  box-shadow: inset 1px 0 0 var(--line);
  color: var(--fg-subtle);
  font-size: var(--fs-14);
  line-height: 1.5rem;
  padding: 4px 12px 4px 16px;
}

.y-toc__row > :where(a:first-child):hover {
  box-shadow: inset 1px 0 0 var(--line-strong);
  color: var(--fg);
}
/* In the reading aids only the contents' own name is a heading. Every other
   block's name — the note to read beside this one, the sources, the notes
   citing it, the note's health, and the status — is a label over a list, a
   step smaller and quieter, so the eye finds the contents first. The aids'
   links are one anatomy whatever list they are in: the quiet ink at rest and
   the full ink under a pointer, the same as the contents' own rows. */
:is(.y-rail-right, .y-toc-inline__body, .y-related, .y-sealbar) .ui-side__label {
  color: var(--fg-muted);
  font-size: var(--fs-13);
  font-weight: var(--fw-medium);
}

:is(.y-rail-right, .y-toc-inline__body, .y-related, .y-sealbar) .ui-side__label .ui-navitem__count {
  font-size: var(--fs-13);
}

.y-rail-right .ui-side__label:has(+ .y-toc__list) {
  color: var(--fg);
  font-size: var(--fs-14);
  font-weight: var(--fw-semibold);
}

:is(.y-rail-right, .y-toc-inline__body, .y-related) .ui-navitem {
  color: var(--fg-subtle);
}

:is(.y-rail-right, .y-toc-inline__body, .y-related) .ui-navitem:hover {
  color: var(--fg);
}

/* the scroll-spy's active entry (set by the enhancement script) */
.y-toc__list .is-active {
  box-shadow: inset 2px 0 0 var(--fg);
  color: var(--fg);
  font-weight: var(--fw-medium);
}
/* Indent follows the authored level the renderer stamps on each row, so a
   #### stays the deepest indent even though the shell writes it as h5. */
.y-toc__list a[data-level='3'] {
  padding-left: 28px;
}

.y-toc__list a[data-level='4'] {
  padding-left: 40px;
}

/* The section's own door shows under a pointer and takes no room until then:
   it sits over the end of the row's first line, on the ground colour so it
   covers whatever words run under it, and a title keeps the whole width to
   wrap in. A reserved strip at rest made every long title break one word
   early, for a control that was not on screen. */
.y-toc__row {
  align-items: stretch;
  display: flex;
  position: relative;
}

.y-toc__row > :where(a:first-child) {
  flex: 1;
  min-width: 0;
  overflow-wrap: anywhere;
}

.y-toc__list .y-toc__door {
  align-items: center;
  background: var(--bg);
  border-left: 0;
  color: var(--fg-subtle);
  display: flex;
  height: 2rem;
  inset-block-start: 0;
  inset-inline-end: 0;
  justify-content: center;
  margin-left: 0;
  opacity: 0;
  padding: 0;
  position: absolute;
  width: 28px;
}

.y-toc__door svg {
  height: 14px;
  width: 14px;
}

.y-toc__row:hover .y-toc__door,
.y-toc__row:focus-within .y-toc__door {
  opacity: 1;
}

.y-toc__list .y-toc__door:hover {
  color: var(--fg);
}

/* A touch screen has no pointer to reveal the door with, so there it is always
   drawn and keeps its own width beside the title. */
@media (pointer: coarse) {
  .y-toc__list .y-toc__door {
    flex: none;
    height: auto;
    opacity: 1;
    position: static;
    width: 44px;
  }
}

.y-statuspanel {
  border-top: 1px solid var(--line);
  padding-top: 20px;
}

.y-statuspanel__head {
  padding: 0;
}

.y-statuspanel__row {
  align-items: center;
  display: flex;
  justify-content: space-between;
  margin-bottom: 8px;
}

.y-statuspanel__forms {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 16px 0 0;
}

.y-statuspanel__notice {
  color: var(--fg-subtle);
  font-family: var(--font-serif);
  font-size: var(--fs-13);
  line-height: 1.6;
  margin: 0;
  padding: 12px 0 0;
}

.y-statusform {
  margin: 0;
}

/* one plain transition control; every legal target renders identically */
.y-xbtn {
  background: transparent;
  border: 0;
  border-radius: var(--r-md);
  box-shadow: inset 0 0 0 1px var(--line);
  color: var(--fg-muted);
  cursor: pointer;
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  font-weight: var(--fw-medium);
  height: 32px;
  padding: 0 12px;
  white-space: nowrap;
}

.y-xbtn:hover {
  background: var(--wash);
  color: var(--fg);
}

/* terminal-target confirm — the native disclosure that keeps the submit one
   interaction away; its summary wears the plain control's face */
.y-statusconfirm {
  min-width: 0;
}

/* The confirmation reads as a sentence and a button, so once it is open its
   form takes the whole row rather than sitting among the others. */
.y-statusform:has(.y-statusconfirm[open]) {
  flex-basis: 100%;
}

/* Open, the confirmation takes its row, and its summary stays the size of the
   control a reader pressed rather than stretching across the row with it. */
.y-statusconfirm__summary {
  align-items: center;
  display: flex;
  justify-content: center;
  list-style: none;
  -webkit-user-select: none;
  user-select: none;
  width: fit-content;
}

.y-statusconfirm__summary::marker {
  content: none;
}

.y-statusconfirm[open] .y-statusconfirm__summary {
  background: var(--overlay);
  color: var(--fg);
}

.y-statusconfirm__note {
  color: var(--fg-muted);
  font-family: var(--font-serif);
  font-size: var(--fs-13);
  line-height: 1.6;
  margin: 8px 0;
}

.y-statusconfirm__submit {
  border-color: var(--warn);
  color: var(--fg);
}

.y-sealbar .y-statusconfirm {
  width: auto;
}

/* diagnostics rail */
.y-diaghead {
  align-items: center;
  display: flex;
  gap: 8px;
}

.y-diagcount {
  background: var(--warn-bg);
  border-radius: var(--r-sm);
  color: var(--warn);
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  font-variant-numeric: tabular-nums;
  font-weight: var(--fw-semibold);
  line-height: 1.25rem;
  padding: 0 6px;
}

.y-diaglist {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 12px;
}

.y-diag {
  background: var(--warn-bg);
  border-radius: var(--r-lg);
  padding: 12px 14px;
}

.y-diag__label {
  align-items: center;
  color: var(--warn);
  display: flex;
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  font-weight: var(--fw-semibold);
  gap: 7px;
  line-height: 1.25rem;
  margin-bottom: 4px;
}

.y-diag__label svg {
  flex: none;
  height: 13px;
  width: 13px;
}

/* The card says what is wrong in the interface's face, small; only the address
   a link was written to is set in the machine face, because that is the part a
   reader copies to find the link in the note. */
.y-diag__body {
  color: var(--fg-muted);
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  line-height: 1.125rem;
  margin: 0;
}

.y-diag__body code {
  color: var(--fg);
  font-family: var(--font-sans);
  font-size: inherit;
}

.y-diag__body .y-diag__target {
  font-family: var(--font-mono);
}
/* how a link naming a section was read, stated under the diagnostic it
   belongs to. It is a second paragraph rather than more of the first because
   the first says what failed and this says how the words were split, and a
   reader scanning for one should not have to read past the other. */
.y-diag__split {
  color: var(--fg-muted);
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  line-height: 1.125rem;
  margin: 7px 0 0;
}

.y-diag__split code {
  color: var(--fg);
  font-family: var(--font-mono);
  font-size: var(--fs-12);
}

.y-diag__note {
  color: var(--fg-faint);
  font-family: var(--font-serif);
  font-size: var(--fs-12);
  font-style: italic;
  line-height: 1.6;
  margin: 2px 0 0;
}
/* out-of-enum status flag — the diagnostics rail's amber accent applied to a
   status-face notice, so the flag reads as a finding rather than as copy */
.y-statusflag {
  border-left: 2.5px solid var(--warn);
}

.y-statusflag code {
  color: var(--fg);
  font-family: var(--font-mono);
  font-size: var(--fs-12);
}

/* ============================================================
   Status bar — the status face at the end of the article wherever the right
   rail is absent: narrow viewports hide the rail, and no-aid notes drop its
   column. The write face belongs after reading, not pinned to the viewport
   bottom. The y-sealbar class names are kept; the layout predates the bar
   being a plain control surface.
   ============================================================ */
.y-sealbar {
  align-items: center;
  border-top: 1px solid var(--line);
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 32px;
  padding: 16px 0 0;
}
@media (min-width: 1280px) {
  .y-shell:not(.y-shell--rail-empty) .y-sealbar {
    display: none;
  }
}

.y-sealbar__spacer {
  flex: 1;
}

.y-sealbar .y-xbtn {
  padding-inline: 14px;
  width: auto;
}

.y-sealbar__notice {
  color: var(--fg-subtle);
  font-family: var(--font-serif);
  font-size: var(--fs-13);
  line-height: 1.6;
  margin: 0;
}

/* ============================================================
   The search field — one shape wherever it is asked for
   ============================================================ */
/* Three places ask a reader to type a search: the desk, the search page, and
   the palette the keyboard opens. They are one field, so the height, the
   corner, the type and the icon are written once here and each place adds
   only the ground it sits on.

   The resting line is an outline drawn inward rather than a border, so focus
   can thicken it from one pixel to two and colour it without a single glyph
   moving. That is the whole of the answer: the fields autofocus, and the
   page-wide ring firing on every open is news the reader did not ask for, so
   the ring stays off the input and the field around it carries the state —
   the same outline every other control answers focus with, drawn where this
   one already has a line. */
.y-homesearch,
.y-searchdialog__form,
.y-searchpage__form {
  align-items: center;
  border-radius: var(--r-sm);
  display: flex;
  gap: 10px;
  height: 44px;
  outline: 1px solid var(--border);
  outline-offset: -1px;
  padding: 0 12px;
}

.y-homesearch:focus-within,
.y-searchdialog__form:focus-within,
.y-searchpage__form:focus-within {
  outline: 2px solid var(--fg-subtle);
  outline-offset: -2px;
}

/* A phone zooms the page when it focuses an input set below its own body
   size, which moves the whole layout to read one word. Sixteen pixels is
   where that stops, and it is the size the typed query deserves anyway. */
.y-homesearch input,
.y-searchdialog__input,
.y-searchpage__form input {
  background: transparent;
  border: 0;
  color: var(--fg);
  flex: 1;
  font-family: var(--font-sans);
  font-size: var(--fs-16);
  min-width: 0;
  outline: 0;
}

.y-homesearch input:focus-visible,
.y-searchdialog__input:focus-visible,
.y-searchpage__form input:focus-visible {
  outline: 0;
}

/* The browser's own clear control is drawn in the system's accent colour, which
   is a blue nothing else in this interface uses. It keeps its place and its
   behaviour; only the mark is redrawn, as a cross in the subtle ink. */
.y-homesearch input::-webkit-search-cancel-button,
.y-searchdialog__input::-webkit-search-cancel-button,
.y-searchpage__form input::-webkit-search-cancel-button {
  appearance: none;
  background: linear-gradient(45deg, transparent 43%, var(--fg-subtle) 43% 57%, transparent 57%), linear-gradient(-45deg, transparent 43%, var(--fg-subtle) 43% 57%, transparent 57%);
  cursor: pointer;
  height: 14px;
  width: 14px;
}

.y-homesearch input::-webkit-search-cancel-button:hover,
.y-searchdialog__input::-webkit-search-cancel-button:hover,
.y-searchpage__form input::-webkit-search-cancel-button:hover {
  background: linear-gradient(45deg, transparent 43%, var(--fg) 43% 57%, transparent 57%), linear-gradient(-45deg, transparent 43%, var(--fg) 43% 57%, transparent 57%);
}

/* One glyph size for every way into search, the header's entry included. */
.y-homesearch svg,
.y-searchbtn svg,
.y-searchdialog__form svg,
.y-searchpage__form svg {
  flex: none;
  height: 16px;
  width: 16px;
}

/* The header's entry takes its ink from the control around it, which is why
   it is not in this list. */
.y-homesearch svg,
.y-searchdialog__form svg,
.y-searchpage__form svg {
  color: var(--fg-subtle);
}

.y-homesearch .y-xbtn,
.y-searchpage__form .y-xbtn {
  flex: none;
  height: 32px;
  padding: 0 16px;
  width: auto;
}

/* ============================================================
   ⌘K search dialog (native <dialog>)
   ============================================================ */
.y-searchdialog {
  border: none;
  margin: 12vh auto auto;
  max-width: 600px;
  padding: 0;
  width: min(600px, 92vw);
}

.y-searchdialog::backdrop {
  background: var(--overlay-mask);
}
/* In the palette the field is the panel's head rather than a box inside it:
   it spans the panel, and one hairline under it parts the question from the
   answers. It is the only field there and it opens focused, so it draws no
   ring; a reader who has moved focus elsewhere and back still sees where it
   is, by the hairline under it turning to the line every focused field
   draws, which colours the edge without moving a glyph. */
.y-searchdialog__form,
.y-searchdialog__form:focus-within {
  border-radius: 0;
  box-shadow: inset 0 -1px 0 var(--line);
  height: 52px;
  margin: 0;
  outline: 0;
  padding: 0 18px;
}

.y-searchdialog__form:focus-within {
  box-shadow: inset 0 -2px 0 var(--fg-subtle);
}

.y-searchdialog .y-live-search__status {
  margin-inline: 18px;
}
/* The fragment still prints a count so /search names how many it found without
   a script. The palette already announces that number in the live status, so
   a second line here is the same fact in a second voice. */
.y-searchdialog .y-results__count {
  display: none;
}

.y-searchdialog .y-searchresults {
  max-height: min(52vh, 480px);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 0 6px 12px;
}

.y-searchdialog .y-results {
  gap: 2px;
  margin-top: 10px;
}
/* One step in from the page's row: the panel is a glance rather than a place
   to read, and a shorter row puts more of the answer inside it. The row a
   pointer is over, the one the keyboard has reached, and the one chosen with
   the arrow keys all stand on the same faint wash, so the three ways of
   pointing at a hit look like one. */
.y-searchdialog .y-result {
  border-bottom: 0;
  padding: 12px;
}

.y-searchdialog .y-result:hover {
  box-shadow: none;
}

.y-searchdialog .y-result:hover,
.y-searchdialog .y-result:focus-visible,
.y-searchdialog .y-result[aria-selected='true'] {
  background: var(--wash);
}

.y-searchdialog .y-searchpage__empty {
  margin: 18px 12px 8px;
}

.y-searchdialog__foot {
  align-items: center;
  background: var(--elevated);
  color: var(--fg-faint);
  display: flex;
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  gap: 14px;
  padding: 10px 18px;
}

/* ============================================================
   Lexical search results — shared by the full page and ⌘K panel
   ============================================================ */
/* ============================================================
   The answer shell
   ============================================================ */
/* What a search comes back as. The results are read as prose and take a
   measure of their own, and at the widths where there is room the divisions
   the answer is made of stand in a narrow column beside them rather than
   above. The column and the space before it are named here once: the page's
   own width is the measure plus both of them, so the two figures cannot be
   stated in one place and forgotten in the other. */
.y-searchpage {
  --facet-column: 192px;
  --facet-gap: 28px;
  margin: 0 auto;
  max-width: calc(var(--measure-answer) + 2 * var(--gutter-answer));
  padding: 40px var(--gutter-answer) 96px;
}

/* The page names itself, then offers the field, then answers: three steps a
   measured distance apart, so the count is not read as part of the field's
   frame and the field is not read as the title's underline. */
.y-searchpage > .y-title {
  margin-bottom: 24px;
}


/* The divisions beside the results. The page's own column is a reading
   measure, so the answer and what it is made of are laid out as a pair rather
   than by squeezing one into the other: the results keep the measure and the
   divisions take a narrow column of their own.

   The column is a <details> in the markup and stays one here at the widths
   where there is no room for a second column — a list of values standing above
   the results would push them off the first screen of a phone. Where the pair
   fits, the fold is held open and the control that closes it is taken away,
   since a fold nobody can close is a heading with a triangle beside it. Both
   halves of that are inside @supports: an engine that cannot hold the fold open
   keeps its summary, and the reader opens the column themselves, rather than
   being left with a region they can neither see nor reach. */
.y-facets {
  margin: 20px 0 0;
}

.y-facets__summary {
  align-items: center;
  color: var(--fg-muted);
  display: flex;
  font-size: var(--fs-13);
  gap: 6px;
  padding: 6px 4px;
}

.y-facets__body {
  display: flex;
  flex-direction: column;
  gap: 18px;
  padding: 6px 4px 0;
}

.y-facetgroup__head {
  color: var(--fg-muted);
  font-family: var(--font-sans);
  font-size: var(--fs-13);
  font-weight: var(--fw-medium);
  margin: 0 0 4px;
}

.y-facetlist {
  display: flex;
  flex-direction: column;
  gap: 1px;
  list-style: none;
  margin: 0;
  padding: 0;
}

.y-facetrow {
  align-items: baseline;
  border-radius: var(--r-sm);
  color: var(--fg-muted);
  display: flex;
  font-size: var(--fs-13);
  gap: 10px;
  padding: 4px 6px;
}
/* stylelint-disable-next-line selector-no-qualifying-type -- an empty facet
   query renders this class on a span, not an anchor, so it has nothing to
   hover; the type keeps the highlight on the row that is actually clickable. */
a.y-facetrow:hover {
  background: var(--overlay);
  color: var(--fg);
}

.y-facetrow__label {
  flex: 1;
  min-width: 0;
  overflow-wrap: anywhere;
}
/* The count is machinery, so it is set in the machinery's face, and it is
   aligned on its own edge so a column of them reads as a column. */
.y-facetrow__count {
  color: var(--fg-faint);
  flex: none;
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  font-variant-numeric: tabular-nums;
}
/* A value already in the query leads back out of it, so it is marked as
   standing rather than as somewhere to go. */
.y-facetrow--active {
  color: var(--fg);
  font-weight: var(--fw-medium);
}

.y-facetrow--active .y-facetrow__label::before {
  color: var(--fg);
  content: '\2713\00a0';
}
/* A row with nothing to ask for is a cell, and reads as one. */
.y-facetrow--quiet {
  color: var(--fg-faint);
}

/* The pair appears only where both halves can have their own width. The
   results keep the measure they have always had — the column is added beside
   the page rather than taken out of it, so the page grows by exactly the
   column and the gap and a line of prose is the same length it was. Below this
   the page is its old self with a fold above the results. */
@media (width >= 1280px) {
  .y-searchpage {
    max-width: calc(var(--measure-answer) + var(--facet-gap) + var(--facet-column) + 2 * var(--gutter-answer));
  }

  .y-searchpage .y-searchresults {
    align-items: start;
    display: grid;
    gap: var(--facet-gap);
    grid-template-columns: minmax(0, 1fr) var(--facet-column);
  }

  .y-searchpage .y-facets {
    margin: 0;
    order: 2;
    position: sticky;
    top: 24px;
  }

  .y-searchpage .y-searchresults__body {
    min-width: 0;
    order: 1;
  }
  @supports selector(::details-content) {
    .y-searchpage .y-facets > summary {
      display: none;
    }

    .y-searchpage .y-facets::details-content {
      block-size: auto;
      content-visibility: visible;
    }
  }
}
/* The palette asks the same route for the same rows and has no column to put a
   division in, so it never asks for one; nothing here paints one out. */
.y-searchpage__form {
  background: var(--panel);
  margin-bottom: 16px;
}
/* The count line prints the query back, and a query can be one run with
   nowhere to break — the same thing that used to carry the notice below it off
   the side of a phone. It takes the same last-resort break, or a reader on a
   phone is told how many results they have by a line that runs past the edge
   of the box. */
.y-live-search__status {
  color: var(--fg-muted);
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  margin: 8px 4px 0;
  min-height: 1.4em;
  overflow-wrap: anywhere;
}

.y-live-search__status:empty {
  margin-block: 0;
  min-height: 0;
}
/* Live search keeps the last answer on screen while the next one loads.
   Fading the region would fade every word with it — already-compliant ink
   compositing down past AA at the one moment a reader is waiting to read.
   The busy signal sits on the container: a quieter ground, a line, and a
   spinner. The ink stays opaque. */
.y-searchresults[aria-busy='true'] {
  background: var(--elevated);
  box-shadow: inset 0 0 0 1px var(--border);
  position: relative;
}

.y-searchresults[aria-busy='true']::after {
  animation: y-search-spin 0.7s linear infinite;
  border: 1.5px solid var(--border-strong);
  border-radius: 50%;
  border-top-color: var(--fg-subtle);
  content: '';
  height: 12px;
  pointer-events: none;
  position: absolute;
  right: 12px;
  top: 10px;
  width: 12px;
}
@keyframes y-search-spin {
  to {
    transform: rotate(1turn);
  }
}

.y-searchpage__empty {
  color: var(--fg-subtle);
  font-family: var(--font-serif);
  margin: 28px 4px;
}

.y-searchpage__empty p {
  margin: 0;
}

.y-searchpage__emptytitle {
  color: var(--fg);
  font-family: var(--font-serif);
  font-size: var(--fs-ed-20);
  font-weight: var(--fw-regular);
  margin: 0;
}

.y-searchpage__emptylede {
  margin-top: 6px;
}
/* The grammar sits under the sentence, quieter than it. */
.y-searchpage__empty .y-searchpage__hint {
  color: var(--fg-faint);
  font-size: var(--fs-12);
  margin-top: 14px;
}
/* An answer is a shelf of its own, so its rows are the shelf's rows: a hairline
   between them, the same inset, and nothing drawn around any one of them. What
   a list of findings needs is a place for the eye to stop between one and the
   next, and a line does that where two pixels of air did not — twenty-odd hits
   ran together. The row is taller than a shelf row because it carries a
   sentence under its title as well as a path. */
.y-results {
  display: flex;
  flex-direction: column;
  margin: 20px 0 0;
}

/* A hit's words start under the field's own edge, and the wash a pointer
   brings reaches past it, as a shelf row's does. */
.y-result {
  border-bottom: 1px solid var(--border-faint);
  border-radius: var(--r-md);
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 16px 0;
}

.y-result__title {
  color: var(--fg);
  font-family: var(--font-serif);
  font-size: var(--fs-ed-17);
  transition: color var(--dur-base) var(--ease-standard);
}

.y-result__meta {
  color: var(--fg-faint);
  font-family: var(--font-mono);
  font-size: var(--fs-12);
  overflow-wrap: anywhere;
}

.y-result__sep {
  margin: 0 6px;
}

.y-result__kind {
  color: var(--fg-faint);
  font-family: var(--font-sans);
  font-size: var(--fs-12);
}
/* a status the contract does not declare — worn by a search hit, a landing-page
   row, and the distribution chip alike, so one fault reads as one thing
   wherever it is met. The words inside carry the meaning; the tint only makes
   them findable while a reader scans a list that otherwise looks uniform. It
   sits inline so it wraps with the line it belongs to rather than pushing the
   row wider than its neighbours. */
.y-outofenum {
  background: var(--warn-bg);
  border-radius: 3px;
  color: var(--warn);
  display: inline-block;
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  line-height: 1.75;
  margin-left: 8px;
  padding: 0 6px;
  white-space: nowrap;
}
/* Half the snippets on this page are Chinese, and Chinese prose needs the
   reading leading rather than the interface's. */
.y-result__snippet {
  color: var(--fg-subtle);
  font-family: var(--font-serif);
  font-size: var(--fs-13);
  line-height: var(--lh-read);
  overflow-wrap: anywhere;
}

/* ============================================================
   Ruby (furigana) — rt hidden by visibility only → zero reflow
   ============================================================ */
.yomihon ruby {
  ruby-align: center;
  ruby-position: over;
}
/* The reading is what a learner is here to read, so it is not allowed to be
   both the smallest and the faintest thing on the page. It takes the ink the
   prose beside it takes — a reading fainter than the word it explains asks the
   reader to work hardest on the part they know least — and the face the word
   under it is set in, so the two read as one line of type. It is set in em, so
   it grows with the reader's own type choice instead of needing a rule per
   step. */
.yomihon rt {
  color: var(--fg-muted);
  font-family: var(--font-read);
  font-size: 0.55em;
  font-weight: 400;
  -webkit-user-select: none;
  user-select: none;
}

[data-ruby='off'] rt:not([data-keep='1']) {
  visibility: hidden;
}

/* ============================================================
   TTS — read-aloud speak button on lesson sentences.
   Server-rendered (render.InjectTTS) with reading-stripped data-tts; the
   button hangs in the left gutter and is revealed only once the lesson module
   confirms a Web Speech engine (root[data-speech]), so with no JS or no
   engine the sentence still reads and the affordance is simply absent.
   ============================================================ */
.y-prose .y-reading {
  margin: var(--sp-block) 0 0;
  position: relative;
}

.y-prose .y-reading > p {
  margin: 0;
}
/* The paragraph the voice is on. A colour and nothing else: the blanket that
   switches every transition and animation off for a reduced-motion reader must
   leave this readable, and the spread shadow gives the colour room without
   moving a single line of the note. */
.y-prose .y-reading[data-reading] {
  background: var(--accent-muted);
  border-radius: var(--r-sm);
  box-shadow: 0 0 0 5px var(--accent-muted);
}

.y-tts {
  align-items: center;
  background: none;
  border: none;
  border-radius: var(--r-sm);
  color: var(--fg-faint);
  cursor: pointer;
  display: none;
  justify-content: center;
  left: -30px;
  min-height: 24px;
  min-width: 24px;
  padding: 0;
  position: absolute;
  top: 0.35em;
  transition: color var(--dur-base) var(--ease-standard), background var(--dur-base) var(--ease-standard);
}

[data-speech] .y-tts {
  display: inline-flex;
}

.y-tts:hover {
  background: var(--overlay);
  color: var(--fg);
}

.y-tts[data-speaking] {
  background: var(--accent-muted);
  color: var(--accent);
}

.y-tts svg {
  height: 15px;
  width: 15px;
}
/* A list item's speak button is its first child (a <li> can't be gutter-wrapped
   without breaking list semantics), so it sits inline before the sentence. */
.y-prose li > .y-tts {
  margin: 0 5px 0 0;
  min-height: 24px;
  min-width: 24px;
  position: static;
  vertical-align: -0.15em;
}

.y-prose li > .y-tts svg {
  height: 14px;
  width: 14px;
}
/* Narrow screens have no gutter — the paragraph button sits inline too. */
@media (max-width: 720px) {
  .y-tts {
    margin-right: 6px;
    position: static;
    vertical-align: -0.15em;
  }
}

/* The read-aloud controls are a line of the page rather than a panel on it:
   the moves are ghost buttons like the header's, and the speeds one segmented
   group inside a single hairline, the chosen one on the faint wash in the
   full ink. A frame round the whole bar made four verbs and a setting read as
   a notice. */
.y-ttsbar {
  align-items: center;
  color: var(--fg-muted);
  display: flex;
  flex-wrap: wrap;
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  gap: 4px;
  margin: 22px 0 8px;
}

.y-ttsbar__label {
  color: var(--fg-subtle);
  font-size: var(--fs-13);
  margin: 0 6px 0 12px;
}

.y-ttsbar button {
  background: transparent;
  border: 0;
  border-radius: var(--r-md);
  color: var(--fg-muted);
  cursor: pointer;
  font-size: var(--fs-14);
  font-weight: var(--fw-medium);
  min-height: 32px;
  padding: 0 10px;
}

.y-ttsbar button:hover:not(:disabled) {
  background: var(--wash);
  color: var(--fg);
}

.y-ttsbar button[data-speech-rate] {
  border-radius: 0;
  box-shadow: inset 0 1px 0 var(--line), inset 0 -1px 0 var(--line);
  font-size: var(--fs-13);
  min-height: 30px;
  padding: 0 9px;
}

.y-ttsbar__label + button[data-speech-rate] {
  border-end-start-radius: var(--r-md);
  border-start-start-radius: var(--r-md);
  box-shadow: inset 1px 0 0 var(--line), inset 0 1px 0 var(--line), inset 0 -1px 0 var(--line);
}

.y-ttsbar button[data-speech-rate] + button[data-speech-rate] {
  margin-inline-start: -4px;
}

.y-ttsbar button[data-speech-rate]:not(:has(+ button[data-speech-rate])) {
  border-end-end-radius: var(--r-md);
  border-start-end-radius: var(--r-md);
  box-shadow: inset -1px 0 0 var(--line), inset 0 1px 0 var(--line), inset 0 -1px 0 var(--line);
}

.y-ttsbar button[aria-pressed='true'] {
  background: var(--wash);
  color: var(--fg);
}
/* Previous on the first paragraph and next on the last: the move is not there,
   and a control that looks live while refusing every press is worse than one
   that says so. */
.y-ttsbar button:disabled {
  color: var(--fg-faint);
  cursor: default;
}

.y-ttsbar__status {
  color: var(--fg-subtle);
  margin-left: auto;
}
/* What speech synthesis cannot be asked for, on its own line under the
   controls it explains the absence of. */
.y-ttsbar__limits {
  color: var(--fg-subtle);
  flex-basis: 100%;
  margin: 2px 0 0;
}

/* ============================================================
   Slot machine — interactive sentence-pattern practice.
   Server-rendered on the first fill so it reads with JS off; the controls
   (selects, shuffle) show only under [data-js] and the speak button only
   under [data-speech], so no-JS / no-engine pages hide dead controls.
   ============================================================ */
.y-slotmachine {
  margin: 40px 0 0;
}

.y-slotmachine__intro {
  margin: 0 0 4px;
}

/* The practice opens a section of the lesson, so its name is set as the
   lesson's own sections are. */
.y-slotmachine__title {
  color: var(--fg);
  font-family: var(--font-read);
  font-size: var(--fs-ed-24);
  font-weight: var(--fw-semibold);
  line-height: 1.3333;
  margin: 0;
  text-wrap: balance;
}

.y-slotmachine__title:lang(ja),
.y-slotmachine__title:lang(ko),
.y-slotmachine__title:lang(zh) {
  line-height: 1.4;
}

.y-slotmachine__sub {
  color: var(--fg-subtle);
  font-family: var(--font-sans);
  font-size: var(--fs-13);
  margin: 4px 0 0;
}

.y-slotcard {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  margin: 14px 0 0;
  padding: 18px 20px;
}

.y-slotcard__head {
  align-items: baseline;
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

.y-slotcard__abstract {
  color: var(--fg);
  font-family: var(--font-sans);
  font-size: var(--fs-ed-17);
  font-weight: var(--fw-semibold);
}

.y-slotcard__note {
  color: var(--fg-subtle);
  font-family: var(--font-sans);
  font-size: var(--fs-12);
}

.y-slotcontrols {
  display: none;
  flex-wrap: wrap;
  gap: 12px;
  margin: 14px 0 0;
}

[data-js] .y-slotcontrols {
  display: flex;
}

.y-slotfield {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* A slot's grammatical role is told by a small square of its colour beside
   its name, the same square the role's words are tinted with below, rather
   than by a coloured edge on the control that read as a warning bar. */
.y-slotfield__label {
  align-items: center;
  color: var(--fg-subtle);
  display: inline-flex;
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  gap: 6px;
}

.y-slotfield__label::before {
  background: var(--c, var(--border-strong));
  border-radius: 2px;
  content: '';
  flex: none;
  height: 8px;
  width: 8px;
}

.y-slotselect {
  background: var(--elevated);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  color: var(--fg);
  cursor: pointer;
  font-family: var(--font-serif);
  font-size: var(--fs-14);
  padding: 6px 10px;
}
/* Where the customizable-select API is present, the closed control keeps the
   native styling above and the picker it opens is branded to match. Opt both
   the control and its picker in — one without the other leaves the picker
   native. base-select routes the closed face through the inner button, which
   carries no box of its own, so the surface, border, and coloured edge above
   still draw the face unchanged. base-select also stops sizing the control to
   its longest option — it now fits the current selection — so a floor width
   keeps the controls row from shifting as fills of different lengths appear. */
@supports (appearance: base-select) {
  .y-slotselect,
  .y-slotselect::picker(select) {
    appearance: base-select;
  }

  .y-slotselect {
    min-width: 12rem;
  }

  .y-slotselect button {
    background: none;
    border: 0;
    color: inherit;
    cursor: inherit;
    font: inherit;
    padding: 0;
  }

  .y-slotselect::picker-icon {
    color: var(--fg-subtle);
  }

  .y-slotselect::picker(select) {
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: var(--r-md);
    box-shadow: var(--shadow-2);
    font-family: var(--font-serif);
    padding: 4px;
  }

  .y-slotselect option {
    border-radius: var(--r-sm);
    color: var(--fg);
    cursor: pointer;
    padding: 6px 10px;
  }

  .y-slotselect option:hover {
    background: var(--elevated);
  }

  .y-slotselect option:checked {
    background: color-mix(in oklch, var(--c) 10%, transparent);
  }

  .y-slotselect option::checkmark {
    color: var(--c);
  }
}

.y-slotoutput {
  color: var(--fg);
  font-family: var(--font-read);
  font-size: var(--fs-ed-24);
  line-height: var(--lh-cjk);
  margin: 18px 0 0;
}

.y-slotout {
  background: color-mix(in oklch, var(--c) 12%, transparent);
  border-radius: 4px;
  color: var(--c);
  padding: 0 3px;
}

.y-slotout rt {
  color: var(--c);
}

.y-slotgloss {
  color: var(--fg-muted);
  font-family: var(--font-serif);
  font-size: var(--fs-14);
  margin: 8px 0 0;
}

.y-slotgloss::before {
  color: var(--fg-faint);
  content: '→ ';
}

.y-slotactions {
  display: none;
  gap: 8px;
  margin: 14px 0 0;
}

[data-js] .y-slotactions {
  display: flex;
}

/* The two actions are the interface's own icon buttons; the class beside it
   only says which of them waits for a voice to speak with. */
.y-slotbtn[data-slot-action='speak'] {
  display: none;
}

[data-speech] .y-slotbtn[data-slot-action='speak'] {
  display: inline-flex;
}

/* The 8 grammatical-role colours: each class only assigns --c (a theme token),
   so one token tints a slot's swatch, its surface, and its ruby. */
.y-slot-topic {
  --c: var(--slot-topic);
}

.y-slot-pred {
  --c: var(--slot-pred);
}

.y-slot-poss {
  --c: var(--slot-poss);
}

.y-slot-obj {
  --c: var(--slot-obj);
}

.y-slot-place {
  --c: var(--slot-place);
}

.y-slot-time {
  --c: var(--slot-time);
}

.y-slot-verb {
  --c: var(--slot-verb);
}

.y-slot-num {
  --c: var(--slot-num);
}

/* A concept-link is a real wikilink that also opens the sheet; mark it with a
   dotted underline so the reader knows a grammar note is one click away. With
   JS off it is still the plain wikilink that navigates to the note. */
.y-prose .concept-link {
  border-bottom: 1px dashed var(--line-strong);
  cursor: help;
  text-underline-offset: 2px;
}

.y-prose .concept-link:hover {
  border-bottom-color: var(--fg);
}

/* ============================================================
   Concept sheet — a grammar note in a native <dialog> drawer.
   Held against the right edge on wide screens, against the bottom on narrow;
   how it arrives is written with the other top-layer surfaces further down.
   Only present when a lesson references a concept (rendered conditionally).
   ============================================================ */
.y-conceptsheet {
  background: var(--panel);
  border: none;
  border-left: 1px solid var(--border);
  box-shadow: var(--shadow-2);
  box-sizing: border-box;
  height: 100dvh;
  margin: 0 0 0 auto;
  max-height: 100dvh;
  padding: 0;
  width: min(460px, 92vw);
}
/* The flex layout applies only while the dialog is open: an unconditional
   display would defeat the browser's own hiding of a closed dialog and
   paint the sheet over the right rail. */
.y-conceptsheet[open] {
  display: flex;
  flex-direction: column;
}

.y-conceptsheet::backdrop {
  background: var(--overlay-mask);
}

.y-conceptsheet__head {
  align-items: center;
  border-bottom: 1px solid var(--border);
  display: flex;
  flex: none;
  gap: 12px;
  justify-content: space-between;
  padding: 16px 20px;
}

.y-conceptsheet__title {
  color: var(--fg);
  font-family: var(--font-sans);
  font-size: var(--fs-ed-17);
  font-weight: var(--fw-semibold);
}

.y-conceptsheet__body {
  overflow-y: auto;
  padding: 18px 20px;
}
@media (max-width: 720px) {
  .y-conceptsheet {
    border-left: none;
    border-radius: var(--r-lg) var(--r-lg) 0 0;
    border-top: 1px solid var(--border);
    height: auto;
    margin: auto 0 0;
    max-height: 82dvh;
    width: 100vw;
  }
}

/* Reduced motion kills decorative animation/transition — but not the
   reading-position hairline: it is scroll state, not decoration (its
   scroll timeline is not driven by durations anyway, but the exemption
   is explicit — WCAG exempts motion that conveys essential state). */
/* stylelint-disable declaration-no-important -- this rule answers a reader's
   own platform preference against every duration and easing already set,
   wherever it was set and at whatever specificity; !important is the only
   way it can be sure it wins against all of them at once. */
@media (prefers-reduced-motion: reduce) {
  .yomihon *:not(.y-readline),
  .yomihon *::before,
  .yomihon *::after {
    animation-duration: 0.001ms !important;
    transition-duration: 0.001ms !important;
  }
}
/* stylelint-enable declaration-no-important */

/* ============================================================
   Syllabus page (study-path) — a full-page render of nav.Map.
   Two-column shell (no right rail). Reuses ui-status / ui-navitem /
   y-metarow / y-navdot; warning markers reuse y-navmark--warn.
   The page says how long a course is and where in it the reader is
   standing; it has no figure for how much of one is behind them.
   ============================================================ */
.y-shell2 {
  align-items: start;
  display: grid;
  flex: 1;
  grid-template-columns: var(--rail-open) minmax(0, 1fr);
  width: 100%;
}

/* A page with no rail has the one column to fill. Left on the two tracks, its
   main would land in the track the rail would have had and be read at the width
   of a sidebar. With no rail to put against the window's edge it is the one
   thing on the page, and it stands in the middle of the window rather than
   running out to its sides. */
.y-shell2:not(:has(> .y-rail-left)) {
  grid-template-columns: minmax(0, 1fr);
  margin-inline: auto;
  max-width: var(--shell-max);
}

/* Home — the quiet reading desk, ending in a link to the vault README. The
   same shell dresses the four mode index pages, and the blocks here are what
   the folder index fills with its recent list and status distribution. The
   block markers live in markup for the HTTP probe; layout may move without
   changing their identity. Its column is the shelf shell's, declared with the
   other pages that list rather than read. */
.y-home__head {
  margin-bottom: 26px;
}

.y-home__head .y-title {
  margin-top: 10px;
}

.y-home__head > p {
  color: var(--fg-subtle);
  font-family: var(--font-serif);
  font-size: var(--fs-ed-17);
  margin: 10px 0 0;
  max-width: 600px;
}

/* The desk's shelves are sections of one page rather than cards laid on it:
   each opens on a hairline and its heading, two to a row with a wide gutter
   between, and nothing draws a box around a list a reader only scans. */
.y-homegrid {
  display: grid;
  gap: 40px 48px;
  grid-template-columns: repeat(12, minmax(0, 1fr));
}

/* The reader's return shelf precedes the four navigation modes as one row;
   those four keep their existing two-by-two organisation beneath it. */
.y-homegrid > [data-home-block='open-thoughts'] {
  grid-column: 1 / -1;
}

.y-homeblock {
  border-top: 1px solid var(--line);
  min-width: 0;
  padding: 20px 0 0;
}

.y-homeblock--recent {
  grid-column: span 7;
}

.y-homeblock--lifecycle {
  grid-column: span 5;
}
/* The four ways in sit two by two; the desk's own search field is not one of
   them and takes the width under them. */
.y-modeblock {
  grid-column: span 6;
}

.y-modeblock h2 a {
  color: inherit;
}

.y-modeblock h2 a:hover {
  color: var(--link);
}

/* A shelf's rows start where its heading does, and the wash a pointer brings
   reaches out past that edge, as a shelf page's rows do. */
.y-modeblock .ui-navitem {
  padding-inline: 0;
}

/* The way to the whole shelf is one more line under its rows, in the rows'
   own size and a step quieter, and only as wide as its words. */
.y-modeblock .y-shelfall {
  color: var(--fg-muted);
  display: inline-flex;
  font-size: var(--fs-14);
  margin-top: 4px;
}
/* The search row sits above the ways in, at the width of the page rather than
   as one more block beside them. */
.y-homesearchrow {
  display: block;
  margin: 0 0 22px;
}

/* The thought door shows the Markdown it composed in a field the reader can
   select from but not edit, set in the mono face at body size. */
.y-thought__field {
  margin-top: 0.5rem;
}

.y-thought__markdown {
  border: 1px solid;
  border-radius: var(--r-md);
  display: block;
  font-family: var(--font-mono);
  font-size: 1rem;
  line-height: 1.5;
  padding: 0.375rem 0.75rem;
  width: 100%;
}

.y-homeblock__lede {
  color: var(--fg-faint);
  font-family: var(--font-serif);
  font-size: var(--fs-13);
  font-style: italic;
  margin: -8px 0 12px;
}

.y-homeseamnote {
  color: var(--fg-faint);
  font-family: var(--font-serif);
  font-size: var(--fs-13);
  font-style: italic;
  margin: 16px 0 0;
}

.y-shelfblocks {
  margin-top: 30px;
}
/* A fault the whole page is subject to is full-width news, and it reads as a
   sentence rather than a column of items. Without a track of its own it auto-
   places into a single twelfth and the message renders as an unreadable
   one-character-wide tower. */
.y-homeblock--faults {
  grid-column: 1 / -1;
}

.y-homeblock__head {
  align-items: baseline;
  display: flex;
  gap: 10px;
  margin-bottom: 14px;
}
/* CJK min-content is one character, so a five-character title beside its lede
   orphans the last glyph before the description wraps. Keep the heading intact
   and let the description take the wrap. */
.y-homeblock__head h2 {
  color: var(--fg);
  flex: none;
  font-family: var(--font-display);
  font-size: var(--fs-16);
  font-weight: var(--fw-semibold);
  margin: 0;
  white-space: nowrap;
}

.y-homeblock__head p {
  color: var(--fg-faint);
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  margin: 0 0 0 auto;
  text-align: right;
}

.y-homeempty {
  color: var(--fg-faint);
  font-family: var(--font-serif);
  font-size: var(--fs-13);
  font-style: italic;
  margin: 4px 0 0;
}

.y-homerecent,
.y-homepaths {
  display: flex;
  flex-direction: column;
}

.y-homenote {
  align-items: center;
  border-top: 1px solid var(--border-faint);
  display: flex;
  gap: 16px;
  min-width: 0;
  padding: 10px 2px;
}

.y-homenote:first-child {
  border-top: 0;
}

.y-homenote:hover .y-homenote__title,
.y-homepath:hover .y-homepath__title {
  color: var(--link);
}

.y-homenote__main {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.y-homenote__title {
  color: var(--fg);
  font-family: var(--font-serif);
  font-size: var(--fs-14);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.y-homenote__path {
  color: var(--fg-faint);
  font-family: var(--font-mono);
  font-size: var(--fs-12);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.y-homenote__meta {
  align-items: center;
  display: flex;
  flex: none;
  gap: 9px;
}

.y-homenote__meta time {
  color: var(--fg-faint);
  font-family: var(--font-sans);
  font-size: var(--fs-12);
}

.y-homechips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.y-homechip {
  align-items: center;
  background: var(--elevated);
  border: 1px solid var(--border-faint);
  border-radius: var(--r-sm);
  color: var(--fg-muted);
  display: inline-flex;
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  gap: 7px;
  padding: 7px 9px;
}

.y-homechip:hover {
  border-color: var(--border-strong);
  color: var(--fg);
}
/* a status no carrying type declares — the note page's amber flag family */
.y-homechip--unknown {
  border-left: 2.5px solid var(--warn);
}

.y-homechip__count {
  color: var(--fg-faint);
  min-width: 1.35em;
  text-align: right;
}

.y-homepath {
  align-items: baseline;
  border-top: 1px solid var(--border-faint);
  display: flex;
  gap: 14px;
  padding: 11px 2px;
}

.y-homepath:first-child {
  border-top: 0;
}

.y-homepath__title {
  color: var(--fg);
  flex: 1;
  font-family: var(--font-serif);
  font-size: var(--fs-14);
  min-width: 0;
}

.y-homepath__progress {
  color: var(--fg-faint);
  flex: none;
  font-family: var(--font-sans);
  font-size: var(--fs-12);
}

.y-homesearch {
  background: var(--elevated);
  border-radius: var(--r-md);
}
/* The way to the folder's own README closes the desk. It is set in the reading
   measure, and it begins where everything stacked above it begins: centred, its
   rule and its one line both started a hundred pixels in from the shelves and
   read as a block belonging to some other page. */
.y-homereadme {
  border-top: 1px solid var(--border);
  margin: 58px 0 0;
  max-width: var(--measure-read);
  padding-top: 34px;
}

.y-homereadme__head {
  align-items: baseline;
  display: flex;
  gap: 14px;
  margin-bottom: 18px;
}

.y-homereadme__head h2 {
  color: var(--fg);
  font-family: var(--font-serif);
  font-size: var(--fs-ed-24);
  font-weight: var(--fw-regular);
  margin: 0;
}

@media (max-width: 1180px) {
  .y-homeblock--recent,
  .y-modeblock,
  .y-homeblock--lifecycle {
    grid-column: 1 / -1;
  }

  /* Every shelf spans the row here, so the gutter between twelve tracks is
     never seen, and eleven of them would hold the grid wider than a phone. */
  .y-homegrid {
    column-gap: 0;
  }
}

/* The four mode index pages — one kind of thing, listed whole. They carry no
   left rail: the rail is the thing being read, and a list beside a copy of
   itself is a second organisation of the same notes. The reading column is
   therefore the page's only column. */
.y-deskmain {
  flex: 1;
}

/* The way back to the desk is a line a reader can hit: its own words at the
   size of the links around it, a target as tall as a button, and the wash a
   pointer brings reaching past the words rather than moving them. */
.y-back {
  align-items: center;
  border-radius: var(--r-md);
  color: var(--fg-muted);
  display: inline-flex;
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  margin: 16px -10px 0;
  min-height: 32px;
  padding: 0 10px;
}

.y-back:hover {
  background: var(--wash);
  color: var(--fg);
}

.y-list {
  border-top: 1px solid var(--border);
}

/* A row's words start on the list's own edge, under the heading above it, and
   the wash a pointer brings reaches twelve pixels past that edge on either
   side rather than pushing the words in to make room for itself. The reach is
   painted, not laid out — the wash continued sideways as two shadows of
   itself — so it widens nothing, and the hairline between rows stays on the
   words' edge. */
:root {
  --bleed: 12px;
}

.y-row {
  align-items: baseline;
  border-bottom: 1px solid var(--border-faint);
  border-radius: var(--r-md);
  display: flex;
  gap: 18px;
  padding: 14px 0;
}

.y-row:hover,
.y-result:hover,
.y-modeblock .ui-navitem:hover {
  background: var(--wash);
  box-shadow: calc(-1 * var(--bleed)) 0 0 var(--wash), var(--bleed) 0 0 var(--wash);
}

.y-row__title {
  color: var(--fg);
  flex: 1;
  font-family: var(--font-serif);
  font-size: var(--fs-ed-17);
  min-width: 0;
  overflow-wrap: anywhere;
}

.y-row__measure {
  color: var(--fg-faint);
  flex: none;
  font-family: var(--font-sans);
  font-size: var(--fs-12);
}
/* A row reporting a hole in its own shelf. The colour has to be written here,
   after the measure it overrides: the shared warning colour is declared far
   above this and would lose to the faint one on equal specificity, which is
   how a fault came to be drawn in the colour of an ordinary figure. */
.y-row__measure--warn {
  color: var(--warn);
}

/* A row on a shelf that is read by date, as the journal is. The row leads with
   its day, then its name, and the line the document opens with and a mark at
   the far edge where it has them. The day and the mark hold the two edges of
   the top line with the name between them; the opening runs under the name,
   clipped to a single line, because a listing whose rows are two sentences
   deep has stopped being a listing. The hairline, the padding and the hover
   are the shelf's own and stay untouched. */
.y-row--dated {
  align-items: baseline;
  column-gap: 18px;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  row-gap: 3px;
}

.y-row__when {
  color: var(--fg-faint);
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  font-variant-numeric: tabular-nums;
  grid-column: 1;
  grid-row: 1;
  white-space: nowrap;
}

.y-row--dated .y-row__title {
  grid-column: 2;
  grid-row: 1;
}

.y-row--dated .y-row__measure {
  grid-column: 3;
  grid-row: 1;
}

.y-row__opening {
  color: var(--fg-muted);
  font-family: var(--font-serif);
  font-size: var(--fs-13);
  grid-column: 2;
  grid-row: 2;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* A phone has no room for three columns. The day and the mark keep the top
   line between them — the day still leads the row — and the name and the
   opening take the full width beneath, where a title has the space to wrap
   instead of being squeezed into a third of it. */
@media (max-width: 720px) {
  .y-row--dated {
    grid-template-columns: minmax(0, 1fr) auto;
  }

  .y-row--dated .y-row__title {
    grid-column: 1 / -1;
    grid-row: 2;
  }

  .y-row--dated .y-row__measure {
    grid-column: 2;
    grid-row: 1;
  }

  .y-row__opening {
    grid-column: 1 / -1;
    grid-row: 3;
  }
}

/* A row on a shelf read for what each document is called. A report is read for
   what it answers, so the row opens with its title; the day it is for and the
   kind it is, where it has one, hold the two fixed columns after it, and the
   opening runs under the title, clipped to a single line. The columns have a
   width of their own because the rows are separate grids: left to size
   themselves, the day would start somewhere different on every row according to
   how long the kind beside it is spelled, or whether it has one. The hairline,
   the padding and the hover are the shelf's own. */
.y-row--by-title {
  align-items: baseline;
  column-gap: 18px;
  display: grid;
  grid-template-columns: minmax(0, 1fr) 5.5rem 6rem;
  row-gap: 3px;
}

.y-row--by-title .y-row__title {
  grid-column: 1;
  grid-row: 1;
}

.y-row--by-title .y-row__when {
  grid-column: 2;
  grid-row: 1;
}

.y-row--by-title .y-row__measure {
  grid-column: 3;
  grid-row: 1;
  text-align: right;
}

.y-row--by-title .y-row__opening {
  grid-column: 1;
  grid-row: 2;
}

/* A phone has no room for three columns. The title takes the full width and
   wraps instead of being squeezed into what the day and the kind leave, the
   opening runs under it, and the day and the kind, where there is one, share the
   line below. */
@media (max-width: 720px) {
  .y-row--by-title {
    grid-template-columns: minmax(0, 1fr) auto;
    row-gap: 0;
  }

  .y-row--by-title .y-row__title {
    grid-column: 1 / -1;
  }

  .y-row--by-title .y-row__opening {
    grid-column: 1 / -1;
    margin-top: 3px;
  }

  .y-row--by-title .y-row__when {
    grid-column: 1;
    grid-row: 3;
    margin-top: 6px;
  }

  .y-row--by-title .y-row__measure {
    grid-column: 2;
    grid-row: 3;
    margin-top: 6px;
  }
}

/* The journal, read a month at a time. The two ways out of the month hold the
   ends of one line above the calendar, each named for the month it leads to;
   where there is nothing on one side the other keeps its end. */
.y-months {
  display: flex;
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  gap: 12px;
  justify-content: space-between;
  margin-bottom: 14px;
}

.y-months__step {
  color: var(--fg-faint);
}

.y-months__step:hover {
  color: var(--fg);
}

.y-months__step--later {
  margin-left: auto;
}

/* The month itself. Seven columns share the width between them whatever the
   width is, so a phone is given a smaller calendar rather than one it has to
   scroll sideways to reach the end of — which is why the columns are laid out
   from the table rather than from what is written in them. */
.y-month {
  border-collapse: collapse;
  margin-bottom: 28px;
  table-layout: fixed;
  width: 100%;
}

.y-month__caption {
  caption-side: top;
  color: var(--fg);
  font-family: var(--font-serif);
  font-size: var(--fs-ed-20);
  padding-bottom: 10px;
  text-align: left;
}

.y-month__weekday {
  color: var(--fg-faint);
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  font-weight: var(--fw-regular);
  padding: 0 0 6px;
  text-align: left;
}

.y-month__cell {
  border: 1px solid var(--border-faint);
  height: 62px;
  overflow: hidden;
  padding: 4px 5px;
  vertical-align: top;
}

/* The squares before the month began and after it ended are the table's shape
   rather than days, so they are drawn as the gap they are: no rule and no fill,
   which is what leaves the month itself the only thing with edges. */
.y-month__cell--outside {
  border-color: transparent;
}

.y-month__cell--written {
  background: var(--sage-bg);
}

.y-month__day {
  color: var(--fg-faint);
  display: block;
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  font-variant-numeric: tabular-nums;
}

.y-month__cell--written .y-month__day {
  color: var(--fg);
}

/* One link per entry written that day, because two entries on one day is where
   a rule that made the square the link would have to choose between them. The
   name is clipped to the square on screen and the row below carries it whole,
   which is what keeps a busy week from growing down the page. */
.y-month__entry {
  color: var(--link);
  display: block;
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  min-height: 24px;
  overflow: hidden;
  padding-top: 3px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.y-month__entry:hover {
  text-decoration: underline;
}

/* An entry named for nothing but its own day has no words to add to the number
   above it, so it is drawn as the mark a calendar uses for one. */
.y-month__entry--mark::before {
  background: var(--fg);
  border-radius: 50%;
  content: '';
  display: block;
  height: 7px;
  margin-top: 6px;
  width: 7px;
}

/* The entries that wrote no day belong to no month, so they sit under whichever
   month is open rather than falling off every one of them. */
.y-journalundated {
  margin-top: 34px;
}

/* Reports — the sandboxed briefing shell. Not a note: a title, then an iframe
   that fills the reading column. The briefing HTML is served verbatim from
   /reports/<name>/raw and sandboxed (allow-scripts, never allow-same-origin);
   yomihon styles only the frame, never the content inside it. The column is no
   wider than the room the shell left beside the rail before the rail went to
   the window's edge, and stands in the middle of what is left, so a wide window
   puts space around the briefing instead of making the page wider. */
.y-reportmain {
  display: flex;
  flex-direction: column;
  gap: 14px;
  justify-self: center;
  max-width: calc(var(--shell-max) - var(--rail-open));
  overflow: hidden;
  padding: 22px 26px;
  width: 100%;
}

.y-reporthead {
  align-items: baseline;
  display: flex;
  flex-wrap: wrap;
  gap: 8px 16px;
}

.y-reporttitle {
  color: var(--fg);
  flex: 1 0 100%;
  font-family: var(--font-serif);
  font-size: var(--fs-ed-20);
  font-weight: 600;
  min-width: 0;
  overflow-wrap: anywhere;
}

/* The file the report is kept in, under what it calls itself: a fact about
   where it lives, kept quiet so the title is what the head says. */
.y-reportfile {
  color: var(--fg-subtle);
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  min-width: 0;
  overflow-wrap: anywhere;
}

.y-reportraw {
  color: var(--link);
  font-family: var(--font-sans);
  font-size: var(--fs-13);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}

.y-reportframe {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  flex: 1 1 auto;
  height: min(78vh, 900px);
  min-height: 0;
  width: 100%;
}

/* File views — a vault file that is not a note. Source and pictures borrow the
   reading column's own type and code styling, so only the document frame and
   the information page need rules of their own. */
.y-source .chroma {
  margin-top: 22px;
}

.y-fileframe {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  display: block;
  height: min(78vh, 900px);
  margin: 22px 0 0;
  width: 100%;
}

.y-fileinfo {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  margin: 26px 0 0;
  padding: 20px 22px;
}

.y-fileinfo__lead {
  color: var(--fg-subtle);
  font-family: var(--font-serif);
  font-style: italic;
  margin: 0 0 16px;
}

.y-fileinfo__list {
  display: grid;
  gap: 6px 20px;
  grid-template-columns: auto 1fr;
  margin: 0;
}

.y-fileinfo__list dt {
  align-self: baseline;
  color: var(--fg-faint);
  font-family: var(--font-sans);
  font-size: var(--fs-12);
}

.y-fileinfo__list dd {
  color: var(--fg);
  font-family: var(--font-mono);
  font-size: var(--fs-13);
  margin: 0;
  overflow-wrap: anywhere;
}

.y-fileinfo__note {
  color: var(--fg-faint);
  font-family: var(--font-serif);
  font-size: var(--fs-13);
  margin: 10px 0 0;
}

.y-fileinfo__raw {
  color: var(--link);
  display: inline-block;
  font-family: var(--font-sans);
  font-size: var(--fs-13);
  margin: 18px 0 0;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}

/* Status-write recovery — an ordinary reading-shell page whose visual weight
   follows the mutation boundary. Actions are GET links only; the failed POST
   is deliberately not repeatable from this surface. The column it is set in is
   the reading shell's, declared with the other pages that share it, which is
   also how the page for an unknown address came to have a gutter: it draws the
   same column and used to run to both edges of a phone.

   The page for an address that names nothing opens with the shared notice, and
   what follows it — the address the reader typed, the step after, the ways out
   — is the same column continued, on the same left edge. The address is one
   line of the page in the machine face, quiet, rather than a box: it is the
   thing the reader typed, read back, not a block of output to study. */
.y-recovery:has(> .y-nothing) .y-recovery__detail pre {
  background: none;
  border: 0;
  padding: 0;
}

.y-recovery__kicker {
  color: var(--fg-faint);
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  margin: 0 0 10px;
}

.y-recovery__state {
  background: var(--elevated);
  border-left: 3px solid var(--fg-subtle);
  color: var(--fg);
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  margin: 24px 0 0;
  padding: 14px 16px;
}

.y-recovery__state[data-status-changed='false'] {
  border-left-color: var(--border-strong);
}

.y-recovery__summary {
  color: var(--fg);
  font-family: var(--font-serif);
  font-size: var(--fs-ed-17);
  line-height: var(--lh-read);
  margin: 22px 0 0;
}

.y-recovery__detail,
.y-recovery__next {
  margin: 28px 0 0;
}

.y-recovery__detail h2,
.y-recovery__next h2 {
  color: var(--fg);
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  font-weight: var(--fw-semibold);
  margin: 0 0 8px;
}

.y-recovery__detail pre {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  margin: 0;
  overflow-wrap: anywhere;
  overflow-x: auto;
  padding: 14px 16px;
  white-space: pre-wrap;
}

.y-recovery__detail code {
  color: var(--fg-subtle);
  font-family: var(--font-mono);
  font-size: var(--fs-12);
}

.y-recovery__next p {
  color: var(--fg-subtle);
  font-family: var(--font-serif);
  margin: 0;
}

.y-recovery__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 30px 0 0;
}
/* The seal ink is kept for one meaning, the reader's own place, so the way out
   of a page a reader reached by accident is the plain outline every other
   control here wears: ink on the ground, a hairline round it, and the faint
   wash under a pointer. The first action is told from the second by its ink
   and a firmer line, not by a colour that would read as an alarm. */
.y-recovery__action {
  align-items: center;
  background: transparent;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  color: var(--fg);
  display: inline-flex;
  font-family: var(--font-sans);
  font-size: var(--fs-13);
  min-height: 38px;
  padding: 0 14px;
  text-decoration: none;
  transition: background var(--dur-base) var(--ease-standard), border-color var(--dur-base) var(--ease-standard);
}

.y-recovery__action:hover {
  background: var(--wash);
}

.y-recovery__action--quiet {
  border-color: var(--line);
  color: var(--fg-muted);
}

.y-recovery__action--quiet:hover {
  color: var(--fg);
}

/* One notice for every surface that has nothing to show: a search that matched
   no note, an address that names none, a folder with no fault left to report, a
   shelf no declaration has filled, and the desk's notices about a reading that
   came up short. It is one column at a reading measure, so the same
   kind of news reads as the same thing wherever a reader meets it. It starts
   where the page's own title and words start, on the column's left edge,
   rather than centred in a column that a rail or a set of divisions beside it
   has made narrower than the page. The desk's fault sections are the one place
   it gives up the column: they stand in a panel at the page's width and are
   set out below with the rest of the desk.

   An absence stays quiet — the page's own ink, one step down. A fault says so
   in the warning colour and on a warning tint, which is the whole visual
   difference between "there is nothing here" and "something could not be
   read". */
.y-nothing {
  color: var(--fg-subtle);
  font-family: var(--font-serif);
  font-size: var(--fs-14);
  line-height: var(--lh-read);
  margin: 24px 0 0;
  max-width: var(--notice-column);
  /* A vault path, a filter key, or a diagnostic from the operating system is
     one long run with nowhere to break, and at phone width it would otherwise
     carry the whole column off the side of the screen. */
  overflow-wrap: anywhere;
  text-align: start;
}

/* The word that stands where an icon would: the chrome draws none that means
   "there is nothing here", so nothing is drawn. */
.y-nothing__mark {
  color: var(--fg-faint);
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  margin: 0 0 10px;
}

.y-nothing__title {
  color: var(--fg);
  font-family: var(--font-serif);
  font-size: var(--fs-ed-20);
  font-weight: var(--fw-regular);
  line-height: var(--lh-tight);
  margin: 0;
}

/* The one that is also the page's own title carries the page's own size. */
.y-nothing__title--page {
  font-size: var(--fs-ed-30);
}

.y-nothing__says {
  margin-top: 10px;
}

.y-nothing__says p {
  margin: 0;
}

.y-nothing__says p + p {
  margin-top: 10px;
}

.y-nothing__says code {
  font-family: var(--font-mono);
  font-size: 0.92em;
}

/* What the reader can do next sits under the sentence, quieter than it. */
.y-nothing__says .y-nothing__hint {
  color: var(--fg-faint);
  font-size: var(--fs-12);
  line-height: var(--lh-normal);
  margin-top: 14px;
}

.y-nothing--fault {
  background: var(--warn-bg);
  border-radius: var(--r-lg);
  padding: 20px 24px;
}

.y-nothing--fault .y-nothing__mark,
.y-nothing--fault .y-nothing__title {
  color: var(--warn);
}

/* A desk block is small and already has its own heading over it, so the notice
   inside it opens where the rows would have. */
.y-modeblock .y-nothing {
  margin-top: 6px;
}

/* The desk's fault sections are one kind of news said four ways (a standing
   notice, the contract's refusal, the egress declaration, a degraded read), so
   they share one panel: a warning tint at the width of the column, left-aligned
   like the rows above it, with the same gap between neighbours. The notice
   component inside keeps its words and gives up its own tint. */
.y-homeblock--faults {
  background: var(--warn-bg);
  border: 0;
  border-radius: var(--r-lg);
  margin-top: 16px;
  padding: 20px 24px;
}

.y-homeblock--faults .y-nothing {
  background: none;
  border-radius: 0;
  color: var(--fg-muted);
  margin: 0;
  max-width: none;
  padding: 0;
  text-align: left;
}

.y-homeblock--faults .y-nothing__title {
  color: var(--fg);
  font-family: var(--font-display);
  font-size: var(--fs-16);
  font-weight: var(--fw-semibold);
  line-height: 1.5rem;
}

.y-homeblock--faults .y-homeempty {
  color: var(--fg-muted);
}

/* The palette is a floating box rather than a page, and the notice sits inside
   its own padding. */
.y-searchdialog .y-nothing {
  margin: 18px 12px 8px;
}

.y-syl-kicker {
  color: var(--fg-faint);
  font-family: var(--font-sans);
  font-size: var(--fs-12);
}

.y-syl .y-title {
  margin-top: 12px;
}

/* ============================================================
   Listen — one course as the paragraphs its lessons are marked to be read
   aloud in. The paragraphs are the notes' own read-aloud elements, so the
   reading column's own rules carry them; what is here is the page around
   them and the place the browser-built bar lands.
   ============================================================ */
.y-listen .y-title {
  margin-top: 12px;
}

.y-listen__none {
  color: var(--fg-muted);
  margin-top: 24px;
}

.y-listen__lesson {
  margin-top: 34px;
}

.y-listen__title {
  font-family: var(--font-sans);
  font-size: var(--fs-ed-20);
  font-weight: var(--fw-medium);
}

.y-listen__title a {
  color: var(--fg);
  text-decoration: none;
}

.y-listen__title a:hover {
  color: var(--fg);
}

.y-part-block {
  margin-top: 30px;
}

.y-part {
  align-items: baseline;
  border-bottom: 1px solid var(--border);
  display: flex;
  gap: 14px;
  margin: 0;
  padding: 12px 0;
  scroll-margin-top: var(--header-clearance);
}

/* The part's number is a word of its own, 第一部 or Part I, set above the quiet
   end of the ink scale and in the interface face, so it reads as a label on the
   name beside it rather than as a stray mark in the margin. */
.y-part__num {
  color: var(--fg-muted);
  flex: none;
  font-family: var(--font-sans);
  font-size: var(--fs-13);
  font-weight: var(--fw-medium);
}

.y-part__name {
  color: var(--fg);
  flex: 1;
  font-family: var(--font-serif);
  font-size: var(--fs-ed-24);
  font-weight: var(--fw-medium);
  min-width: 0;
}

.y-module {
  padding: 22px 0 0 38px;
}

.y-module__label {
  align-items: center;
  display: flex;
  gap: 10px;
  margin: 0 0 4px;
}

/* A side branch is drawn inside the item of the row it hangs from, so the line
   the main line is read along has to pass it to reach the row that follows. The
   branch draws the stretch of that line beside itself, on the same column the
   rows' own rules sit in, and starts it flush with the row above and ends it
   flush with the one below. Its own text sits to the right of the line. */
.y-lessons > li:has(+ li) > .y-module {
  position: relative;
}

.y-lessons > li:has(+ li) > .y-module::before {
  border-left: 1px solid var(--border-strong);
  bottom: 0;
  content: '';
  left: calc(var(--lesson-inset) + var(--lesson-gutter) / 2 - 0.5px);
  position: absolute;
  top: 0;
  width: 0;
}

.y-module__tag {
  color: var(--fg-faint);
  flex: none;
  font-family: var(--font-sans);
  font-size: var(--fs-12);
}

.y-module__name {
  color: var(--fg-muted);
  flex: none;
  font-family: var(--font-sans);
  font-size: var(--fs-13);
  font-weight: var(--fw-medium);
}

.y-module__rule {
  background: var(--border-faint);
  flex: 1;
  height: 1px;
}

/* An ordered run of lessons is a real ordered list; the base layer strips
   list markers, so the numbers are restored here on purpose — they are the
   author's declared order made visible, and hiding them would also cost the
   list its semantics for some screen readers. */
.y-lessons {
  list-style: decimal;
  margin: 0;
  padding: 0 0 0 34px;
}

.y-lessons > li::marker {
  color: var(--fg-faint);
  font-family: var(--font-sans);
  font-size: var(--fs-12);
}

/* The two measures the line and the rows are laid out by, set on the page so a
   row and the side branch beside it read the same two. */
.y-syl {
  --lesson-gutter: 20px;
  --lesson-inset: 6px;
}

/* A part reads as a line: the rows of one run are joined by a single vertical
   rule through the column their marks already sit in, and each mark is a point
   on it. The rule is drawn by the rows themselves, so it needs no element of
   its own and follows however tall a wrapped name makes its row.

   The two numbers the rule is placed by are the same two the grid is built
   from, so the rule cannot drift off the marks when either changes. Half a
   pixel back from the centre puts the one-pixel edge across it rather than
   beside it. It is an edge and not a filled box because paper prints an edge
   and leaves a background behind. It never moves, so reduced motion and
   forced colours have nothing of it to strip.

   It carries the strongest of the hairline inks, one step above the rule
   between two rows: the line is what the part is read along, and at the
   weight of the row rules it was there to be found rather than seen. */
.y-lesson {
  align-items: center;
  border-bottom: 1px solid var(--border-faint);
  color: inherit;
  display: grid;
  gap: 14px;
  grid-template-columns: var(--lesson-gutter) minmax(0, 1fr) auto;
  padding: 11px var(--lesson-inset);
  position: relative;
  text-decoration: none;
}
/* A row standing alone joins nothing, so the rule starts and ends at its own
   point and has no length. The two rules below give it one, in the direction
   there is a neighbour to reach. */
.y-lesson::before {
  border-left: 1px solid var(--border-strong);
  bottom: 50%;
  content: '';
  left: calc(var(--lesson-inset) + var(--lesson-gutter) / 2 - 0.5px);
  position: absolute;
  top: 50%;
  width: 0;
}
/* Up to meet the point above. An ordered run wraps each row in a list item, a
   run navigation never walks does not, and a bare row sits directly under the
   part or the module that lists it. */
.y-lessons > li + li > .y-lesson::before,
.y-part-block > .y-lesson + .y-lesson::before,
.y-module > .y-lesson + .y-lesson::before {
  top: 0;
}
/* Down to meet the point below, across the row's own rule: without that pixel
   the line breaks once per row and reads as a table. */
.y-lessons > li:has(+ li) > .y-lesson::before,
.y-part-block > .y-lesson:has(+ .y-lesson)::before,
.y-module > .y-lesson:has(+ .y-lesson)::before {
  bottom: -1px;
}

.y-lesson:hover {
  background: var(--overlay);
}
/* Every mark sits on the line and none is drawn through: the rule is
   positioned, so it paints above everything in the row's ordinary flow and
   drew a pale stripe across the dark point until the marks were positioned
   too, which puts them after it in the row. Centring them is what puts them
   on the line at all — the warning mark filled its whole column and left its
   glyph against the column's left edge, a finger's width off the line every
   other row runs along. */
.y-lesson .y-navdot,
.y-lesson .y-navmark {
  justify-self: center;
  position: relative;
}

/* The points are stops on the line and nothing more: drawn in the line's own
   ink and all one shape, whatever status the lesson is at. A status that needs
   noticing is said once, at the row's other end, rather than twice down the
   same row. */
.y-lesson .y-navdot {
  background: var(--line-strong);
  border-radius: 50%;
}
/* Where the reader is standing is the heavier point: larger, and in the
   reading ink rather than the quiet one the other points keep. */
.y-lesson--here .y-navdot {
  background: var(--fg);
  height: 11px;
  width: 11px;
}
/* A row's words are one run of text: the lesson's name, then the sentence its
   author wrote after the link, which carries on from it as a sentence does and
   wraps with it. A phone row used to hide the words that decide which lesson
   to open (nowrap + ellipsis beside a whole status badge), and the first fix
   clamped the name to two lines; with the sentence in the same run nothing is
   clipped at all, so every word the author put on the row is there to be read.
   The badge stays on the auto track and must not wrap. The rail keeps its own
   ellipsis. */
.y-lesson__text {
  min-width: 0;
  overflow-wrap: anywhere;
}

.y-lesson__title {
  color: var(--fg);
  font-family: var(--font-serif);
  font-size: var(--fs-ed-20);
}

/* The author's sentence, set after the name in the same face one step smaller
   and a step quieter, so that the name is what is read first and the sentence
   follows it on the line instead of hanging beneath it as a second row. It is
   inline on purpose: a box here would be the second line it replaces. */
.y-gloss {
  color: var(--fg-muted);
  font-family: var(--font-serif);
  font-size: var(--fs-ed-17);
}

/* What closes a row: the reader's own bookmark where they left off in this
   lesson, and the lesson's status where it needs noticing. They share the one
   trailing track so the grid keeps its three columns. */
.y-lesson__aside {
  align-items: center;
  display: inline-flex;
  gap: 12px;
}

.y-lesson--broken {
  cursor: help;
}

.y-lesson--broken .y-lesson__title {
  color: var(--fg-muted);
}

.y-pathitem__num {
  color: var(--fg-faint);
  flex: none;
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  min-width: 16px;
}

.y-pathitem__name {
  display: block;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.y-syl-raillabel {
  padding: 0 8px 6px;
}

.y-syl-raillabel--gap {
  padding-top: 20px;
}


/* The words end on a space and the arrow follows as inline text, the way the
   rest of the interface sets "All of it →". A flex or grid box here would drop
   that space, since the space is trailing whitespace inside a box of its own,
   and leave the arrow touching the last word. */
.y-syl-read {
  color: var(--fg);
  font-family: var(--font-sans);
  font-size: var(--fs-13);
  font-weight: var(--fw-medium);
}

/* What the author kept out of the course and listed after it: titles with the
   sentences they wrote, set as a plain list. Nothing here is a step, so it has
   no marks and no line, and no number. */
.y-appendix {
  margin-top: 54px;
}

.y-reflist {
  list-style: none;
  margin: 0;
  padding: 0;
}

.y-ref {
  border-bottom: 1px solid var(--border-faint);
  color: inherit;
  display: block;
  overflow-wrap: anywhere;
  padding: 11px 6px;
  text-decoration: none;
}

.y-ref:hover {
  background: var(--overlay);
}

.y-ref__title {
  color: var(--fg);
  font-family: var(--font-serif);
  font-size: var(--fs-ed-17);
}

.y-ref--broken {
  cursor: help;
}

.y-ref--broken:hover {
  background: none;
}

.y-ref--broken .y-ref__title {
  color: var(--fg-muted);
}

/* The way to the whole note ends the contents, as one quiet line. */
.y-cover__note {
  margin: 40px 0 0;
}

/* The cover: what a course is, before what it contains. The author's own
   opening reads as prose at the reading face, and the verb below the counts
   is the one way in. */
.y-cover__opening {
  margin: 18px 0 0;
}

.y-cover__ways {
  align-items: center;
  column-gap: 20px;
  display: flex;
  flex-wrap: wrap;
  margin: 22px 0 4px;
  row-gap: 10px;
}

/* The verb and the lesson beside it are one line that wraps rather than one
   that clips: a lesson's name is the reader's only way of telling where they
   are being sent back to, and a name cut short is no answer at all. */
.y-cover__open {
  align-items: baseline;
  background: var(--elevated);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  color: var(--fg);
  column-gap: 10px;
  display: inline-flex;
  flex-wrap: wrap;
  font-family: var(--font-sans);
  font-size: var(--fs-13);
  max-width: 100%;
  min-width: 0;
  padding: 8px 14px;
  row-gap: 2px;
}

.y-cover__open:hover {
  background: var(--overlay);
}

.y-cover__verb {
  flex: none;
  font-weight: var(--fw-medium);
}

.y-cover__lesson {
  color: var(--fg-muted);
  min-width: 0;
  overflow-wrap: anywhere;
}

/* ============================================================
   Reading smoothness — scroll feel, wrapping, arrival
   ------------------------------------------------------------
   The document is the scroller for prose, so these land on the root.
   The rails keep their own overflow and scroll independently.

   The root scrolls instantly, and has to. A link written at a section of
   another note is a navigation that carries a fragment, and a navigation
   animates the page across to the new document; an animated scroll started
   underneath that animation is dropped, so the reader is left at the top of
   the note with the address bar naming a section they cannot see. Measured
   in Chrome, headed and headless alike, and with scripting off, which is why
   no enhancement can stand in for this line. Instant arrival was already a
   shipped mode of this reader — it is what a reduced-motion setting asked
   for — and it costs a glide within the page to keep every link that names a
   section actually landing on it.
   ============================================================ */
html {
  scroll-behavior: auto;
  scroll-timeline: --y-read block;
  scrollbar-gutter: stable;
}

.y-rail-left,
.y-rail-right {
  overscroll-behavior: contain;
}

/* paragraphs wrap with the last lines weighed; titles keep their balance */
.y-prose p {
  text-wrap: pretty;
}

/* A run the browser has nowhere to break — a URL pasted mid-sentence, a vault
   path, the escaped source of a tag the renderer shows as words — is as wide
   as it is, and the paragraph holding it is at least that wide. Unbroken it
   pushes the reading column past the phone, and what the reader then scrolls
   sideways is the whole document: every line of the note pays for the one run
   in it. So the run bends. `anywhere` rather than `break-word` because the two
   break a line identically and only `anywhere` also takes the run out of the
   column's own minimum width, which is what decides whether a box that sizes
   to its content can shrink at all.

   Japanese is untouched by this. CJK already breaks between characters, so an
   emergency break adds no opportunity the line did not have, and the
   phrase-aware rule below still chooses where an ordinary Japanese line ends.
   Measured in the browser: a Japanese paragraph breaks after the same
   characters with this rule and without it. */
.y-prose {
  overflow-wrap: anywhere;
}

/* A ## is laid out as a flex row so it can carry its section bar, which makes a
   ## naming an address two flex items — the words, and the link the renderer
   made of the address. The two shrink together when the row will not fit, and
   under `anywhere` each can shrink to a single character: "A heading naming
   <address>" came back as "A / headi / ng / nami / ng". `break-word` breaks the
   same run in the same place while leaving the intrinsic minimum alone, so the
   words of a heading stay whole. The levels that are not flex rows are given it
   too, so one line describes every heading. A ## whose own address is wider
   than the column still runs past it, exactly as it does today: that is the
   flex row's shape rather than this rule's, and it is left where it is. */
.y-prose [data-level] {
  overflow-wrap: break-word;
}

/* Code and a diagram's source are written in lines, and a table is written in
   columns; all three already answer an overflow by scrolling inside a box of
   their own rather than by reflowing. A break invented inside them would be a
   line the author did not write — with scripting off, `A[SomeLongNodeName]`
   came back split across two lines of the diagram source — or a column crushed
   to a few characters where .y-tablewrap would have scrolled. They keep their
   own idea of a line. */
.y-prose pre,
.y-prose .mermaid-diagram,
.y-prose table {
  overflow-wrap: normal;
}

/* CJK punctuation sets solid where full widths would double up, and only a
   paragraph's first line keeps its full-width opening bracket — the
   classical book convention. Adjacent-pair trimming largely rides the
   font's own contextual half-width feature once the content language is
   CJK; this declaration pins the line-start convention explicitly. */
.y-prose {
  text-spacing-trim: space-first;
}

/* Han meeting a Latin letter or a digit with no space between them (a title like
   "Go 並行" is typed with one, "第3課" is not) is set apart by the browser, so
   the page is spaced the same whether or not the author typed the gap. The
   property is inherited, so this one declaration reaches the chrome, the rails
   and the prose; a browser without it sets the text as written. */
body {
  text-autospace: normal;
}

/* Code, a shortcut and a file's path are read one character at a time and
   typed back, so none of them is given a gap the text does not have. */
code,
kbd,
pre,
samp,
.y-fileinfo__list dd,
.y-findings__path,
.y-homenote__path,
.y-notefacts a,
.y-result__meta {
  text-autospace: no-autospace;
}

/* CJK type has no italic. Asked for one, a browser slants the upright glyphs
   itself, which no type designer drew and which reads as a smear beside the
   same line's real Latin italic. Where the language is CJK that synthesis is
   switched off, so the Han stays upright and only the Latin takes the italic. */
:lang(ja),
:lang(ko),
:lang(zh) {
  font-synthesis-style: none;
}

/* Strict breaking keeps the characters that may not begin a line (closing
   brackets, commas, full stops, the iteration and prolonged-sound marks) off
   the start of one, which is the rule a Chinese reader expects of a book. */
:lang(zh) {
  line-break: strict;
}

/* phrase-aware Japanese line breaking, wherever content is language-tagged */
.yomihon [lang='ja'] {
  word-break: auto-phrase;
}

/* Arrival echo — a brief wash on the heading a jump lands on, whether the
   reader came from the table of contents, a footnote, or a link in another
   note. The eye arrives the moment the jump does, so the echo plays at once;
   it used to be held back while the viewport was still travelling, which is a
   wait that no longer describes anything and would now simply delay the one
   signal saying where the reader ended up. */
.y-prose :target {
  animation: y-arrive var(--dur-dwell) var(--ease-standard);
}
@keyframes y-arrive {
  from {
    background: var(--accent-muted);
    box-shadow: 0 0 0 6px var(--accent-muted);
  }

  to {
    background: transparent;
    box-shadow: 0 0 0 6px transparent;
  }
}

/* Top-layer open and close. The transition lives on the element, not on
   [open]: removing the open state used to drop the declaration, so the
   exit had nowhere to live and the surface vanished mid-curve. display
   and overlay stay in the list with allow-discrete so the element remains
   on the top layer while it fades; display alone would keep painting
   frames after it had already left, behind the backdrop.
   The palette answers a press in place, so it comes forward from its top
   edge, a little small and faint and growing into place, and leaves the same
   way back. */
.y-searchdialog {
  opacity: 0;
  scale: 0.98;
  transform-origin: top;
  transition: display var(--dur-slow) var(--ease-standard), overlay var(--dur-slow) var(--ease-standard), opacity var(--dur-slow) var(--ease-standard), scale var(--dur-slow) var(--ease-standard);
  transition-behavior: allow-discrete;
}

.y-searchdialog[open] {
  opacity: 1;
  scale: none;
}
/* The sheet is held against the right edge of the window, so it arrives from
   there: a short travel inward beside the fade, which says where the panel
   came from and where the close control on that same edge sends it back to.
   It rests at none, so the open sheet leaves no transform behind to become a
   containing block for anything fixed inside it. */
.y-conceptsheet {
  opacity: 0;
  transform: translateX(16px);
  transition: display var(--dur-slow) var(--ease-standard), overlay var(--dur-slow) var(--ease-standard), opacity var(--dur-slow) var(--ease-standard), transform var(--dur-slow) var(--ease-standard);
  transition-behavior: allow-discrete;
}

.y-conceptsheet[open] {
  opacity: 1;
  transform: none;
}
@starting-style {
  .y-searchdialog[open] {
    opacity: 0;
    scale: 0.98;
  }

  .y-conceptsheet[open] {
    opacity: 0;
    transform: translateX(16px);
  }
}

/* Reading-position hairline — the header's bottom edge doubles as the
   indicator: a quiet ink fill rides the existing 1px hairline, driven by the
   reading column's scroll position through a named scroll timeline (the
   header is not a descendant of the scroller, so the scope is lifted to
   their common ancestor). It is scroll position, not completion — the
   syllabus bars own "done" — and it is essential state display: reduced
   motion keeps it, via the explicit exemption in the blanket kill below.
   A page with nothing to scroll leaves the timeline inactive and the
   fill invisible. */
.yomihon {
  timeline-scope: --y-read;
}

.y-readline {
  background: var(--fg-subtle);
  bottom: -1px;
  height: 1px;
  left: 0;
  pointer-events: none;
  position: absolute;
  right: 0;
  transform: scaleX(0);
  transform-origin: left;
}
/* The hairline is driven only where a scroll timeline exists. Guarding the
   animation keeps an engine without scroll timelines at the resting scaleX(0)
   — no line — rather than snapping to a permanently full one, which is what a
   scroll-bound animation with no timeline and no duration would otherwise do.
   The explicit auto duration keeps the time component valid for the timeline. */
@supports (animation-timeline: scroll()) {
  .y-readline {
    animation: y-readline linear both;
    animation-duration: auto;
    animation-timeline: --y-read;
  }
}
@keyframes y-readline {
  from {
    transform: scaleX(0);
  }

  to {
    transform: scaleX(1);
  }
}


/* ============================================================
   Compare (two notes side by side)
   ------------------------------------------------------------
   Two reading columns need the width, so neither rail is drawn and each
   column keeps its own scroller: the whole point of the page is moving
   through one note while the other stays where the reader left it. Below the
   width two columns fit in, the columns stack and the document scrolls, and
   the strip above them carries a link to each so a reader can move between
   them without scrolling past one whole note.
   ============================================================ */
.y-compare-shell {
  display: flex;
  flex: 1;
  flex-direction: column;
  min-width: 0;
}

/* The strip holds nothing a reader can see while the columns are side by side
   and no script is running, so its height comes from its children and it
   collapses rather than leaving an empty bar over the reading. */
.y-compare__tabs {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
  min-width: 0;
}

.y-compare__tab {
  display: none;
}

/* Keeping the columns level is the script's to do, so the control appears only
   where a script marked the document. */
.y-compare__align {
  background: none;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  color: var(--fg-subtle);
  cursor: pointer;
  display: none;
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  margin: 8px 20px 0;
  padding: 6px 12px;
}

[data-js] .y-compare__align {
  display: inline-flex;
}

.y-compare__align[aria-pressed='true'] {
  border-color: var(--fg);
  color: var(--fg);
}

.y-compare {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  min-width: 0;
}

/* minmax(0, 1fr) above and min-width here are the same guard written at both
   ends: without it a long word, a wide table or a code block sets the column's
   floor and the page grows sideways. */
.y-compare__column {
  max-height: calc(100dvh - var(--header-height));
  min-width: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  scroll-margin-top: var(--header-clearance);
}

.y-compare__column + .y-compare__column {
  border-left: 1px solid var(--border);
}

/* Half the page's width per note, so the reading measure gives back what the
   note page spends on a gutter it no longer has room for. */
.y-compare__column .y-article {
  --article-gutter: 28px;
}

/* The aids fold inline inside every column at every width: the right rail that
   would otherwise carry them is not drawn here, and one rail beside two notes
   could not say which of them it spoke for. */
.y-compare__column .y-inlineaids {
  display: block;
  margin: 16px 0 0;
}

.y-compare__column .y-related {
  display: flex;
  flex-direction: column;
  gap: 24px;
  margin-top: 56px;
}

.y-compare__head {
  align-items: center;
  border-bottom: 1px solid var(--border-faint);
  display: flex;
  gap: 10px;
  justify-content: space-between;
  padding: 12px 28px;
}

.y-compare__alone {
  color: var(--fg-faint);
  font-family: var(--font-sans);
  font-size: var(--fs-12);
}


/* ============================================================
   Responsive
   ============================================================ */
@media (min-width: 1280px) {
  /* The same split .y-rail-right and .y-inlineaids already use: past it the
     facts stand open beside the title, so the disclosure that folds them at
     narrower widths is not also drawn. The selector repeats the type
     qualifier details.y-metarow carries at its own display: block rule
     above, matching that rule's specificity instead of reaching for
     !important — source order alone then decides it, this query being the
     later one. */
  /* stylelint-disable-next-line selector-no-qualifying-type -- the same class
     marks a plain row on the file and study-path pages; only the note's
     disclosure is meant here. */
  details.y-metarow {
    display: none;
  }

  .y-headmeta {
    display: flex;
  }
}
/* One column at a time, the document scrolling, and the strip pinned under the
   header so the other note is always one press away. Levelling two columns
   that are no longer beside one another means nothing, so that control goes. */
@media (max-width: 1100px) {
  .y-compare {
    grid-template-columns: minmax(0, 1fr);
  }

  .y-compare__column {
    max-height: none;
    overflow: visible;
  }

  .y-compare__column + .y-compare__column {
    border-left: 0;
    border-top: 1px solid var(--border);
  }

  .y-compare__tabs {
    background: var(--bg);
    border-bottom: 1px solid var(--border);
    justify-content: flex-start;
    position: sticky;
    top: var(--header-height);
    z-index: 5;
  }

  .y-compare__tab {
    color: var(--fg-subtle);
    display: inline-flex;
    font-family: var(--font-mono);
    font-size: var(--fs-12);
    max-width: 50%;
    overflow: hidden;
    padding: 12px 14px;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  [data-js] .y-compare__align {
    display: none;
  }
}

@media (max-width: 1279px) {
  .y-shell {
    --rail-open: 248px;
    grid-template-columns: var(--rail-open) minmax(0, 1fr);
  }

  .y-rail-right {
    display: none;
  }

  .y-inlineaids {
    display: block;
    margin: 16px 0 0;
  }

  .y-related {
    display: flex;
    flex-direction: column;
    gap: 24px;
    margin-top: 56px;
  }
}
/* The left column folded away, above the width where it is a drawer. What is
   left of it is a strip holding the one button that brings it back, so the
   reader is never left without a way to undo the choice and the navigation
   landmark stays, labelled, holding a button. The strip is the rail itself
   narrowed — the same ground and the same edge — not a second element.

   The rule reads the root rather than anything the script sets, because the
   script arrives after the first paint and a column that painted and then
   vanished on every page would be worse than one that never folded. It is held
   to the shells that have a rail: a page without one has nothing in the first
   track to give up, and its main would otherwise slide into the strip.

   Everything here sits inside the width query. A cookie that says collapsed
   read at a narrow width must leave the drawer whole, not empty.

   The fold is a transition between two settled states. The server writes the
   state on the root before anything is painted, so the first style the page
   ever computes is already the final one and nothing moves on load; a change
   afterwards, by the button or the key, is what the transition answers. While
   the track narrows the panel keeps the width it had and the rail clips it, so
   no row reflows on the way; it fades over the same time and leaves the tree
   at the end, which is what lets the strip hold only the button. */
@media (min-width: 901px) {
  .y-shell:has(> .y-rail-left),
  .y-shell2:has(> .y-rail-left) {
    transition: grid-template-columns var(--dur-base) var(--ease-standard);
  }

  .y-rail-left {
    overflow-x: hidden;
  }

  .y-railbody,
  .y-railhead .y-filter {
    opacity: 1;
    transition: display var(--dur-base) var(--ease-standard), opacity var(--dur-base) var(--ease-standard);
    transition-behavior: allow-discrete;
  }

  /* Held at the width the column has when open for as long as the column is
     moving, either way, so no row reflows on the way and the rail's scroll
     position means the same thing at both ends. Only while it moves: an open
     panel at rest is as wide as the room it has, which is less than the column
     by the rail's padding, its edge, and the scrollbar when it scrolls, and a
     floor set from the column alone would push the panel's right side under
     that scrollbar. The subtraction is the two sides of the rail's padding
     and its one-pixel edge. */
  html[data-rail-moving] .y-railbody {
    min-width: calc(var(--rail-open) - var(--rail-pad) - var(--rail-pad-start) - 1px);
  }

  html[data-rail='collapsed'] {
    --rail-strip: 40px;
  }

  html[data-rail='collapsed'] .y-shell:has(> .y-rail-left) {
    grid-template-columns: var(--rail-strip) minmax(0, 1fr) var(--rail-right);
  }

  html[data-rail='collapsed'] .y-shell--rail-empty:has(> .y-rail-left),
  html[data-rail='collapsed'] .y-shell2:has(> .y-rail-left) {
    grid-template-columns: var(--rail-strip) minmax(0, 1fr);
  }

  html[data-rail='collapsed'] .y-railbody,
  html[data-rail='collapsed'] .y-railhead .y-filter {
    display: none;
    opacity: 0;
  }

  /* The ring's edge is the reach of the outline, and with the usual offset it
     would run a pixel past the strip's inside edge on the right. */
  html[data-rail='collapsed'] .y-railtoggle__button:focus-visible {
    outline-offset: 0;
  }

  /* A page restored from the back/forward cache is put in the state its cookie
     names at once: the fold is answered by a transition only when the reader
     asked for it, and a restore is not the reader asking. */
  html[data-rail-settling] .y-shell:has(> .y-rail-left),
  html[data-rail-settling] .y-shell2:has(> .y-rail-left),
  html[data-rail-settling] .y-railbody,
  html[data-rail-settling] .y-railhead .y-filter {
    transition: none;
  }

  html[data-rail='collapsed'] .y-railtoggle__pane {
    fill-opacity: 0;
  }
}

@media (min-width: 901px) and (max-width: 1279px) {
  html[data-rail='collapsed'] .y-shell:has(> .y-rail-left) {
    grid-template-columns: var(--rail-strip) minmax(0, 1fr);
  }
}

@media (max-width: 900px) {
  .y-shell,
  .y-shell2 {
    grid-template-columns: 1fr;
  }

  .y-healthpage,
  .y-home {
    --gutter-list: 20px;
    padding: 32px var(--gutter-list) 120px;
  }

  .y-homeblock__head {
    align-items: flex-start;
    flex-direction: column;
    gap: 3px;
  }

  .y-homeblock__head p {
    margin-left: 0;
    text-align: left;
  }

  .y-homenote {
    align-items: flex-start;
  }

  .y-homenote__meta {
    align-items: flex-end;
    flex-direction: column;
  }

  .y-module {
    padding-left: 16px;
  }

  /* The line the main line is read along runs a little under sixteen pixels in
     from the edge of a list item, and a side branch is drawn inside that item.
     The part's other blocks start sixteen pixels in at this width, which would
     put the branch's label across the line, so a branch starts further in. */
  .y-lessons > li > .y-module {
    padding-left: 30px;
  }

  .y-lessons {
    padding-left: 24px;
  }

  .y-lesson {
    gap: 10px;
  }
  /* The sidebar is ordinary stacked content until the enhancement runtime
     marks the document. With JS off it therefore remains reachable, and the
     drawer-only hamburger keeps no dead face in the header. */
  .y-rail-left {
    max-height: none;
    position: static;
    width: auto;
  }
  /* A page with no rail has nothing for the drawer to open. */
  html[data-js] .yomihon:has(.y-rail-left) .y-hamburger {
    display: inline-flex;
  }

  /* The closed drawer waits just off the left edge, and a shadow drawn around
     it reaches back across that edge into the page as a grey band. The shadow
     belongs to the open drawer, where it lifts the panel off the page, and it
     fades with the slide so a closing drawer does not leave it behind. */
  html[data-js] .y-rail-left {
    bottom: 0;
    box-shadow: none;
    left: 0;
    max-height: none;
    position: fixed;
    top: var(--header-height);
    transform: translateX(-102%);
    transition: box-shadow var(--dur-slow) var(--ease-standard), transform var(--dur-slow) var(--ease-standard);
    width: min(300px, 86vw);
    z-index: 45;
  }

  html[data-js][data-nav='open'] .y-rail-left {
    box-shadow: var(--shadow-2);
    transform: none;
  }

  html[data-js][data-nav='open'] .y-nav-scrim {
    display: block;
  }

  html[data-js][data-nav='open'] .y-main {
    overflow: hidden;
  }

  .y-searchbtn {
    min-width: 0;
  }

  .y-searchbtn__label,
  .y-searchbtn .ui-kbd {
    display: none;
  }
  /* The shell narrows as one: the gutter is redeclared and the box follows
     it, so the line length is still the measure and not what is left over. */
  .y-article,
  .y-listen,
  .y-prefs,
  .y-recovery,
  .y-syl {
    --gutter-read: 24px;
    padding: 32px var(--gutter-read) 120px;
  }
}
@media (max-width: 520px) {
  .y-header {
    gap: 5px;
    padding-inline: 8px;
  }

  .y-article,
  .y-listen,
  .y-prefs,
  .y-recovery,
  .y-syl {
    --gutter-read: 20px;
  }

  .y-brand {
    gap: 7px;
  }

  .y-brand__tag {
    display: none;
  }

  /* The state word goes onto its own line so the controls keep one row each;
     everything else stays in the order it is written, which keeps stop beside
     the play, previous and next it belongs with. */
  .y-ttsbar__status {
    flex-basis: 100%;
    margin-left: 0;
    order: 2;
  }
}
@media (forced-colors: active) {
  /* The fixed-colour mark is decorative. Keep the exact textual product name
     as the forced-colours identity instead of overriding user colours. */
  .y-brand__mark {
    display: none;
  }

  /* A bookmark is a fill, and a fill is what forced colours takes away. The
     reader's chosen highlight keeps it a shape to be seen. */
  .y-keptmark,
  [data-mark-kept] [data-mark-button]::before {
    background: Highlight;
    forced-color-adjust: none;
  }
}
@media (prefers-reduced-motion: reduce) {
  .y-rail-left {
    transition: none;
  }

  .y-rail-left details::details-content,
  .y-metarow::details-content,
  .y-toc-inline::details-content,
  .y-statusconfirm::details-content,
  .y-facets::details-content {
    transition: none;
  }
}

/* Print: the page becomes the paper. The chrome exists to move between notes,
   which paper cannot do, so it goes; ink is black on white whatever theme the
   screen was showing, because a teacher printing after an evening in dark mode
   must not get a dark page; and the reading aids that fold in at narrow
   widths stay hidden — a lesson handed out in class must not open with a
   panel of note problems. */
@media print {
  /* Paper is one colour, so every surface goes white and every mark goes
     black. Resetting only the inks left the dark theme's raised panels and
     tinted callouts underneath them: code, table headings and status chips
     printed as black on near-black, which is not a duller page but a blank
     one. Naming the whole palette here also means a colour added later cannot
     quietly reintroduce that, since anything unnamed inherits a white ground.
     color-scheme goes with it, or the browser paints form controls dark.
     The system-dark palette is written as :root:not([data-theme='light']), a
     specificity the bare :root does not reach, so a reader who stored no
     choice would print the screen's dark ink; the same selector here ties
     with it and wins by coming later. */
  :root,
  :root:not([data-theme='light']),
  :root[data-theme='dark'],
  :root[data-theme='light'] {
    color-scheme: light;
    --bg: #fff;
    --panel: #fff;
    --elevated: #fff;
    --overlay: #fff;
    --fg: #000;
    --fg-muted: #000;
    --fg-subtle: #333;
    --fg-faint: #555;
    --border: #bbb;
    --border-strong: #888;
    --border-faint: #ddd;
    --accent: #000;
    --accent-strong: #000;
    --accent-muted: transparent;
    --accent-faint: transparent;
    --on-accent: #fff;
    --link: #000;
    --warn: #000;
    --warn-bg: transparent;
    --gold: #000;
    --gold-ink: #000;
    --error: #000;
    --error-bg: transparent;
    --sage: #000;
    --sage-bg: transparent;
    --success: #000;
    --success-bg: transparent;
  }

  /* A notice about a fault prints as its words. The box around it on screen
     separates it from the page it interrupts; a sheet holding one notice has
     nothing to be separated from, and the rule would print as an empty frame
     around two lines. */
  .y-nothing--fault {
    border: 0;
    padding: 0;
  }

  /* stylelint-disable declaration-no-important -- print has no later stylesheet
     to out-order and no script to toggle a class instead; !important is the
     only way these rules can be sure they win against whatever screen-only
     rule came before them, regardless of its own specificity. */
  /* Machinery that only answers to a pointer leaves no trace on paper. */
  .y-header,
  .y-rail-left,
  .y-rail-right,
  .y-inlineaids,
  .y-related,
  .y-searchdialog,
  .y-nav-scrim,
  .y-skiplink,
  .y-sealbar,
  .y-preview,
  .y-compare__tabs,
  .y-tts,
  .y-ttsbar,
  .y-slotcontrols,
  .y-slotactions,
  .y-facets,
  .y-pager,
  .y-months {
    display: none !important;
  }
  /* Librarian chrome in the opening column is for repair on screen, not for
     paper. A printed page is the title and the body; path crumbs, type and
     Obsidian metarow, and folder previous/next stay off the sheet. Course
     lesson-order prev/next may remain — it is part of the book. .y-headmeta is
     the same facts' wide-open copy, named here too: without it the width the
     browser prints at (often 1280 or wider) would show the facts
     .y-metarow was just told to hide, doubling them on the sheet rather than
     dropping them from it. */
  .y-crumbs,
  .y-metarow,
  .y-headmeta,
  .y-steps:not(.y-steps--course) {
    display: none !important;
  }
  /* A course's own verb is pressed, and paper takes no press. What the author
     wrote above the counts stays on the sheet: it is the course's own words,
     which is what the sheet is for. */
  .y-cover__open {
    display: none !important;
  }
  /* stylelint-enable declaration-no-important */
  /* A listing row clips its document's opening to one line on screen so the
     shelf stays a shelf and the rest is one click away. Paper has the room and
     takes no click, so there the line wraps and is printed whole. */
  .y-row__opening {
    overflow: visible;
    white-space: normal;
  }
  /* A month printed on paper is a calendar somebody will write on: the squares
     keep their rules, a week is never split across two sheets, and an entry's
     name is set whole because the reader cannot click the row below to see the
     rest of it. */
  .y-month__cell {
    border-color: var(--border);
    height: auto;
    overflow: visible;
  }

  .y-month tr {
    break-inside: avoid;
  }

  .y-month__entry {
    overflow: visible;
    white-space: normal;
  }
  /* A fold the author closed is an offer the reader takes by clicking: hold
     the answer back, then give it. Paper takes no click, so on a sheet the
     closed fold prints its one-line summary and drops everything under it —
     an aside, or the explanation behind a question, gone with no way for the
     person holding the page to ask for it. So a fold in the note's own prose
     arrives open. The engine wraps a disclosure's body in ::details-content
     and skips it while the element is closed, so naming that wrapper is the
     whole of the change. The interface's own disclosures are already off the
     sheet, which is why this is scoped to the prose. */
  .y-prose details::details-content {
    content-visibility: visible;
  }
  /* What each finding means goes on the sheet with the findings. A printed
     table of faults whose words nobody can look up is a list of complaints,
     and the person holding the paper cannot click the fold open. The arrows
     that offer another ordering go, for the same reason the rest of the
     pointer machinery does. */
  .y-findingsguide::details-content {
    content-visibility: visible;
  }

  .y-findings__sort::after {
    content: none;
  }

  .y-shell,
  .y-shell2 {
    display: block;
  }

  .y-main {
    min-width: 0;
  }
  /* Two notes come off the press one after the other, each opening a sheet.
     A scroll box cannot be paged, so what the reader would have scrolled to
     would simply not be printed; and two columns of unequal length side by
     side leave every sheet half empty from wherever the shorter one ends. */
  .y-compare {
    display: block;
  }

  .y-compare__column {
    max-height: none;
    overflow: visible;
  }

  .y-compare__column + .y-compare__column {
    border-left: 0;
    break-before: page;
  }
  /* Paper has edges the screen does not, and a block that runs across one
     comes back cut with no way to rejoin it: half a diagram, a listing whose
     indentation restarts, an excerpt separated from the line naming the note
     it came from, a row with one cell on each sheet. So the sheet is given
     margins wide enough to hold and to bind, and the blocks that mean nothing
     in halves are asked to stay whole. One that genuinely cannot fit is
     broken by the browser anyway, which is the right answer and not this
     rule's to make.

     A heading keeps at least the opening of what it announces beside it, and
     a paragraph neither leaves one line behind nor carries one over. A note's
     own top-level heading starts a fresh sheet, because that is where one
     part of it ends and the next begins — except a first one, which would
     otherwise put a blank sheet in front of the opening line. The break
     follows the authored level, not the demoted tag. */
  @page {
    margin: 18mm 16mm;
  }

  .y-prose pre,
  .y-prose blockquote,
  .y-prose .callout,
  .y-prose .embed,
  .y-prose .mermaid-diagram,
  .y-prose table,
  .y-prose tr,
  .y-slotcard,
  .y-findings tr {
    break-inside: avoid;
  }

  .y-title,
  .y-prose [data-level='1'],
  .y-prose [data-level='2'],
  .y-prose [data-level='3'],
  .y-prose [data-level='4'] {
    break-after: avoid;
  }

  .y-prose [data-level='1']:not(:first-child) {
    break-before: page;
  }

  .y-prose p,
  .y-prose li {
    orphans: 2;
    widows: 2;
  }
}

/* Keyboard help: a native popover the header's ? opens. It exists because the
   single-key shortcuts had no written trace anywhere on screen — a switch for
   keys nobody can list is pressure, not a preference. At phone widths the
   control that opens it is inside the folded group rather than gone: a phone
   can have a keyboard attached, and a reader who has one was the only person
   the panel was ever for. */
.y-kbdhelp {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-2);
  color: var(--fg);
  inset: auto;
  margin: 0;
  max-width: 340px;
  padding: 16px 18px;
  position: fixed;
  right: 16px;
  top: 64px;
}

.y-kbdhelp__title {
  color: var(--fg);
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  font-weight: var(--fw-semibold);
  line-height: 1.25rem;
  margin: 0 0 10px;
}

.y-kbdhelp__list {
  display: grid;
  gap: 8px 12px;
  grid-template-columns: auto 1fr;
  margin: 0;
}

.y-kbdhelp__list dt {
  white-space: nowrap;
}

.y-kbdhelp .ui-kbd {
  background: var(--wash);
  border-radius: var(--r-sm);
  padding: 3px 6px;
}

.y-kbdhelp__list dd {
  color: var(--fg-muted);
  font-size: var(--fs-13);
  line-height: 1.6;
  margin: 0;
}
/* The one line a folder carrying no vault contract gets: a fact about what this
   folder is, set below the key list and quieter than it, because it explains an
   absence rather than offering an action. */
.y-kbdhelp__note {
  border-top: 1px solid var(--border-faint);
  color: var(--fg-subtle);
  font-size: var(--fs-12);
  line-height: 1.6;
  margin: 12px 0 0;
  padding-top: 10px;
}
/* Cited-by and declared sources share spacing: two lists of names in the
   rail, answering different questions. Cited-by keeps its heading when the
   list is empty, because a note nothing cites in the text is a finding
   rather than a blank — the block disappearing would leave the reader
   unable to tell an island from a feature this interface does not have.
   Declared sources hide when the note wrote none. */
.y-citedby,
.y-basedon {
  margin-top: 0;
}

.y-basedon__groups,
.y-basedon__locations {
  list-style: none;
  margin: 0;
  padding: 0;
}

.y-basedon__locations {
  margin-inline-start: 14px;
}

.y-basedon__places {
  margin-left: 1rem;
}

.y-basedon .ui-navitem {
  overflow-wrap: anywhere;
}

.y-basedon .ui-navitem:not([href]) {
  cursor: default;
}

.y-basedon__reason {
  color: var(--fg-subtle);
  display: block;
  font-size: var(--fs-12);
  margin-top: 3px;
}

.y-basedon .wikilink-degraded {
  -webkit-text-decoration: underline dotted;
  text-decoration: underline dotted;
  text-underline-offset: 3px;
}

.y-citedby__none {
  color: var(--fg-subtle);
  font-size: var(--fs-13);
  line-height: 1.6;
  margin: 4px 0 0;
}

/* Whole-folder health: a reading page, not a dashboard. The file a finding is
   about is set at reading size, because that name is what a person is deciding
   whether to open; everything the machinery contributes beside it — the weight,
   the count, a path, an error — stays in the monospace face at label size, so
   one glance down the column reads as one thing. */
.y-healthlede {
  color: var(--fg-subtle);
  font-family: var(--font-serif);
  font-size: var(--fs-ed-17);
  line-height: var(--lh-read);
  margin: 18px 0 0;
}

.y-healthsection {
  margin: 36px 0 0;
}

.y-healthsection__title {
  align-items: center;
  color: var(--fg);
  display: flex;
  font-family: var(--font-serif);
  font-size: var(--fs-ed-24);
  font-weight: var(--fw-regular);
  gap: 10px;
  margin: 0;
}

/* A standing notice qualifies every row beneath it, so it is the one section on
   the page that carries the warning tint. */
.y-healthsection--notice {
  background: var(--warn-bg);
  border-radius: var(--r-lg);
  padding: 20px 24px;
}

.y-healthsection--notice .y-healthsection__lede {
  color: var(--fg-muted);
}

.y-healthsection__lede {
  color: var(--fg-subtle);
  font-family: var(--font-serif);
  font-size: var(--fs-13);
  line-height: 1.7;
  margin: 6px 0 0;
}

.y-healthlink {
  color: var(--fg);
  font-family: var(--font-serif);
  font-size: var(--fs-ed-17);
  text-decoration: none;
}

.y-healthlink:hover {
  text-decoration: underline;
}

/* The report's own shape, in the same label face as the table's own headings
   below it: monospace, small, and no colour of its own — a weight already
   carries the table's badge colour, and everything else here stays neutral. */
.y-healthshape {
  align-items: center;
  color: var(--fg-faint);
  display: flex;
  flex-wrap: wrap;
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  gap: 6px 10px;
  margin: 14px 0 0;
}

.y-healthshape__weight {
  align-items: center;
  color: inherit;
  display: inline-flex;
  gap: 6px;
  text-decoration: none;
}

.y-healthshape__weight:hover {
  color: var(--fg);
}

/* The findings, as one table. A hairline under the header and one between rows
   is the whole frame: a box around a ledger adds a line for every line it
   already has. */
.y-findings {
  border-collapse: collapse;
  margin: 26px 0 0;
  text-align: left;
  width: 100%;
}

.y-findings__head {
  border-bottom: 1px solid var(--border-strong);
  color: var(--fg-faint);
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  font-weight: var(--fw-regular);
  padding: 0 12px 8px 0;
  vertical-align: bottom;
  white-space: nowrap;
}

.y-findings__head--count {
  padding-right: 0;
  text-align: right;
}

/* The count column is read down its right edge, so its heading ends there too:
   the arrow that offers another order stands before the word rather than
   after it, and the word sits flush over the figures. */
.y-findings__head--count .y-findings__sort {
  align-items: baseline;
  display: inline-flex;
  flex-direction: row-reverse;
  gap: 5px;
}

.y-findings__head--count .y-findings__sort::after {
  margin-left: 0;
}
/* Every heading is a link that asks the server for the same table in another
   order, so the mark that says so belongs on all of them; the one the table is
   actually in carries the reader's own ink. */
.y-findings__sort {
  color: inherit;
  text-decoration: none;
}

.y-findings__sort::after {
  content: '↕';
  margin-left: 5px;
  opacity: 0.45;
}

.y-findings__sort:hover {
  color: var(--fg);
}

.y-findings__head[aria-sort] .y-findings__sort {
  color: var(--fg);
}

.y-findings__head[aria-sort='ascending'] .y-findings__sort::after {
  content: '↑';
  opacity: 1;
}

.y-findings__head[aria-sort='descending'] .y-findings__sort::after {
  content: '↓';
  opacity: 1;
}

.y-findings tbody td {
  border-top: 1px solid var(--border-faint);
  padding: 11px 12px 11px 0;
  vertical-align: baseline;
}

/* The last column is the count, read down the table's right edge; its figures
   end there, under the end of their heading, rather than a gutter short of it. */
.y-findings tbody td:last-child {
  padding-right: 0;
}

.y-findings__path {
  color: var(--fg);
  font-family: var(--font-mono);
  font-size: var(--fs-12);
  overflow-wrap: anywhere;
}

.y-findings__kind {
  color: var(--fg);
  font-family: var(--font-sans);
  font-size: var(--fs-13);
}

.y-findings__detail {
  color: var(--fg-subtle);
  display: block;
  font-size: var(--fs-12);
  margin-top: 3px;
  overflow-wrap: anywhere;
}

.y-findings__detail .y-healthlink {
  font-size: var(--fs-13);
}

.y-findings__count {
  color: var(--fg-faint);
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  padding-right: 0;
  text-align: right;
}

/* The weight the judging face gives a finding, in that face's own word. Error
   and warn used to share one ink and differ only by a border, which reads as
   one colour at a glance. On screen they are pulled further apart by two more
   channels: the marker's shape — squared for error, round for warn — and
   error's ink, a hue of its own rather than a darker warning, never the same
   colour. Neither borrows the vermilion, which marks the reader's own place
   and nothing else. The border is what a
   printed page keeps no matter how a reader's printer treats background
   colour: the marker's dot is a background fill, and a printer that drops
   background graphics drops it with it. */
.y-severity {
  align-items: center;
  background: var(--elevated);
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  color: var(--fg-subtle);
  display: inline-flex;
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  font-weight: var(--fw-medium);
  gap: 6px;
  line-height: 1;
  padding: 3px 8px 3px 7px;
}

.y-severity::before {
  background: currentcolor;
  border-radius: 50%;
  content: '';
  flex: none;
  height: 6px;
  width: 6px;
}

.y-severity--error {
  background: var(--error-bg);
  border-color: var(--error);
  color: var(--error);
}

.y-severity--error::before {
  border-radius: 1px;
}

.y-severity--warn {
  background: var(--warn-bg);
  color: var(--warn);
}

.y-severity--info {
  background: var(--panel);
  border-color: var(--border-faint);
  color: var(--fg-faint);
}

/* What each kind of finding means, under the table rather than over it. A
   reader who knows what a row claims came for the rows; the sentences are here
   for the one who does not, and they are the same sentences the page used to
   open every list with. */
.y-findingsguide {
  margin: 30px 0 0;
}

.y-findingsguide__head {
  align-items: center;
  color: var(--fg-muted);
  cursor: pointer;
  display: inline-flex;
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  gap: 7px;
}

.y-findingsguide__head:hover {
  color: var(--fg);
}

.y-findingsguide__list {
  margin: 14px 0 0;
}

.y-findingsguide__term {
  align-items: baseline;
  color: var(--fg);
  display: flex;
  font-family: var(--font-sans);
  font-size: var(--fs-13);
  gap: 10px;
  margin: 16px 0 0;
}

.y-findingsguide__meaning {
  color: var(--fg-subtle);
  font-family: var(--font-serif);
  font-size: var(--fs-13);
  line-height: 1.7;
  margin: 4px 0 0;
}

/* Which rows of the report are on this page, set in the same label face as the
   shape line it follows and quieter than it: the shape is what the folder owes,
   this is only where in the list the reader is standing. */
.y-healthrange {
  color: var(--fg-faint);
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  margin: 6px 0 0;
}

/* ============================================================
   The strip under a listing that runs past one page
   ============================================================ */

/* Shared by the search results and the findings table, because a reader who
   has learned to step through one has learned to step through the other. It is
   a row of plain links in the label face, wrapping rather than scrolling at a
   phone's width, and each target is large enough to be hit with a thumb. */
.y-pager {
  align-items: center;
  border-top: 1px solid var(--border-faint);
  display: flex;
  flex-wrap: wrap;
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  gap: 4px 6px;
  margin: 26px 0 0;
  padding-top: 14px;
}

.y-pager__pages {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: 4px 6px;
  list-style: none;
  margin: 0;
  padding: 0;
}

.y-pager__step,
.y-pager__page,
.y-pager__whole {
  align-items: center;
  border-radius: var(--r-sm);
  color: var(--fg-subtle);
  display: inline-flex;
  justify-content: center;
  min-height: 32px;
  padding: 0 10px;
  text-decoration: none;
}

.y-pager__page {
  min-width: 32px;
  padding: 0 6px;
}

.y-pager__step:hover,
.y-pager__page:hover,
.y-pager__whole:hover {
  background: var(--overlay);
  color: var(--fg);
}

/* The page being read is marked by the attribute that says so, not by a class
   beside it, so what a reader hears and what the stylesheet paints cannot come
   apart. It is also the one item that is not a step to somewhere else, which is
   why it is the only one with a ground of its own. */
.y-pager__page[aria-current='page'] {
  background: var(--wash);
  color: var(--fg);
  font-weight: var(--fw-medium);
}

/* The undivided listing sits apart from the numbers: it is not another page,
   it is all of them, and it is what a reader follows before printing. */
.y-pager__whole {
  color: var(--fg-faint);
  margin-left: auto;
}

@media (max-width: 520px) {
  /* At a phone's width the strip has already wrapped, and a link pushed to the
     far edge of a line of its own reads as unrelated to the numbers above it. */
  .y-pager__whole {
    margin-left: 0;
  }
}

/* Narrow: the table stacks instead of scrolling sideways, because a sheet the
   reader has to push left and right is one they read half of. Each cell then
   carries its column's own name, so a stacked row still says what each value
   is. The headings stay — they are the ordering controls, and losing them at
   the width where the table is longest would take the ordering away exactly
   where it helps most. */
@media screen and (max-width: 720px) {
  .y-findings,
  .y-findings thead,
  .y-findings tbody,
  .y-findings tr,
  .y-findings th,
  .y-findings td {
    display: block;
  }

  .y-findings thead tr {
    border-bottom: 1px solid var(--border-strong);
    display: flex;
    flex-wrap: wrap;
    gap: 4px 16px;
    padding-bottom: 8px;
  }

  .y-findings__head {
    border-bottom: none;
    padding: 0;
  }

  .y-findings__head--count {
    text-align: left;
  }

  .y-findings tbody tr {
    border-top: 1px solid var(--border-faint);
    padding: 10px 0;
  }

  .y-findings tbody td {
    border-top: none;
    display: grid;
    gap: 2px 12px;
    grid-template-columns: minmax(0, 5.5em) minmax(0, 1fr);
    padding: 3px 0;
  }

  .y-findings tbody td::before {
    color: var(--fg-faint);
    content: attr(data-col);
    font-family: var(--font-mono);
    font-size: var(--fs-12);
  }
  /* The label owns the first column and everything the cell holds owns the
     second, however many pieces that is. A cell with a name and evidence under
     it would otherwise put the evidence back under the label, where it reads
     as a second label and wraps into a fifth of the width it needs. */
  .y-findings tbody td > * {
    grid-column: 2;
  }
  /* A badge is as wide as the word in it. Laid out as a cell of a grid it
     would stretch the full column, and a coloured block the width of the
     screen is a banner rather than a mark beside a row. */
  .y-findings__severity .y-severity {
    justify-self: start;
  }
  /* A cell with nothing in it goes, label and all: a stacked row is a list of
     what this finding has, and "weight" over a blank line says a weight is
     missing rather than that none was ever assigned. Laid out as a table the
     empty cell is just white space in its column, so this is the narrow
     width's problem alone. */
  .y-findings tbody td:empty {
    display: none;
  }

  .y-findings__count {
    text-align: left;
  }
}

/* The whole-folder view's entrance. It is a destination rather than something
   read from, so on a window too narrow for the whole row it folds away with
   the other destinations instead of disappearing: a reader who cannot see it
   can still reach it, which is not what the width it used to vanish at
   offered. */
.y-healthlinkbtn {
  border-radius: var(--r-md);
  color: var(--fg-muted);
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  font-weight: var(--fw-medium);
  line-height: 1.25rem;
  padding: 6px 8px;
  text-decoration: none;
  white-space: nowrap;
}

.y-healthlinkbtn:hover {
  background: var(--wash);
  color: var(--fg);
}

/* The reading choices, reached by a word rather than a gear, because a gear
   promises a machine and what is behind this one is seven sentences, so the
   label says the thing and the panel behind it does the explaining. It is
   reachable at every width — a reader on a narrow window is the one most
   likely to want the text larger, and an entry that disappeared there would
   disappear exactly when it is needed. On a narrow window it is the first
   thing in the folded group rather than absent. */
.y-prefslink {
  border-radius: var(--r-md);
  color: var(--fg-muted);
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  font-weight: var(--fw-medium);
  line-height: 1.25rem;
  padding: 6px 8px;
  text-decoration: none;
  white-space: nowrap;
}

.y-prefslink:hover {
  background: var(--wash);
  color: var(--fg);
}

/* The page a header link leads to, while the reader is on it: the ink, and a
   line under the word drawn inside the link's own box, so marking it moves
   nothing in a row whose width is measured to the pixel. */
.y-healthlinkbtn[aria-current='page'],
.y-prefslink[aria-current='page'] {
  color: var(--fg);
  position: relative;
}

.y-healthlinkbtn[aria-current='page']::after,
.y-prefslink[aria-current='page']::after {
  background: var(--fg);
  bottom: 0;
  content: '';
  height: 2px;
  left: 8px;
  position: absolute;
  right: 8px;
}
/* The same page again, from the panel that explains how the interface behaves:
   a reader who opened this one was already asking that question. */
.y-kbdhelp__prefs {
  border-top: 1px solid var(--line-faint);
  color: var(--fg-muted);
  display: block;
  font-size: var(--fs-12);
  margin: 12px 0 0;
  padding-top: 10px;
  text-decoration: none;
}

.y-kbdhelp__prefs:hover {
  text-decoration: underline;
}

/* The reading choices, one page: a column of sections named by a small
   heading, not boxes cut by a title — the rest of the interface separates a
   group of controls by space and a label, never a frame, and this page had
   been the one place that drew one. The sentence under each heading — what
   the choice changes, and what it leaves alone — is the part that earns a
   heading of its own. The sections sit inside one form, which is what
   applies them where nothing else can, so what a reader picks across the
   column is applied together. No rail down the side, because the page is
   reached from whatever was being read and gives it straight back. */
.y-prefs__lede {
  color: var(--fg-subtle);
  font-family: var(--font-serif);
  font-size: var(--fs-ed-17);
  line-height: var(--lh-read);
  margin: 18px 0 0;
}

/* Each choice is one row: its name and what it changes on the left, the
   control on the right, and a faint hairline between one choice and the next,
   so the eye runs down the names and across to the setting. The words are the
   interface's own, so they are set in its face rather than the reading one.
   A phone has no room for two columns and stacks them.

   The legend is floated, which is the one thing that makes a fieldset's legend
   an ordinary box the grid can place; left to the browser it is drawn in the
   fieldset's frame, above the grid. */
.y-preffield {
  border: 0;
  border-top: 1px solid var(--line-faint);
  column-gap: 32px;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  margin: 0;
  min-width: 0;
  padding: 20px 0;
}

.y-prefs__lede + form > .y-preffield:first-child {
  margin-top: 24px;
}

.y-preffield legend {
  color: var(--fg);
  float: left;
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  font-weight: var(--fw-medium);
  grid-column: 1;
  line-height: 1.25rem;
  margin: 0;
  padding: 0;
}

.y-preffield__note {
  color: var(--fg-muted);
  font-family: var(--font-sans);
  font-size: var(--fs-13);
  grid-column: 1;
  line-height: 1.25rem;
  margin: 4px 0 0;
}

.y-preffield__options {
  align-self: center;
  display: flex;
  flex-wrap: wrap;
  gap: 8px 18px;
  grid-column: 2;
  grid-row: 1 / span 2;
  justify-content: flex-end;
  margin: 0;
}

.y-preffield > .y-prefs__apply {
  align-self: center;
  grid-column: 2;
  grid-row: 1 / span 2;
  margin: 0;
}

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

  .y-preffield__options,
  .y-preffield > .y-prefs__apply {
    grid-column: 1;
    grid-row: auto;
    justify-content: flex-start;
    justify-self: start;
    margin-top: 12px;
  }
}

.y-preffield__options label {
  align-items: center;
  color: var(--fg);
  cursor: pointer;
  display: flex;
  font-size: var(--fs-14);
  gap: 7px;
  min-width: 0;
}

.y-preffield__options input {
  accent-color: var(--fg);
  cursor: pointer;
  flex: none;
  height: 14px;
  margin: 0;
  width: 14px;
}
/* A choice the browser would not keep says so under its own options, and takes
   up no room on a page where nothing has gone wrong — an empty block box is
   zero-height, so only its margin has to go. */
.y-preffield__status {
  grid-column: 1 / -1;
  margin: 12px 0 0;
}
/* The submit sits below the panels it applies rather than inside any one of
   them, because it belongs to all of them: a button inside a panel would read
   as that panel's, which is what it used to be and what discarded the rest. */
.y-prefs__apply {
  background: var(--elevated);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  color: var(--fg);
  cursor: pointer;
  font-family: var(--font-sans);
  font-size: var(--fs-13);
  margin: 16px 0 0;
  padding: 6px 14px;
}

.y-prefs__apply:hover {
  background: var(--overlay);
}
/* Two controls for one spot, and never both. Where the page keeps a choice as
   it is picked, the submit above has nothing left to do and the way back is
   what the reader wants from that corner of the page; where nothing is running
   to keep a choice, the submit is the only thing that applies one and a link
   out would leave with everything picked and nothing kept. */
[data-js] .y-prefs__fallback {
  display: none;
}

.y-prefs__return {
  background: var(--elevated);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  color: var(--fg);
  display: none;
  font-family: var(--font-sans);
  font-size: var(--fs-13);
  margin: 16px 0 0;
  padding: 6px 14px;
  text-decoration: none;
}

[data-js] .y-prefs__return {
  display: inline-block;
}

.y-prefs__return:hover {
  background: var(--overlay);
}

/* The keys this interface answers to, named as a section like the choices
   above it: a reader who reaches this page already asked how the interface
   behaves, so the list the header's own help popover carries earns the same
   heading here rather than sitting under nothing. */
.y-prefs__shortcuts {
  border-top: 1px solid var(--line-faint);
  margin: 20px 0 0;
  padding: 20px 0;
}

.y-prefs__shortcuts h2 {
  color: var(--fg);
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  font-weight: var(--fw-medium);
  margin: 0;
}

.y-prefs__shortcuts .y-kbdhelp__list {
  margin: 14px 0 0;
}

/* What this browser is holding, listed by the page that puts it there. */
.y-prefs__storage {
  border-top: 1px solid var(--border);
  margin: 42px 0 0;
  padding-top: 20px;
}

.y-prefs__storage h2 {
  color: var(--fg);
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  font-weight: var(--fw-medium);
  margin: 0;
}

.y-prefs__storage ul {
  display: grid;
  gap: 6px;
  list-style: none;
  margin: 12px 0 0;
  padding: 0;
}

.y-prefs__storage li {
  color: var(--fg-muted);
  font-family: var(--font-mono);
  font-size: var(--fs-12);
  line-height: 1.6;
  overflow-wrap: anywhere;
}

.y-prefs__storage p {
  color: var(--fg-muted);
  font-family: var(--font-sans);
  font-size: var(--fs-13);
  line-height: 1.25rem;
  margin: 12px 0 0;
}
/* One folder seen whole. A reading page, not a file manager: no sizes, no
   dates, no sort controls — the captured order, which is the folder's own. */
.y-crumbs__link {
  color: inherit;
  text-decoration: none;
}

.y-crumbs__link:hover {
  color: var(--fg);
  text-decoration: underline;
}

/* An index says how much it holds once, as a quiet fact under its title. */
.y-home__head > .y-indexcount {
  color: var(--fg-subtle);
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  margin: 8px 0 0;
}

/* Footnotes already close the piece with a hairline of their own, so the way
   onward under them stands off by space alone; a second rule a few lines below
   the first read as two endings. */
.y-article:has(.footnotes) .y-steps {
  border-top: 0;
  margin-top: 48px;
}

/* The way onward, closing the article. The source line names the order the
   links walk — a course's declared order or the folder's file adjacency —
   because the two can disagree completely and nothing else on the links says
   which one the reader is holding. It takes the full first row; the two links
   share the second. */
.y-steps {
  border-top: 1px solid var(--line);
  display: flex;
  flex-wrap: wrap;
  gap: 12px 16px;
  justify-content: space-between;
  margin: 64px 0 0;
  padding-top: 32px;
}

.y-steps__source {
  color: var(--fg-subtle);
  flex: 0 0 100%;
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  margin: 0;
  overflow-wrap: anywhere;
}

/* The course's title in the source line is a way to its contents, and reads as
   the quiet line it replaced until it is pointed at. */
.y-steps__path {
  color: inherit;
  text-decoration: none;
}

.y-steps__path:hover {
  color: var(--fg);
  text-decoration: underline;
}

.y-steps__link {
  border-radius: var(--r-lg);
  color: var(--fg);
  display: flex;
  flex: 0 1 calc(50% - 8px);
  flex-direction: column;
  gap: 2px;
  margin: -12px -16px;
  min-width: 0;
  padding: 12px 16px;
  text-decoration: none;
}

.y-steps__link:hover {
  background: var(--wash);
}

.y-steps__link--next {
  margin-left: auto;
  text-align: right;
}

.y-steps__role {
  color: var(--fg-subtle);
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  line-height: 1.25rem;
  white-space: nowrap;
}

.y-steps__name {
  font-family: var(--font-serif);
  font-size: var(--fs-16);
  font-weight: var(--fw-medium);
  line-height: 1.5rem;
  overflow-wrap: anywhere;
  text-wrap: balance;
}

.y-steps__dir {
  color: var(--fg-subtle);
  font-family: var(--font-sans);
}

/* A side branch hanging from this lesson, as one quiet line under the steps. It
   is beside them and never one of them, so it takes a row of its own and the
   lesson's own previous and next keep the places they always had. */
.y-steps__aside {
  color: var(--fg-subtle);
  flex: 0 0 100%;
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  margin: 0;
  overflow-wrap: anywhere;
}

.y-steps__asidelink {
  color: var(--fg-muted);
  text-decoration: none;
}

.y-steps__asidelink:hover {
  color: var(--fg);
  text-decoration: underline;
}

/* The siblings block is a neighbourhood, not the folder — it says what it left
   out and where the whole of it lives. */
.y-here__more {
  color: var(--fg-subtle);
  display: block;
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  padding: 6px 10px;
  text-decoration: none;
}

.y-here__more:hover {
  color: var(--fg);
}

/* What the filter could not reach. It sits with the empty-result line because
   they answer the same question — why is this list shorter than I expected —
   and one of them is always the more honest answer. */
.y-filter-partial {
  color: var(--fg-subtle);
  font-family: var(--font-serif);
  font-size: var(--fs-12);
  line-height: 1.6;
  margin: 8px 8px 4px;
}

.y-filter-partial a {
  color: var(--fg);
  text-decoration: none;
}

.y-filter-partial a:hover {
  text-decoration: underline;
}

/* The hover preview: the note a link leads to, shown beside that link. Where it
   lands is settled here rather than in script — the client names an anchor on
   the link under the pointer and nothing else about geometry — and the
   fallbacks are what keeps a card opened near an edge of the window on screen.
   The excerpt scrolls inside the card without taking the page with it. */
.y-prose .wikilink[data-preview-open] {
  anchor-name: --y-preview-anchor;
  text-decoration-color: color-mix(in oklch, var(--fg) 60%, transparent);
}

/* A declared source owns the card the same way, and shows it with the row
   colouring a pointer resting on it already gets, so the card's owner is
   still readable after the pointer has crossed into the card. */
.y-basedon .ui-navitem[data-preview-open] {
  anchor-name: --y-preview-anchor;
  background: var(--overlay);
  color: var(--fg);
}

.y-preview {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-2);
  color: var(--fg);
  inline-size: min(38rem, calc(100vw - 2rem));
  inset: auto;
  margin: 0;
  max-block-size: min(18rem, 60vh);
  opacity: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 14px 16px;
  position: fixed;
  position-anchor: --y-preview-anchor;
  position-area: block-end span-inline-end;
  position-try-fallbacks: flip-block, flip-inline, block-start span-inline-start;
  transition: display var(--dur-base) var(--ease-standard), overlay var(--dur-base) var(--ease-standard), opacity var(--dur-base) var(--ease-standard);
  transition-behavior: allow-discrete;
}

.y-preview:popover-open {
  opacity: 1;
}
@starting-style {
  .y-preview:popover-open {
    opacity: 0;
  }
}

.y-preview__body {
  contain-intrinsic-block-size: auto 20rem;
  content-visibility: auto;
}
/* The excerpt opens flush: the space a first heading or paragraph normally
   keeps above it belongs to a page, and the card is not one. */
.y-preview .y-prose > :first-child {
  margin-block-start: 0;
}
/* The line above the excerpt: which note this is, and the way to it. Quiet
   enough to read past on the way to the words, and the only thing on the card
   that is not the other note's. */
.y-preview__source {
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  line-height: 1.5;
  margin: 0 0 10px;
}

.y-preview__sourcelink {
  color: var(--fg-muted);
  text-decoration: none;
}

.y-preview__sourcelink:hover {
  color: var(--fg);
}

.y-preview__sep {
  color: var(--fg-faint);
  margin: 0 6px;
}

.y-preview__section {
  color: var(--fg-subtle);
}
/* The two sentences the card says in its own voice: why it is empty, and where
   the rest of a note it cut short can be read. */
.y-preview__notice {
  color: var(--fg-subtle);
  font-family: var(--font-serif);
  font-size: var(--fs-13);
  line-height: 1.6;
  margin: 0;
}

/* ============================ THE READER'S OWN MARK ============================ */
/* The control is hidden until the document says a script is running. This is
   the one control on a reading page with no version for a browser without
   one, because what it sends is where the window is and there is no such
   thing to send without a script. */
.y-markset {
  display: none;
  margin: 0;
}

[data-js] .y-markset {
  display: block;
}

.y-markset__note {
  color: var(--fg-subtle);
  font-family: var(--font-sans);
  font-size: var(--fs-12);
  line-height: 1.5;
  margin: 8px 0 0;
}

.y-markset__said {
  margin: 8px 0 0;
}

/* The same verb again in the header's folded panel, which is the only home a
   narrow window has for it: the rail is dropped below its own width, and the
   header is the piece of the page that stays put while the note scrolls.

   Drawn only while the panel is open. Above the width the panel folds at, the
   box around its controls dissolves and each of them becomes an item of the
   header row — this one never does, because at that width the row has nothing
   left to give and the wordmark would be what pays for it. The script is the
   second condition, for the reason the rail's control has it: what the press
   sends is where the window is.

   Its own row inside the panel rather than another item wrapped in beside the
   icons, because it is a sentence-long verb about the note and they are marks
   about the interface. */
.y-headermark {
  display: none;
  flex-basis: 100%;
}

[data-js] .y-headerfold:popover-open .y-headermark {
  display: block;
}

.y-headermark__said {
  margin: 8px 0 0;
}

/* The reader's own place, in the one accent the interface has: a bookmark. It is
   drawn wherever the place that was kept is shown — beside the lesson that
   holds it in a course and in the rail, on the link that returns to it, and on
   the control that keeps it where the note already holds it — and nowhere else,
   so the colour means one thing. A shape cut from a filled box, because paper
   prints a fill and a border-drawn ribbon would not close. */
.y-keptmark,
[data-mark-kept] [data-mark-button]::before {
  background: var(--accent);
  clip-path: polygon(0 0, 100% 0, 100% 100%, 50% 72%, 0 100%);
  display: inline-block;
  flex: none;
  height: 12px;
  width: 8px;
}

[data-mark-kept] [data-mark-button]::before {
  content: '';
  margin-inline-end: 8px;
  vertical-align: -1px;
}

.y-keptplace {
  align-items: center;
  display: inline-flex;
  flex: none;
}

/* The way back to the place the reader kept, ahead of the four ways in. */
.y-continue {
  background: var(--elevated);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  margin: 0 0 28px;
  padding: 16px 18px;
}

.y-continue__note {
  color: var(--fg);
  display: block;
  font-family: var(--font-serif);
  font-size: var(--fs-15);
  line-height: 1.5;
  margin: 8px 0 0;
}

/* The way back to the place is the reader's own, so it takes their colour. A
   row whose note is gone is a sentence and keeps the ink. */
.y-continue__note[href] {
  color: var(--accent);
}

.y-continue__note[href]:hover {
  text-decoration: underline;
}

.y-continue__note > .y-keptmark {
  margin-inline-end: 8px;
  vertical-align: -1px;
}

.y-continue__notice {
  color: var(--fg-muted);
  font-family: var(--font-serif);
  font-size: var(--fs-13);
  line-height: 1.6;
  margin: 8px 0 0;
}

.y-continue__device {
  color: var(--fg-subtle);
  font-family: var(--font-serif);
  font-size: var(--fs-12);
  line-height: 1.6;
  margin: 8px 0 0;
}
